Fix Subscribe form UX (#10744)

This commit is contained in:
Sergei Garin 2024-08-05 16:57:48 +03:00 committed by GitHub
parent e94974a0a8
commit c179701a00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 111 additions and 111 deletions

View File

@ -16,8 +16,8 @@ import {
import { mergeRefs } from '#/utilities/mergeRefs'
import RadioGroup from '#/components/styled/RadioGroup'
import { tv } from '#/utilities/tailwindVariants'
import { Controller } from 'react-hook-form'
import { SELECTOR_STYLES } from '../variants'
import { SelectorOption } from './SelectorOption'
/** * Props for the Selector component. */
@ -44,8 +44,45 @@ export interface SelectorProps<
readonly readOnly?: boolean
}
export const SELECTOR_STYLES = tv({
base: 'block w-full bg-transparent transition-[border-color,outline] duration-200',
variants: {
disabled: {
true: { base: 'cursor-default opacity-50', textArea: 'cursor-default' },
false: { base: 'cursor-text', textArea: 'cursor-text' },
},
readOnly: { true: 'cursor-default' },
size: {
medium: { base: '' },
},
rounded: {
none: 'rounded-none',
small: 'rounded-sm',
medium: 'rounded-md',
large: 'rounded-lg',
xlarge: 'rounded-xl',
xxlarge: 'rounded-2xl',
xxxlarge: 'rounded-3xl',
full: 'rounded-full',
},
variant: {
outline: {
base: 'border-[0.5px] border-primary/20',
},
},
},
defaultVariants: {
size: 'medium',
rounded: 'xxxlarge',
variant: 'outline',
},
slots: {
radioGroup: 'flex',
},
})
/**
* Basic input component. Input component is a component that is used to get user input in a text field.
* A horizontal selector.
*/
// eslint-disable-next-line no-restricted-syntax
export const Selector = React.forwardRef(function Selector<

View File

@ -1,8 +1,9 @@
/** @file An option in a selector. */
import { Radio, type RadioProps } from '#/components/aria'
import { tv } from '#/utilities/tailwindVariants'
import * as React from 'react'
import type { VariantProps } from 'tailwind-variants'
import { SELECTOR_OPTION_STYLES } from '../variants'
import { TEXT_STYLE } from '../../Text'
/** Props for a {@link SelectorOption}. */
export interface SelectorOptionProps
@ -11,6 +12,39 @@ export interface SelectorOptionProps
readonly label: string
}
export const SELECTOR_OPTION_STYLES = tv({
base: TEXT_STYLE({
className:
'flex flex-1 items-center justify-center min-h-8 relative overflow-clip cursor-pointer transition-[background-color,color,outline-offset] duration-200',
variant: 'body',
}),
variants: {
rounded: {
none: 'rounded-none',
small: 'rounded-sm',
medium: 'rounded-md',
large: 'rounded-lg',
xlarge: 'rounded-xl',
xxlarge: 'rounded-2xl',
xxxlarge: 'rounded-3xl',
full: 'rounded-full',
},
size: {
medium: { base: 'px-[11px] pb-1.5 pt-2' },
small: { base: 'px-[11px] pb-0.5 pt-1' },
},
variant: {
primary:
'selected:bg-primary selected:text-white hover:bg-primary/5 pressed:bg-primary/10 outline outline-2 outline-transparent outline-offset-[-2px] focus-visible:outline-primary focus-visible:outline-offset-0',
},
},
defaultVariants: {
size: 'medium',
rounded: 'xxxlarge',
variant: 'primary',
},
})
export const SelectorOption = React.forwardRef(function SelectorOption(
props: SelectorOptionProps,
ref: React.ForwardedRef<HTMLLabelElement>,

View File

@ -76,73 +76,3 @@ export const INPUT_STYLES = tv({
variant: 'outline',
},
})
export const SELECTOR_STYLES = tv({
base: 'block w-full overflow-hidden bg-transparent transition-[border-color,outline] duration-200',
variants: {
disabled: {
true: { base: 'cursor-default opacity-50', textArea: 'cursor-default' },
false: { base: 'cursor-text', textArea: 'cursor-text' },
},
readOnly: { true: 'cursor-default' },
size: {
medium: { base: '' },
},
rounded: {
none: 'rounded-none',
small: 'rounded-sm',
medium: 'rounded-md',
large: 'rounded-lg',
xlarge: 'rounded-xl',
xxlarge: 'rounded-2xl',
xxxlarge: 'rounded-3xl',
full: 'rounded-full',
},
variant: {
outline: {
base: 'border-[0.5px] outline-offset-2 border-primary/20 focus-within:outline focus-within:outline-2 focus-within:outline-offset-[-1px] focus-within:outline-primary focus-within:border-primary/50',
textArea: 'border-transparent focus-within:border-transparent',
},
},
},
defaultVariants: {
size: 'medium',
rounded: 'xlarge',
variant: 'outline',
},
slots: {
radioGroup: 'flex',
},
})
export const SELECTOR_OPTION_STYLES = tv({
base: TEXT_STYLE({
className: 'flex flex-1 items-center min-h-6 relative overflow-auto',
variant: 'body',
}),
variants: {
rounded: {
none: 'first:rounded-l-none last:rounded-r-none',
small: 'first:rounded-l-sm last:rounded-r-sm',
medium: 'first:rounded-l-md last:rounded-r-md',
large: 'first:rounded-l-lg last:rounded-r-lg',
xlarge: 'first:rounded-l-xl last:rounded-r-xl',
xxlarge: 'first:rounded-l-2xl last:rounded-r-2xl',
xxxlarge: 'first:rounded-l-3xl last:rounded-r-3xl',
full: 'first:rounded-l-full last:rounded-r-full',
},
size: {
medium: { base: 'px-[11px] pb-1.5 pt-2' },
small: { base: 'px-[11px] pb-0.5 pt-1' },
},
variant: {
primary:
'selected:bg-primary selected:text-white not-selected:hover:bg-primary/20 border-r-[0.5px] border-primary/20',
},
},
defaultVariants: {
size: 'medium',
rounded: 'xlarge',
variant: 'primary',
},
})

View File

@ -30,7 +30,7 @@ export const TEXT_STYLE = twv.tv({
custom: '',
primary: 'text-primary',
danger: 'text-danger',
success: 'text-share',
success: 'text-accent-dark',
disabled: 'text-primary/30',
invert: 'text-white',
inherit: 'text-inherit',

View File

@ -209,7 +209,7 @@ export function Stepper(props: StepperProps) {
}}
transition={{
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
x: { type: 'spring', stiffness: 500, damping: 50, bounce: 10, duration: 0.35 },
x: { type: 'spring', stiffness: 500, damping: 50, duration: 0.2 },
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
opacity: { duration: 0.2 },
}}
@ -362,7 +362,7 @@ function Step(props: StepProps) {
}}
transition={{
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
rotate: { type: 'spring', stiffness: 500, damping: 100, bounce: 0, duration: 0.35 },
rotate: { type: 'spring', stiffness: 500, damping: 100, bounce: 0, duration: 0.2 },
}}
>
{(() => {

View File

@ -129,7 +129,16 @@ export function PlanSelectorDialog(props: PlanSelectorDialogProps) {
<div>
<Text variant="subtitle">{getText('adjustYourPlan')}</Text>
<Form form={form} className="flex flex-row">
<Form form={form} className="mt-1">
<Selector
form={form}
name="period"
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
items={[1, 12, 36]}
itemToString={(item) => billingPeriodToString(getText, item)}
label={getText('billingPeriod')}
/>
<Input
isRequired
readOnly={maxSeats === 1}
@ -142,14 +151,6 @@ export function PlanSelectorDialog(props: PlanSelectorDialogProps) {
label={getText('seats')}
description={getText(`${plan}PlanSeatsDescription`, maxSeats)}
/>
<Selector
form={form}
name="period"
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
items={[1, 12, 36]}
itemToString={(item) => billingPeriodToString(getText, item)}
label={getText('billingPeriod')}
/>
</Form>
</div>
</div>
@ -205,11 +206,7 @@ function Summary(props: SummaryProps) {
const { getText } = useText()
const { data, isLoading, isError, refetch, error } = useQuery({
...createSubscriptionPriceQuery({
plan,
seats,
period,
}),
...createSubscriptionPriceQuery({ plan, seats, period }),
enabled: !isInvalid,
})
@ -260,37 +257,38 @@ function Summary(props: SummaryProps) {
)}
</div>
{data && data.discount !== 0 && (
<div className="table-row">
<Text className="table-cell w-[0%]" variant="body" nowrap>
{getText('originalPrice')}
</Text>
<Text className="table-cell text-danger" variant="body">
{data && (
<Text className="table-cell" variant="body">
{formatter.format(data.fullPrice)}
</Text>
</div>
)}
</div>
{data && data.discount !== 0 && (
<div className="table-row">
<Text className="table-cell w-[0%]" variant="body" nowrap>
{getText('youSave')}
</Text>
<Text className="table-cell" variant="body">
{data && (
<Text
className="table-cell"
color={data.discount > 0 ? 'success' : 'primary'}
variant="body"
>
{formatter.format(data.discount)}
</Text>
</div>
)}
</div>
<div className="table-row">
<Text className="table-cell w-[0%]" variant="body" nowrap>
{getText('totalPrice')}
{getText('subtotalPrice')}
</Text>
{data && (
<Text
className={twMerge('table-cell', data.discount !== 0 && 'text-accent-dark')}
variant="body"
>
<Text className="table-cell" variant="body">
{formatter.format(data.totalPrice)}
</Text>
)}

View File

@ -535,6 +535,7 @@
"originalPrice": "Original Price",
"youSave": "You Save",
"totalPrice": "Total Price",
"subtotalPrice": "Subtotal Price",
"returnToDashboard": "Return to Dashboard",
"subscribeTitle": "Enso Pricing",
"subscribeSuccessSubmit": "Go to Dashboard",