From 33d2e0be2cf4dfe3bfbb939f35e44729482338ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=98=88=EC=A7=84?= Date: Sun, 10 Nov 2024 13:14:35 +0900 Subject: [PATCH 1/2] refactor: use cva --- package-lock.json | 21 +++++++ package.json | 1 + src/shared/ui/Button/Button.module.css | 86 ++++++++++++++------------ src/shared/ui/Button/Button.tsx | 30 ++++++--- 4 files changed, 93 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index b487e17..d32e32a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@remix-run/serve": "^2.9.2", "@tanstack/react-query": "^5.51.23", "axios": "^1.7.2", + "cva": "npm:class-variance-authority@^0.7.0", "dayjs": "^1.11.11", "i18next": "^23.13.0", "i18next-browser-languagedetector": "^8.0.0", @@ -12435,6 +12436,26 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/cva": { + "name": "class-variance-authority", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/cva/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", diff --git a/package.json b/package.json index 941b6e3..8745fca 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@remix-run/serve": "^2.9.2", "@tanstack/react-query": "^5.51.23", "axios": "^1.7.2", + "cva": "npm:class-variance-authority@^0.7.0", "dayjs": "^1.11.11", "i18next": "^23.13.0", "i18next-browser-languagedetector": "^8.0.0", diff --git a/src/shared/ui/Button/Button.module.css b/src/shared/ui/Button/Button.module.css index 279ad96..d66da92 100644 --- a/src/shared/ui/Button/Button.module.css +++ b/src/shared/ui/Button/Button.module.css @@ -8,74 +8,84 @@ align-items: center; } -.Button:not(:disabled)[data-variant='filled'] { - &[data-color='primary'] { - background-color: var(--color-neutral-900); - color: var(--color-primary); - } - &[data-color='neutral'] { - background-color: var(--color-neutral-200); - color: #fff; - } -} -.Button:not(:disabled)[data-variant='outline'] { - &[data-color='primary'] { - border: 1px solid var(--color-primary); - color: var(--color-primary); - } - &[data-color='neutral'] { - border: 1px solid var(--color-neutral-300); - color: var(--color-neutral-900); - } -} -.Button:not(:disabled)[data-variant='ghost'] { +.Variant_Filled_Primary { + background-color: var(--color-neutral-900); + color: var(--color-primary); +} + +.Variant_Filled_Neutral { + background-color: var(--color-neutral-200); + color: #fff; +} + +.Variant_Outline_Primary { + border: 1px solid var(--color-primary); + color: var(--color-primary); +} + +.Variant_Outline_Neutral { + border: 1px solid var(--color-neutral-300); color: var(--color-neutral-900); - &[data-color='primary'] { - color: var(--color-primary); - } - &[data-color='neutral'] { - color: var(--color-neutral-900); - } } + +.Variant_Ghost { + +} + +.Variant_Ghost_Primary { + color: var(--color-primary); +} + +.Variant_Ghost_Neutral { + color: var(--color-neutral-900); +} + .Button:disabled { color: var(--color-neutral-300); background-color: var(--color-neutral-200); - - &[data-variant='ghost'] { - background-color: transparent; - } } +.Disabled_Ghost { + background-color: transparent !important; +} -.Button[data-size='M'] { +.Size_M { height: 48px; border-radius: 48px; padding: 8px 16px; font-size: 16px; font-weight: 600; } -.Button[data-size='S'] { + +.Size_S { height: 32px; border-radius: 40px; padding: 8px 12px; } -.Button[data-size='fit'] { + +.Size_Fit { padding-left: 0; padding-right: 0; } -.Button[data-width-type='fill'] { +.WidthType_Fill { width: 100%; } -.Button[data-width-type='hug'] { + +.WidthType_Hug { width: fit-content; } -.Button[data-text-align='left'] { +.TextAlign_Left { text-align: left; } -.Button[data-text-align='right'] { + +.TextAlign_Center { + text-align: center; +} + +.TextAlign_Right { text-align: right; } diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx index 65c4ba4..f154f6f 100644 --- a/src/shared/ui/Button/Button.tsx +++ b/src/shared/ui/Button/Button.tsx @@ -1,8 +1,9 @@ import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from 'react'; import styles from './Button.module.css'; +import { cva } from 'cva'; type ButtonProps = DetailedHTMLProps, HTMLButtonElement> & { - variant: 'filled' | 'outline' | 'ghost' | 'link'; + variant: 'filled' | 'outline' | 'ghost'; color: 'primary' | 'neutral'; widthType: 'fill' | 'hug'; suffixSlot?: ReactNode; @@ -11,6 +12,26 @@ type ButtonProps = DetailedHTMLProps, HT size?: 'fit' | 'S' | 'M'; }; +const buttonStyle = cva(styles.Button, { + variants: { + disabled: { enabled: '', disabled: '' }, + widthType: { fill: styles.WidthType_Fill, hug: styles.WidthType_Hug }, + textAlign: { left: styles.TextAlign_Left, center: styles.TextAlign_Center, right: styles.TextAlign_Right }, + size: { M: styles.Size_M, S: styles.Size_S, fit: styles.Size_Fit }, + variant: { filled: '', outline: '', ghost: '' }, + color: { primary: '', neutral: '' }, + }, + compoundVariants: [ + { variant: 'filled', color: 'primary', className: styles.Variant_Filled_Primary }, + { variant: 'filled', color: 'neutral', className: styles.Variant_Filled_Neutral }, + { variant: 'outline', color: 'primary', className: styles.Variant_Outline_Primary }, + { variant: 'outline', color: 'neutral', className: styles.Variant_Outline_Neutral }, + { variant: 'ghost', color: 'primary', className: styles.Variant_Ghost_Primary }, + { variant: 'ghost', color: 'neutral', className: styles.Variant_Ghost_Neutral }, + { variant: 'ghost', disabled: 'disabled', className: styles.Disabled_Ghost }, + ], +}); + export const Button = ({ className = '', variant, @@ -24,13 +45,8 @@ export const Button = ({ ...props }: ButtonProps) => ( + ))} + +
+

color

+ {colorList.map((v) => ( + + ))} +
+
+

widthType

+ {widthType.map((v) => ( + + ))} +
+
+

size

+ {size.map((v) => ( + + ))} +
+ + ); + }, +}; + export const PrimaryButton: Story = { args: { variant: 'filled', diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx index f154f6f..ce5bfad 100644 --- a/src/shared/ui/Button/Button.tsx +++ b/src/shared/ui/Button/Button.tsx @@ -1,20 +1,9 @@ import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from 'react'; import styles from './Button.module.css'; -import { cva } from 'cva'; - -type ButtonProps = DetailedHTMLProps, HTMLButtonElement> & { - variant: 'filled' | 'outline' | 'ghost'; - color: 'primary' | 'neutral'; - widthType: 'fill' | 'hug'; - suffixSlot?: ReactNode; - prefixSlot?: ReactNode; - textAlign?: 'left' | 'center' | 'right'; - size?: 'fit' | 'S' | 'M'; -}; +import { cva, VariantProps } from 'cva'; const buttonStyle = cva(styles.Button, { variants: { - disabled: { enabled: '', disabled: '' }, widthType: { fill: styles.WidthType_Fill, hug: styles.WidthType_Hug }, textAlign: { left: styles.TextAlign_Left, center: styles.TextAlign_Center, right: styles.TextAlign_Right }, size: { M: styles.Size_M, S: styles.Size_S, fit: styles.Size_Fit }, @@ -28,26 +17,33 @@ const buttonStyle = cva(styles.Button, { { variant: 'outline', color: 'neutral', className: styles.Variant_Outline_Neutral }, { variant: 'ghost', color: 'primary', className: styles.Variant_Ghost_Primary }, { variant: 'ghost', color: 'neutral', className: styles.Variant_Ghost_Neutral }, - { variant: 'ghost', disabled: 'disabled', className: styles.Disabled_Ghost }, ], + defaultVariants: { + variant: 'filled', + color: 'primary', + size: 'M', + textAlign: 'center', + }, }); +type ButtonProps = DetailedHTMLProps, HTMLButtonElement> & { + suffixSlot?: ReactNode; + prefixSlot?: ReactNode; +} & VariantProps; + export const Button = ({ className = '', variant, color, - size = 'M', - widthType = 'fill', - textAlign = 'center', + size, + widthType, + textAlign, suffixSlot, prefixSlot, children, ...props }: ButtonProps) => ( -