mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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 <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c30d3e62de
commit
c3af2858bd
1 file changed
+111
-59
@@ -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 (
|
||||
<FormField_Shadcn_
|
||||
name="computeSize"
|
||||
@@ -157,7 +186,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
Array(10)
|
||||
Array(INITIALLY_VISIBLE_COUNT)
|
||||
.fill(0)
|
||||
.map((_, i) => <Skeleton key={i} className="w-full h-[110px] rounded-md" />)
|
||||
) : addonsError ? (
|
||||
@@ -166,7 +195,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
|
||||
</FormMessage>
|
||||
) : (
|
||||
<>
|
||||
{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) {
|
||||
)
|
||||
})}
|
||||
|
||||
<RadioGroupCardItem
|
||||
id="larger-compute"
|
||||
key="larger-compute"
|
||||
showIndicator={false}
|
||||
value="larger-compute"
|
||||
onClick={(e) => 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={
|
||||
<SupportLink
|
||||
queryParams={{
|
||||
projectRef: ref,
|
||||
category: SupportCategories.SALES_ENQUIRY,
|
||||
subject: 'Enquiry about larger instance sizes',
|
||||
}}
|
||||
>
|
||||
<div className="w-full flex flex-col gap-3 justify-between">
|
||||
<div className="relative px-3 flex justify-between">
|
||||
<ComputeBadge infraComputeSize=">16XL" />
|
||||
{showAllSizes && (
|
||||
<RadioGroupCardItem
|
||||
id="larger-compute"
|
||||
key="larger-compute"
|
||||
showIndicator={false}
|
||||
value="larger-compute"
|
||||
onClick={(e) => 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={
|
||||
<SupportLink
|
||||
queryParams={{
|
||||
projectRef: ref,
|
||||
category: SupportCategories.SALES_ENQUIRY,
|
||||
subject: 'Enquiry about larger instance sizes',
|
||||
}}
|
||||
>
|
||||
<div className="w-full flex flex-col gap-3 justify-between">
|
||||
<div className="relative px-3 flex justify-between">
|
||||
<ComputeBadge infraComputeSize=">16XL" />
|
||||
|
||||
<div className="flex items-center space-x-1 opacity-50 ">
|
||||
<span className="text-foreground-light text-sm">Contact Us</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="px-3 text-sm flex flex-col gap-1">
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<Microchip
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>Custom memory</span>
|
||||
<div className="flex items-center space-x-1 opacity-50 ">
|
||||
<span className="text-foreground-light text-sm">Contact Us</span>
|
||||
</div>
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<CpuIcon
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>Custom CPU</span>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="px-3 text-sm flex flex-col gap-1">
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<Microchip
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>Custom memory</span>
|
||||
</div>
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<CpuIcon
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>Custom CPU</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SupportLink>
|
||||
}
|
||||
/>
|
||||
</SupportLink>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isLoading && !addonsError && hasHiddenOptions && (
|
||||
<Button
|
||||
type="default"
|
||||
size="tiny"
|
||||
className="mt-4"
|
||||
aria-expanded={showAllSizes}
|
||||
// Prevent collapsing when the selected size would become hidden
|
||||
disabled={showAllSizes && selectedOptionIsHidden}
|
||||
onClick={() => setShowAllSizes((prev) => !prev)}
|
||||
icon={
|
||||
<ChevronRight
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
className={cn('transition-transform', showAllSizes && '-rotate-90')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{showAllSizes ? 'Show fewer sizes' : 'Show all sizes'}
|
||||
</Button>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
</RadioGroupCard>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user