mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): alignment of compute pricing blocks on compute settings (#48289)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Fixes alignment issues across viewports with the size blocks on compute & disk page. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added responsive container support for horizontal form layouts. * Compute-size options now adapt more smoothly to available screen space. * Preserved existing loading states, error messages, pricing details, locked options, and larger-compute guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
8d4d3b57e0
commit
84e273cf0b
2 files changed
+206
-185
No files matched your search
@@ -139,6 +139,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
|
||||
>
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
isContainerResponsive
|
||||
label="Compute size"
|
||||
id={field.name}
|
||||
className="gap-5"
|
||||
@@ -178,201 +179,197 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
!addonsError
|
||||
? 'grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(100%,13em),1fr))]'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
Array(INITIALLY_VISIBLE_COUNT)
|
||||
.fill(0)
|
||||
.map((_, i) => <Skeleton key={i} className="w-full h-[110px] rounded-md" />)
|
||||
) : addonsError ? (
|
||||
<FormMessage message={'Failed to load Compute size options'} type="error">
|
||||
<p>{addonsError?.message}</p>
|
||||
</FormMessage>
|
||||
) : (
|
||||
<>
|
||||
{visibleOptions.map((compute) => {
|
||||
const lockedMicroDueToPITR =
|
||||
compute.identifier === 'ci_micro' && !!subscriptionPitr
|
||||
const lockedNanoDueToPlan =
|
||||
org?.plan.id !== 'free' &&
|
||||
project?.infra_compute_size !== 'nano' &&
|
||||
compute.identifier === 'ci_nano'
|
||||
<div className="@container">
|
||||
<div className={!addonsError ? 'grid gap-4 grid-cols-2 @[680px]:grid-cols-3' : ''}>
|
||||
{isLoading ? (
|
||||
Array(INITIALLY_VISIBLE_COUNT)
|
||||
.fill(0)
|
||||
.map((_, i) => <Skeleton key={i} className="w-full h-[110px] rounded-md" />)
|
||||
) : addonsError ? (
|
||||
<FormMessage message={'Failed to load Compute size options'} type="error">
|
||||
<p>{addonsError?.message}</p>
|
||||
</FormMessage>
|
||||
) : (
|
||||
<>
|
||||
{visibleOptions.map((compute) => {
|
||||
const lockedMicroDueToPITR =
|
||||
compute.identifier === 'ci_micro' && !!subscriptionPitr
|
||||
const lockedNanoDueToPlan =
|
||||
org?.plan.id !== 'free' &&
|
||||
project?.infra_compute_size !== 'nano' &&
|
||||
compute.identifier === 'ci_nano'
|
||||
|
||||
const lockedOption = lockedNanoDueToPlan || lockedMicroDueToPITR
|
||||
const lockedOption = lockedNanoDueToPlan || lockedMicroDueToPITR
|
||||
|
||||
const price =
|
||||
org?.plan.id !== 'free' &&
|
||||
project?.infra_compute_size === 'nano' &&
|
||||
compute.identifier === 'ci_nano'
|
||||
? availableOptions.find(
|
||||
(option: ComputeAddonVariant) => option.identifier === 'ci_micro'
|
||||
)?.price
|
||||
: compute.price
|
||||
const price =
|
||||
org?.plan.id !== 'free' &&
|
||||
project?.infra_compute_size === 'nano' &&
|
||||
compute.identifier === 'ci_nano'
|
||||
? availableOptions.find(
|
||||
(option: ComputeAddonVariant) => option.identifier === 'ci_micro'
|
||||
)?.price
|
||||
: compute.price
|
||||
|
||||
const cpuLabel = (() => {
|
||||
const cpuCores = compute.meta?.cpu_cores
|
||||
if (typeof cpuCores === 'number') {
|
||||
return `${cpuCores}-core CPU`
|
||||
}
|
||||
if (cpuCores) {
|
||||
return `${cpuCores} CPU`
|
||||
}
|
||||
return 'CPU'
|
||||
})()
|
||||
const cpuLabel = (() => {
|
||||
const cpuCores = compute.meta?.cpu_cores
|
||||
if (typeof cpuCores === 'number') {
|
||||
return `${cpuCores}-core CPU`
|
||||
}
|
||||
if (cpuCores) {
|
||||
return `${cpuCores} CPU`
|
||||
}
|
||||
return 'CPU'
|
||||
})()
|
||||
|
||||
return (
|
||||
<RadioGroupCardItem
|
||||
showIndicator={false}
|
||||
id={compute.identifier}
|
||||
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]',
|
||||
lockedOption && 'opacity-50'
|
||||
)}
|
||||
disabled={disabled || lockedOption}
|
||||
label={
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
{showUpgradeBadge && compute.identifier === 'ci_micro' && (
|
||||
<div className="absolute -top-4 -right-3 text-violet-1100 flex items-center gap-1 bg-surface-75 py-0.5 px-2 rounded-full border border-violet-900">
|
||||
<span>No additional charge</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full flex flex-col gap-3 justify-between">
|
||||
<div className="relative px-3 opacity-50 group-data-checked:opacity-100 flex justify-between">
|
||||
<ComputeBadge
|
||||
className="inline-flex font-semibold"
|
||||
infraComputeSize={compute.name as InfraInstanceSize}
|
||||
/>
|
||||
<div className="flex items-center space-x-1">
|
||||
{lockedOption ? (
|
||||
<div className="bg border rounded-lg h-7 w-7 flex items-center justify-center">
|
||||
<Lock size={14} />
|
||||
</div>
|
||||
) : (
|
||||
showComputePrice && (
|
||||
<>
|
||||
<span
|
||||
className="text-foreground text-sm font-semibold"
|
||||
translate="no"
|
||||
>
|
||||
${price}
|
||||
</span>
|
||||
<span className="text-foreground-light translate-y-px">
|
||||
{' '}
|
||||
/{' '}
|
||||
{compute.price_interval === 'monthly'
|
||||
? 'month'
|
||||
: 'hour'}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
return (
|
||||
<RadioGroupCardItem
|
||||
showIndicator={false}
|
||||
id={compute.identifier}
|
||||
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]',
|
||||
lockedOption && 'opacity-50'
|
||||
)}
|
||||
disabled={disabled || lockedOption}
|
||||
label={
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
{showUpgradeBadge && compute.identifier === 'ci_micro' && (
|
||||
<div className="absolute -top-4 -right-3 text-violet-1100 flex items-center gap-1 bg-surface-75 py-0.5 px-2 rounded-full border border-violet-900">
|
||||
<span>No additional charge</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>
|
||||
{compute.identifier === 'ci_nano' && 'Up to '}
|
||||
{compute.meta?.memory_gb ?? 0} GB memory
|
||||
</span>
|
||||
)}
|
||||
<div className="w-full flex flex-col gap-3 justify-between">
|
||||
<div className="relative px-3 opacity-50 group-data-checked:opacity-100 flex justify-between">
|
||||
<ComputeBadge
|
||||
className="inline-flex font-semibold"
|
||||
infraComputeSize={compute.name as InfraInstanceSize}
|
||||
/>
|
||||
<div className="flex items-center space-x-1">
|
||||
{lockedOption ? (
|
||||
<div className="bg border rounded-lg h-7 w-7 flex items-center justify-center">
|
||||
<Lock size={14} />
|
||||
</div>
|
||||
) : (
|
||||
showComputePrice && (
|
||||
<>
|
||||
<span
|
||||
className="text-foreground text-sm font-semibold"
|
||||
translate="no"
|
||||
>
|
||||
${price}
|
||||
</span>
|
||||
<span className="text-foreground-light translate-y-px">
|
||||
{' '}
|
||||
/{' '}
|
||||
{compute.price_interval === 'monthly'
|
||||
? 'month'
|
||||
: 'hour'}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<CpuIcon
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>{cpuLabel}</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>
|
||||
{compute.identifier === 'ci_nano' && 'Up to '}
|
||||
{compute.meta?.memory_gb ?? 0} GB memory
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-foreground-light flex gap-2 items-center">
|
||||
<CpuIcon
|
||||
strokeWidth={1}
|
||||
size={14}
|
||||
className="text-foreground-lighter"
|
||||
/>
|
||||
<span>{cpuLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
{lockedMicroDueToPITR && (
|
||||
<TooltipContent side="bottom" className="w-64 text-center">
|
||||
Project has PITR enabled which requires a minimum of Small
|
||||
compute. Please{' '}
|
||||
<InlineLink href="/project/_/settings/addons?panel=pitr">
|
||||
disable PITR
|
||||
</InlineLink>{' '}
|
||||
first before selecting Micro
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{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>
|
||||
</TooltipTrigger>
|
||||
{lockedMicroDueToPITR && (
|
||||
<TooltipContent side="bottom" className="w-64 text-center">
|
||||
Project has PITR enabled which requires a minimum of Small compute.
|
||||
Please{' '}
|
||||
<InlineLink href="/project/_/settings/addons?panel=pitr">
|
||||
disable PITR
|
||||
</InlineLink>{' '}
|
||||
first before selecting Micro
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
<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>
|
||||
</SupportLink>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{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>
|
||||
<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>
|
||||
</SupportLink>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isLoading && !addonsError && hasHiddenOptions && (
|
||||
|
||||
@@ -18,6 +18,14 @@ type Props = {
|
||||
isReactForm?: boolean
|
||||
hideMessage?: boolean
|
||||
name?: string
|
||||
/**
|
||||
* For layout="horizontal", switches the label/content split to react to the nearest
|
||||
* `@container` ancestor's width instead of the viewport width. Use when the layout sits
|
||||
* inside a narrower column (e.g. next to a sidebar) so it stacks to a single column as
|
||||
* soon as that column gets tight, rather than waiting for a viewport breakpoint that may
|
||||
* never reflect the column's actual available space.
|
||||
*/
|
||||
isContainerResponsive?: boolean
|
||||
}
|
||||
|
||||
const ContainerVariants = cva('relative grid gap-10', {
|
||||
@@ -35,7 +43,7 @@ const ContainerVariants = cva('relative grid gap-10', {
|
||||
false: '',
|
||||
},
|
||||
layout: {
|
||||
horizontal: 'flex flex-col gap-2 md:grid md:grid-cols-12',
|
||||
horizontal: 'flex flex-col gap-2',
|
||||
vertical: 'flex flex-col gap-2',
|
||||
flex: 'flex flex-row gap-3',
|
||||
'flex-row-reverse':
|
||||
@@ -57,8 +65,20 @@ const ContainerVariants = cva('relative grid gap-10', {
|
||||
align: 'right',
|
||||
className: 'justify-between',
|
||||
},
|
||||
{
|
||||
layout: 'horizontal',
|
||||
responsive: false,
|
||||
className: 'md:grid md:grid-cols-12',
|
||||
},
|
||||
{
|
||||
layout: 'horizontal',
|
||||
responsive: true,
|
||||
className: '@xl:grid @xl:grid-cols-12',
|
||||
},
|
||||
],
|
||||
defaultVariants: {},
|
||||
defaultVariants: {
|
||||
responsive: false,
|
||||
},
|
||||
})
|
||||
|
||||
const LabelContainerVariants = cva('transition-all duration-500 ease-in-out', {
|
||||
@@ -287,6 +307,7 @@ export const FormLayout = React.forwardRef<
|
||||
hideMessage = false,
|
||||
isReactForm,
|
||||
error,
|
||||
isContainerResponsive = false,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -356,7 +377,10 @@ export const FormLayout = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={cn(ContainerVariants({ size, flex, align, layout }), className)}
|
||||
className={cn(
|
||||
ContainerVariants({ size, flex, align, layout, responsive: isContainerResponsive }),
|
||||
className
|
||||
)}
|
||||
>
|
||||
{flex && (
|
||||
<div className={cn(FlexContainer({ flex, align, layout }))}>
|
||||
|
||||
Reference in new issue
Block a user