Skip to content

Commit

Permalink
Create product card Discount components
Browse files Browse the repository at this point in the history
  • Loading branch information
Youakeem committed Sep 19, 2024
1 parent 3df35ed commit ab8a5b9
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { style } from '@vanilla-extract/css'

export const formInput = style({
width: '100%',
})

export const formSubmitButton = style({
minWidth: 'auto',
})
124 changes: 124 additions & 0 deletions apps/store/src/components/ProductCard/Discount/Discount.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { action } from '@storybook/addon-actions'
import type { Meta, StoryObj } from '@storybook/react'
import { type FormEvent } from 'react'
import { Discount } from './Discount'

type Controls = {
onOpenChange?: (isOpen: boolean) => void
onSubmit?: (event: FormEvent<HTMLFormElement>) => void
}

const meta: Meta<typeof Discount.Root> = {
title: 'Components / ProductCard / Discount',
component: Discount.Root,
}
export default meta

type Story = StoryObj<Controls>

export const Inactive: Story = {
render: (args: Controls) => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
args.onSubmit?.(event)
}

return (
<div style={{ maxWidth: '400px' }}>
<Discount.Root onOpenChange={args.onOpenChange}>
<Discount.Form onSubmit={handleSubmit} />
</Discount.Root>
</div>
)
},
args: {
onOpenChange: action('onOpenChange'),
onSubmit: action('onSubmit'),
},
}

export const Active: Story = {
render: (args: Controls) => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
args.onSubmit?.(event)
}

return (
<div style={{ maxWidth: '400px' }}>
<Discount.Root onOpenChange={args.onOpenChange} defaultOpen>
<Discount.Form onSubmit={handleSubmit} />
</Discount.Root>
</div>
)
},
args: {
onOpenChange: action('onOpenChange'),
onSubmit: action('onSubmit'),
},
}

export const Loading: Story = {
render: (args: Controls) => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
args.onSubmit?.(event)
}

return (
<div style={{ maxWidth: '400px' }}>
<Discount.Root onOpenChange={args.onOpenChange} defaultOpen>
<Discount.Form onSubmit={handleSubmit} loading />
</Discount.Root>
</div>
)
},
args: {
onOpenChange: action('onOpenChange'),
onSubmit: action('onSubmit'),
},
}

export const Error: Story = {
render: (args: Controls) => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
args.onSubmit?.(event)
}

return (
<div style={{ maxWidth: '400px' }}>
<Discount.Root onOpenChange={args.onOpenChange} defaultOpen>
<Discount.Form onSubmit={handleSubmit} errorMessage="Invalid code" />
</Discount.Root>
</div>
)
},
args: {
onOpenChange: action('onOpenChange'),
onSubmit: action('onSubmit'),
},
}

export const Applied: Story = {
render: (args: Controls) => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
args.onSubmit?.(event)
}

return (
<div style={{ maxWidth: '400px' }}>
<Discount.Root onOpenChange={args.onOpenChange} defaultOpen>
<Discount.Code onSubmit={handleSubmit} code="SUMMER">
3 months for free
</Discount.Code>
</Discount.Root>
</div>
)
},
args: {
onOpenChange: action('onOpenChange'),
onSubmit: action('onSubmit'),
},
}
114 changes: 114 additions & 0 deletions apps/store/src/components/ProductCard/Discount/Discount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import clsx from 'clsx'
import { useTranslation } from 'next-i18next'
import { type ComponentProps, useState } from 'react'
import {
xStack,
Text,
Switch,
sprinkles,
Button,
Badge,
IconButton,
CrossIconSmall,
tokens,
} from 'ui'
import Collapsible from '@/components/Collapsible/Collapsible'
import { TextField } from '@/components/TextField/TextField'
import { formInput, formSubmitButton } from './Discount.css'

const FORM_CAMPAIGN_CODE = 'campaignCode'

type RootProps = Omit<ComponentProps<typeof Collapsible.Root>, 'open'>
const DiscountRoot = ({ children, defaultOpen, ...props }: RootProps) => {
const { t } = useTranslation('cart')

const [isOpen, setIsOpen] = useState(defaultOpen || false)

return (
<Collapsible.Root open={isOpen} {...props}>
<div className={xStack({ justifyContent: 'space-between', alignItems: 'center' })}>
<Text>{t('CAMPAIGN_CODE_HEADING')}</Text>
<Collapsible.Trigger asChild>
<Switch checked={isOpen} onCheckedChange={setIsOpen} />
</Collapsible.Trigger>
</div>

<Collapsible.Content>
<div className={sprinkles({ paddingBlock: 'sm' })}>{children}</div>
</Collapsible.Content>
</Collapsible.Root>
)
}

type FormProps = ComponentProps<'form'> & {
errorMessage?: string
loading?: boolean
}
const DiscountForm = ({ className, errorMessage, loading, ...props }: FormProps) => {
const { t } = useTranslation('cart')

return (
<form
className={clsx(
xStack({ justifyContent: 'space-between', alignItems: 'flex-start', gap: 'sm' }),
className,
)}
{...props}
>
<TextField
className={formInput}
name={FORM_CAMPAIGN_CODE}
label={t('CAMPAIGN_CODE_INPUT_LABEL')}
size="small"
warning={!!errorMessage}
message={errorMessage}
required={true}
upperCaseInput={true}
readOnly={loading}
disabled={loading}
/>

<Button
className={formSubmitButton}
type="submit"
variant="primary-alt"
loading={loading}
disabled={loading}
>
{t('CHECKOUT_ADD_DISCOUNT_BUTTON')}
</Button>
</form>
)
}

type CodeProps = ComponentProps<'form'> & {
code: string
}
const DiscountCode = ({ children, className, code, ...props }: CodeProps) => {
return (
<form
className={clsx(xStack({ justifyContent: 'space-between', alignItems: 'center' }), className)}
{...props}
>
<Badge
className={xStack({ alignItems: 'center', paddingRight: 'none', gap: 'sm' })}
color="gray200"
>
<Text as="span" size="xs">
{code}
</Text>
<input hidden value={code} readOnly />
<IconButton variant="ghost" style={{ padding: 0 }} data-no-hover>
<CrossIconSmall color={tokens.colors.textTertiary} />
</IconButton>
</Badge>
<Text>{children}</Text>
</form>
)
}

export const Discount = {
Root: DiscountRoot,
Form: DiscountForm,
Code: DiscountCode,
}
2 changes: 1 addition & 1 deletion packages/ui/src/components/Badge/Badge.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const bigVariant = {
}
export const badge = recipe({
base: {
display: 'inline-block',
display: 'inline-flex',
color: badgeFontColor,
backgroundColor: badgeBgColor,
borderRadius: tokens.radius.xxs,
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type BadgeColors = Pick<
| 'blueFill2'
| 'blueFill3'
| 'green50'
| 'gray200'
| 'signalAmberHighlight'
| 'signalGreenFill'
| 'pinkFill1'
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Button/Button.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ export const buttonVariant = styleVariants({
color: tokens.colors.textPrimary,

'@media (hover: hover)': {
'&:hover': {
'&:hover:not([data-no-hover])': {
backgroundColor: tokens.colors.buttonGhostHover,
},
},

':active': {
'&:active:not([data-no-hover])': {
backgroundColor: tokens.colors.buttonGhostHover,
},

Expand Down

0 comments on commit ab8a5b9

Please sign in to comment.