-
Notifications
You must be signed in to change notification settings - Fork 380
chore(web_common): add more shared components #5243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,5 @@ tsconfig.tsbuildinfo | |
|
|
||
| *storybook.log | ||
| storybook-static | ||
| **/__snapshots__/** | ||
| **/__screenshots__/** | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| :root { | ||
| --color-badge-background: var(--color-gray-100); | ||
| --color-badge-background: var(--color-neutral-100); | ||
| --color-badge-foreground: var(--color-prose); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| :root { | ||
| --color-button-focused: var(--color-gray-100); | ||
|
|
||
| --color-button-primary-background: var(--color-gray-100); | ||
| --color-button-primary-foreground: var(--color-prose); | ||
| --color-button-primary-hover: var(--color-gray-100); | ||
| --color-button-primary-active: var(--color-gray-100); | ||
|
|
||
| --color-button-secondary-background: var(--color-gray-100); | ||
| --color-button-secondary-foreground: var(--color-prose); | ||
| --color-button-secondary-hover: var(--color-gray-100); | ||
| --color-button-secondary-active: var(--color-gray-100); | ||
|
|
||
| --color-button-alternative-background: var(--color-gray-100); | ||
| --color-button-alternative-foreground: var(--color-prose); | ||
| --color-button-alternative-hover: var(--color-gray-100); | ||
| --color-button-alternative-active: var(--color-gray-100); | ||
|
|
||
| --color-button-destructive-background: var(--color-gray-100); | ||
| --color-button-destructive-foreground: var(--color-prose); | ||
| --color-button-destructive-hover: var(--color-gray-100); | ||
| --color-button-destructive-active: var(--color-gray-100); | ||
|
|
||
| --color-button-danger-background: var(--color-gray-100); | ||
| --color-button-danger-foreground: var(--color-prose); | ||
| --color-button-danger-hover: var(--color-gray-100); | ||
| --color-button-danger-active: var(--color-gray-100); | ||
|
|
||
| --color-button-transparent-background: var(--color-gray-100); | ||
| --color-button-transparent-foreground: var(--color-prose); | ||
| --color-button-transparent-hover: var(--color-gray-100); | ||
| --color-button-transparent-active: var(--color-gray-100); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite' | ||
| import { expect, userEvent, within } from 'storybook/test' | ||
|
|
||
| import { EnumSize } from '@/types/enums' | ||
| import { Button } from './Button' | ||
| import { EnumButtonVariant } from './help' | ||
|
|
||
| const meta: Meta<typeof Button> = { | ||
| title: 'Components/Button', | ||
| component: Button, | ||
| tags: ['autodocs'], | ||
| argTypes: { | ||
| onClick: { action: 'clicked' }, | ||
| variant: { | ||
| control: { type: 'select' }, | ||
| options: Object.values(EnumButtonVariant), | ||
| }, | ||
| size: { | ||
| control: { type: 'select' }, | ||
| options: Object.values(EnumSize), | ||
| }, | ||
| type: { | ||
| control: { type: 'select' }, | ||
| options: ['button', 'submit', 'reset'], | ||
| }, | ||
| disabled: { | ||
| control: 'boolean', | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| export default meta | ||
|
|
||
| type Story = StoryObj<typeof Button> | ||
|
|
||
| export const Default: Story = { | ||
| args: { | ||
| children: 'Default Button', | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement) | ||
| await expect(canvas.getByText('Default Button')).toBeInTheDocument() | ||
| }, | ||
| } | ||
|
|
||
| export const Variants: Story = { | ||
| render: args => ( | ||
| <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}> | ||
| {Object.values(EnumButtonVariant).map(variant => ( | ||
| <Button | ||
| key={variant} | ||
| {...args} | ||
| variant={variant} | ||
| > | ||
| {variant} | ||
| </Button> | ||
| ))} | ||
| </div> | ||
| ), | ||
| } | ||
|
|
||
| export const Sizes: Story = { | ||
| render: args => ( | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| gap: 12, | ||
| flexWrap: 'wrap', | ||
| alignItems: 'center', | ||
| }} | ||
| > | ||
| {Object.values(EnumSize).map(size => ( | ||
| <Button | ||
| key={size} | ||
| {...args} | ||
| size={size} | ||
| > | ||
| {size} | ||
| </Button> | ||
| ))} | ||
| </div> | ||
| ), | ||
| } | ||
|
|
||
| export const Disabled: Story = { | ||
| args: { | ||
| children: 'Disabled Button', | ||
| disabled: true, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement) | ||
| const button = canvas.getByRole('button') | ||
| await expect(button).toBeDisabled() | ||
| await expect(button).toHaveTextContent('Disabled Button') | ||
| }, | ||
| } | ||
|
|
||
| export const AsChild: Story = { | ||
| render: args => ( | ||
| <Button | ||
| asChild | ||
| {...args} | ||
| > | ||
| <a href="#">Link as Button</a> | ||
| </Button> | ||
| ), | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement) | ||
| const linkElement = canvas.getByText('Link as Button') | ||
| await expect(linkElement.tagName).toBe('A') | ||
| await expect(linkElement).toHaveAttribute('href', '#') | ||
| }, | ||
| } | ||
|
|
||
| export const Types: Story = { | ||
| render: args => ( | ||
| <div style={{ display: 'flex', gap: 12 }}> | ||
| <Button | ||
| {...args} | ||
| type="button" | ||
| > | ||
| Button | ||
| </Button> | ||
| <Button | ||
| {...args} | ||
| type="submit" | ||
| > | ||
| Submit | ||
| </Button> | ||
| <Button | ||
| {...args} | ||
| type="reset" | ||
| > | ||
| Reset | ||
| </Button> | ||
| </div> | ||
| ), | ||
| } | ||
|
|
||
| export const InteractiveClick: Story = { | ||
| args: { | ||
| children: 'Click Me', | ||
| }, | ||
| play: async ({ canvasElement, args }) => { | ||
| const canvas = within(canvasElement) | ||
| const user = userEvent.setup() | ||
|
|
||
| const button = canvas.getByRole('button') | ||
| await expect(button).toBeInTheDocument() | ||
|
|
||
| await user.click(button) | ||
| await expect(args.onClick).toHaveBeenCalledTimes(1) | ||
|
|
||
| await user.click(button) | ||
| await expect(args.onClick).toHaveBeenCalledTimes(2) | ||
| }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Slot } from '@radix-ui/react-slot' | ||
| import type { VariantProps } from 'class-variance-authority' | ||
| import React from 'react' | ||
|
|
||
| import { cn } from '@/utils' | ||
| import { buttonVariants } from './help' | ||
| import type { ButtonType } from './help' | ||
|
|
||
| import './Button.css' | ||
|
|
||
| const DEFAULT_BUTTON_TYPE = 'button' | ||
|
|
||
| export interface ButtonProps | ||
| extends React.ButtonHTMLAttributes<HTMLButtonElement>, | ||
| VariantProps<typeof buttonVariants> { | ||
| asChild?: boolean | ||
| type?: ButtonType | ||
| } | ||
|
Comment on lines
+13
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have been looking at this Here is my thought process. When you use a component, the way you want to understand it is:
This is why in past lives I have always structured a component file like. // Imports
import type { ButtonType } from './help'
// Prop of component, named Props so you know it's the props for that component
export interface Props {}
// Component
export const Component = () => {}
// Anything else that makes up the Component
const InternalComponent = () => {}It's not a thing but figuring out how this Button component works requires quite a bit of jumping around files and while this is small, I think the complexity is pretty signficant to figure out for something so small. |
||
|
|
||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||
| ({ className, variant, size, type, asChild = false, ...props }, ref) => { | ||
| const Comp = asChild ? Slot : DEFAULT_BUTTON_TYPE | ||
| return ( | ||
| <Comp | ||
| type={type} | ||
| className={cn(buttonVariants({ variant, size, className }))} | ||
| ref={ref} | ||
| {...props} | ||
| /> | ||
| ) | ||
| }, | ||
| ) | ||
| Button.displayName = 'Button' | ||
|
|
||
| export { Button } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import { cva } from 'class-variance-authority' | ||
|
|
||
| import { EnumSize } from '@/types/enums' | ||
|
|
||
| export const EnumButtonType = { | ||
| Button: 'button', | ||
| Submit: 'submit', | ||
| Reset: 'reset', | ||
| } as const | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am going to keep slowly pushing against enums 👅 https://bluepnume.medium.com/nine-terrible-ways-to-use-typescript-enums-and-one-good-way-f9c7ec68bf15 or even subtypes. type ButtonType = "button" | "submit" | "reset" verifying switches are exhaustive and the usage with just to me is a nicer experience. |
||
|
|
||
| export type ButtonType = (typeof EnumButtonType)[keyof typeof EnumButtonType] | ||
|
|
||
| export const EnumButtonVariant = { | ||
| Primary: 'primary', | ||
| Secondary: 'secondary', | ||
| Alternative: 'alternative', | ||
| Destructive: 'destructive', | ||
| Danger: 'danger', | ||
| Transparent: 'transparent', | ||
| } as const | ||
|
|
||
| export type ButtonVariant = | ||
| (typeof EnumButtonVariant)[keyof typeof EnumButtonVariant] | ||
|
|
||
| export const buttonVariants = cva( | ||
| 'inline-flex items-center w-fit justify-center gap-1 whitespace-nowrap leading-none font-semibold ring-offset-light transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focused focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 border border-[transparent]', | ||
| { | ||
| variants: { | ||
| variant: { | ||
| [EnumButtonVariant.Primary]: | ||
| 'bg-button-primary-background text-button-primary-foreground hover:bg-button-primary-hover active:bg-button-primary-active', | ||
| [EnumButtonVariant.Secondary]: | ||
| 'bg-button-secondary-background text-button-secondary-foreground hover:bg-button-secondary-hover active:bg-button-secondary-active', | ||
| [EnumButtonVariant.Alternative]: | ||
| 'bg-button-alternative-background text-button-alternative-foreground border-neutral-200 hover:bg-button-alternative-hover active:bg-button-alternative-active', | ||
| [EnumButtonVariant.Destructive]: | ||
| 'bg-button-destructive-background text-button-destructive-foreground hover:bg-button-destructive-hover active:bg-button-destructive-active', | ||
| [EnumButtonVariant.Danger]: | ||
| 'bg-button-danger-background text-button-danger-foreground hover:bg-button-danger-hover active:bg-button-danger-active', | ||
| [EnumButtonVariant.Transparent]: | ||
| 'bg-button-transparent-background text-button-transparent-foreground hover:bg-button-transparent-hover active:bg-button-transparent-active', | ||
| }, | ||
| size: { | ||
| [EnumSize.XXS]: 'h-5 px-2 text-2xs leading-none rounded-2xs', | ||
| [EnumSize.XS]: 'h-6 px-2 text-2xs rounded-xs', | ||
| [EnumSize.S]: 'h-7 px-3 text-xs rounded-sm', | ||
| [EnumSize.M]: 'h-8 px-4 rounded-md', | ||
| [EnumSize.L]: 'h-9 px-4 rounded-lg', | ||
| [EnumSize.XL]: 'h-10 px-4 rounded-xl', | ||
| [EnumSize.XXL]: 'h-11 px-6 rounded-2xl', | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| variant: EnumButtonVariant.Primary, | ||
| size: EnumSize.S, | ||
| }, | ||
| }, | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to write these? We should be able to set it up so an enum can be inferred?