From c3af2858bdbe5bccd432581882bda91f5d3e2ffc Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:29:04 +0200 Subject: [PATCH] feat(compute): collapse large instance sizes behind expand toggle DEBUG-50 (#44189) ## Problem The Compute and Disk settings page shows all 12+ instance sizes (Nano through >16XL) in a grid, which requires scrolling on smaller screens like a 13" MacBook. This makes it impossible to see all three sections (compute size, disk size, advanced settings) in a single view. Free tier users have no need to see 8XL, 12XL, or 16XL options. ## Fix Show only the first 6 compute sizes (Nano through XL) by default, cutting the grid from 4 rows to 2 rows. Larger sizes (2XL and above, plus the >16XL contact card) are revealed via a "Show all sizes" toggle button below the grid. The grid auto-expands if the project is already running on a large instance size so the selected option is always visible. ## How to test - Go to Settings > Compute and Disk on a project - Verify only 6 cards are visible by default (Nano, Micro, Small, Medium, Large, XL) - Verify the Disk size and Advanced settings sections are visible without scrolling on a 13" screen - Click "Show all sizes" and verify the remaining sizes appear (2XL through 16XL plus the >16XL contact card) - Click "Show fewer sizes" and verify the grid collapses back to 6 cards - On a project running 4XL or larger, verify the grid opens expanded by default so the selected card is visible ## Summary by CodeRabbit * **New Features** * Compute size options now display in a compact view with a toggle to expand and show all available sizes * Auto-expands to reveal the currently selected compute size when it's not visible in the initial view * Enhanced loading state for improved visual clarity --------- Co-authored-by: Claude Sonnet 4.6 --- .../fields/ComputeSizeField.tsx | 170 ++++++++++++------ 1 file changed, 111 insertions(+), 59 deletions(-) diff --git a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx index 0a8ecbc8d4d..e758e52d73c 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx @@ -1,9 +1,20 @@ import { SupportCategories } from '@supabase/shared-types/out/constants' import { useParams } from 'common' -import { CpuIcon, Lock, Microchip } from 'lucide-react' -import { useMemo } from 'react' +import { SupportLink } from 'components/interfaces/Support/SupportLink' +import { DocsButton } from 'components/ui/DocsButton' +import { InlineLink } from 'components/ui/InlineLink' +import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' +import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' +import { DOCS_URL } from 'lib/constants' +import { ChevronRight, CpuIcon, Lock, Microchip } from 'lucide-react' +import { useEffect, useMemo, useState } from 'react' import { UseFormReturn } from 'react-hook-form' import { + Button, cn, FormField_Shadcn_, RadioGroupCard, @@ -26,16 +37,8 @@ import { import { BillingChangeBadge } from '../ui/BillingChangeBadge' import FormMessage from '../ui/FormMessage' import { NoticeBar } from '../ui/NoticeBar' -import { SupportLink } from '@/components/interfaces/Support/SupportLink' -import { DocsButton } from '@/components/ui/DocsButton' -import { InlineLink } from '@/components/ui/InlineLink' -import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query' -import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' -import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' -import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' -import { getCloudProviderArchitecture } from '@/lib/cloudprovider-utils' -import { DOCS_URL, InstanceSpecs } from '@/lib/constants' + +const INITIALLY_VISIBLE_COUNT = 6 /** * to do: this could be a type from api-types @@ -79,6 +82,21 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { return getAvailableComputeOptions(availableAddons, project?.cloud_provider) }, [availableAddons, project?.cloud_provider]) + // Expand by default if the project's current compute size is beyond the initial visible set + const [showAllSizes, setShowAllSizes] = useState(() => { + const idx = availableOptions.findIndex((o) => o.identifier === computeSize) + return idx >= INITIALLY_VISIBLE_COUNT + }) + + // Expand whenever the selected size falls outside the visible set — covers both initial data + // load (availableOptions starts empty) and computeSize changes after mount (e.g. form reset) + useEffect(() => { + const idx = availableOptions.findIndex((o) => o.identifier === computeSize) + if (idx >= INITIALLY_VISIBLE_COUNT) { + setShowAllSizes(true) + } + }, [computeSize, availableOptions]) + const subscriptionPitr = addons?.selected_addons.find((addon) => addon.type === 'pitr') const computeSizePrice = calculateComputeSizePrice({ @@ -91,6 +109,17 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { const projectComputeSize = project?.infra_compute_size ?? 'nano' const showUpgradeBadge = entitledUpdateCompute && projectComputeSize === 'nano' + const selectedOptionIndex = availableOptions.findIndex((o) => o.identifier === computeSize) + const selectedOptionIsHidden = selectedOptionIndex >= INITIALLY_VISIBLE_COUNT + + // Always show all options if the selected one would be outside the visible slice, + // so the active card is never hidden from the user. + const visibleOptions = + showAllSizes || selectedOptionIsHidden + ? availableOptions + : availableOptions.slice(0, INITIALLY_VISIBLE_COUNT) + const hasHiddenOptions = availableOptions.length > INITIALLY_VISIBLE_COUNT + return ( {isLoading ? ( - Array(10) + Array(INITIALLY_VISIBLE_COUNT) .fill(0) .map((_, i) => ) ) : addonsError ? ( @@ -166,7 +195,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { ) : ( <> - {availableOptions.map((compute) => { + {visibleOptions.map((compute) => { const cpuArchitecture = getCloudProviderArchitecture(project?.cloud_provider) const lockedMicroDueToPITR = @@ -205,7 +234,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { key={compute.identifier} value={compute.identifier} className={cn( - 'relative text-sm text-left flex flex-col gap-0 px-0 py-3 [&_label]:w-full group] w-full h-[110px]', + 'relative text-sm text-left flex flex-col gap-0 px-0 py-3 [&_label]:w-full group w-full h-[110px]', lockedOption && 'opacity-50' )} disabled={disabled || lockedOption} @@ -293,58 +322,81 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { ) })} - e.preventDefault()} - className={cn( - 'relative text-sm text-left flex flex-col gap-0 px-0 py-3 [&_label]:w-full group] w-full h-[110px]' - )} - label={ - -
-
- + {showAllSizes && ( + e.preventDefault()} + className={cn( + 'relative text-sm text-left flex flex-col gap-0 px-0 py-3 [&_label]:w-full group w-full h-[110px]' + )} + label={ + +
+
+ -
- Contact Us -
-
-
-
-
- - Custom memory +
+ Contact Us
-
- - Custom CPU +
+
+
+
+ + Custom memory +
+
+ + Custom CPU +
-
- - } - /> + + } + /> + )} )}
+ + {!isLoading && !addonsError && hasHiddenOptions && ( + + )} )}