Improve pricing table design

This commit is contained in:
gbuomprisco
2025-02-18 14:26:57 +07:00
parent 0478a6428d
commit bb4e318c54
2 changed files with 54 additions and 34 deletions

View File

@@ -26,7 +26,7 @@
"month": "Billed monthly", "month": "Billed monthly",
"year": "Billed yearly" "year": "Billed yearly"
}, },
"perMonth": "per month", "perMonth": "month",
"custom": "Custom Plan", "custom": "Custom Plan",
"lifetime": "Lifetime", "lifetime": "Lifetime",
"trialPeriod": "{{period}} day trial", "trialPeriod": "{{period}} day trial",

View File

@@ -17,7 +17,6 @@ import {
import { Badge } from '@kit/ui/badge'; import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if'; import { If } from '@kit/ui/if';
import { Separator } from '@kit/ui/separator';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils'; import { cn } from '@kit/ui/utils';
@@ -172,7 +171,7 @@ function PricingItem(
data-cy={'subscription-plan'} data-cy={'subscription-plan'}
className={cn( className={cn(
props.className, props.className,
`s-full relative flex flex-1 grow flex-col items-stretch justify-between self-stretch rounded-lg border p-8 lg:w-4/12 xl:max-w-[20rem]`, `s-full relative flex flex-1 grow flex-col items-stretch justify-between self-stretch rounded-lg border px-6 py-5 lg:w-4/12 xl:max-w-[20rem]`,
{ {
['border-primary']: highlighted, ['border-primary']: highlighted,
['border-border']: !highlighted, ['border-border']: !highlighted,
@@ -195,12 +194,12 @@ function PricingItem(
</div> </div>
</If> </If>
<div className={'flex flex-col space-y-6'}> <div className={'flex flex-col gap-y-5'}>
<div className={'flex flex-col space-y-2.5'}> <div className={'flex flex-col gap-y-1'}>
<div className={'flex items-center space-x-6'}> <div className={'flex items-center space-x-6'}>
<b <b
className={ className={
'text-current-foreground font-heading font-semibold uppercase tracking-tight' 'text-secondary-foreground font-heading text-xl font-medium tracking-tight'
} }
> >
<Trans <Trans
@@ -209,20 +208,19 @@ function PricingItem(
/> />
</b> </b>
</div> </div>
<span className={cn(`text-muted-foreground h-6 text-sm`)}>
<Trans
i18nKey={props.product.description}
defaults={props.product.description}
/>
</span>
</div> </div>
<Separator /> <div className={'mt-6 flex flex-col gap-y-1'}>
<Price
<div className={'flex flex-col space-y-2'}> isMonthlyPrice={props.alwaysDisplayMonthlyPrice}
<Price isMonthlyPrice={props.alwaysDisplayMonthlyPrice}> displayBillingPeriod={!props.plan.label}
<If condition={!isCustom} fallback={props.plan.label}> >
<If
condition={!isCustom}
fallback={
<Trans i18nKey={props.plan.label} defaults={props.plan.label} />
}
>
<PlanCostDisplay <PlanCostDisplay
primaryLineItem={lineItem} primaryLineItem={lineItem}
currencyCode={props.product.currency} currencyCode={props.product.currency}
@@ -235,7 +233,7 @@ function PricingItem(
<If condition={props.plan.name}> <If condition={props.plan.name}>
<span <span
className={cn( className={cn(
`animate-in slide-in-from-left-4 fade-in text-muted-foreground flex items-center gap-x-1 text-sm capitalize`, `animate-in slide-in-from-left-4 fade-in text-muted-foreground flex items-center gap-x-1 text-xs capitalize`,
)} )}
> >
<span> <span>
@@ -298,7 +296,14 @@ function PricingItem(
</If> </If>
</If> </If>
<Separator /> <span className={cn(`text-muted-foreground text-base tracking-tight`)}>
<Trans
i18nKey={props.product.description}
defaults={props.product.description}
/>
</span>
<div className={'h-px w-full border border-dashed'} />
<div className={'flex flex-col'}> <div className={'flex flex-col'}>
<FeaturesList <FeaturesList
@@ -308,7 +313,7 @@ function PricingItem(
</div> </div>
<If condition={props.displayPlanDetails && lineItemsToDisplay.length}> <If condition={props.displayPlanDetails && lineItemsToDisplay.length}>
<Separator /> <div className={'h-px w-full border border-dashed'} />
<div className={'flex flex-col space-y-2'}> <div className={'flex flex-col space-y-2'}>
<h6 className={'text-sm font-semibold'}> <h6 className={'text-sm font-semibold'}>
@@ -330,14 +335,14 @@ function PricingItem(
function FeaturesList( function FeaturesList(
props: React.PropsWithChildren<{ props: React.PropsWithChildren<{
features: string[]; features: string[];
highlighted?: boolean; highlighted: boolean;
}>, }>,
) { ) {
return ( return (
<ul className={'flex flex-col space-y-2'}> <ul className={'flex flex-col gap-1'}>
{props.features.map((feature) => { {props.features.map((feature) => {
return ( return (
<ListItem key={feature}> <ListItem highlighted={props.highlighted} key={feature}>
<Trans i18nKey={feature} defaults={feature} /> <Trans i18nKey={feature} defaults={feature} />
</ListItem> </ListItem>
); );
@@ -349,23 +354,27 @@ function FeaturesList(
function Price({ function Price({
children, children,
isMonthlyPrice = true, isMonthlyPrice = true,
displayBillingPeriod = true,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
isMonthlyPrice?: boolean; isMonthlyPrice?: boolean;
displayBillingPeriod?: boolean;
}>) { }>) {
return ( return (
<div <div
className={`animate-in slide-in-from-left-4 fade-in flex items-end gap-2 duration-500`} className={`animate-in slide-in-from-left-4 fade-in flex items-end gap-1 duration-500`}
> >
<span <span
className={ className={
'font-heading flex items-center text-3xl font-semibold tracking-tighter' 'font-heading flex items-center text-4xl font-medium tracking-tighter'
} }
> >
{children} {children}
</span> </span>
<If condition={isMonthlyPrice}> <If condition={isMonthlyPrice && displayBillingPeriod}>
<span className={'text-muted-foreground text-sm leading-loose'}> <span className={'text-muted-foreground text-sm leading-loose'}>
<span>/</span>
<Trans i18nKey={'billing:perMonth'} /> <Trans i18nKey={'billing:perMonth'} />
</span> </span>
</If> </If>
@@ -373,14 +382,25 @@ function Price({
); );
} }
function ListItem({ children }: React.PropsWithChildren) { function ListItem({
children,
highlighted,
}: React.PropsWithChildren<{
highlighted: boolean;
}>) {
return ( return (
<li className={'flex items-center space-x-2.5'}> <li className={'flex items-center gap-x-2.5'}>
<CheckCircle className={'text-primary h-4 min-h-4 w-4 min-w-4'} /> <CheckCircle
className={cn('h-4 min-h-4 w-4 min-w-4', {
'text-secondary-foreground': highlighted,
'text-muted-foreground': !highlighted,
})}
/>
<span <span
className={cn('text-sm', { className={cn('text-sm', {
['text-secondary-foreground']: true, 'text-muted-foreground': !highlighted,
'text-secondary-foreground': highlighted,
})} })}
> >
{children} {children}
@@ -477,10 +497,10 @@ function DefaultCheckoutButton(
<Link className={'w-full'} href={linkHref}> <Link className={'w-full'} href={linkHref}>
<Button <Button
size={'lg'} size={'lg'}
className={'w-full rounded-lg border'} className={'h-12 w-full rounded-lg'}
variant={props.highlighted ? 'default' : 'outline'} variant={props.highlighted ? 'default' : 'secondary'}
> >
<span> <span className={'text-base font-medium tracking-tight'}>
<Trans <Trans
i18nKey={label} i18nKey={label}
defaults={label} defaults={label}