diff --git a/.changeset/happy-seas-cross.md b/.changeset/happy-seas-cross.md new file mode 100644 index 0000000..b4bffaa --- /dev/null +++ b/.changeset/happy-seas-cross.md @@ -0,0 +1,5 @@ +--- +"@sipe-team/button": patch +--- + +fix(button): handle dynamic styles within typescript diff --git a/packages/button/src/env.d.ts b/packages/button/global.d.ts similarity index 100% rename from packages/button/src/env.d.ts rename to packages/button/global.d.ts diff --git a/packages/button/package.json b/packages/button/package.json index ef4083e..201223b 100644 --- a/packages/button/package.json +++ b/packages/button/package.json @@ -5,7 +5,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/sipe-team/3-1_sds" + "url": "https://github.com/sipe-team/3-2_side" }, "type": "module", "exports": "./src/index.ts", @@ -14,13 +14,19 @@ "build": "tsup", "build:storybook": "storybook build", "dev:storybook": "storybook dev -p 6006", - "lint": "biome lint .", + "lint:biome": "pnpm exec biome lint", + "lint:eslint": "pnpm exec eslint --flag unstable_ts_config", "test": "vitest", "typecheck": "tsc", "prepack": "pnpm run build" }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "@sipe-team/typography": "workspace:*", + "clsx": "^2.1.1", + "ts-pattern": "^5.6.0" + }, "devDependencies": { - "@biomejs/biome": "catalog:", "@storybook/addon-essentials": "catalog:", "@storybook/addon-interactions": "catalog:", "@storybook/addon-links": "catalog:", @@ -55,13 +61,9 @@ "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } - } + }, + "./styles.css": "./dist/index.css" } }, - "sideEffects": false, - "dependencies": { - "@sipe-team/typography": "workspace:*", - "@radix-ui/react-slot": "^1.1.0", - "clsx": "^2.1.1" - } + "sideEffects": false } diff --git a/packages/button/src/Button.module.css b/packages/button/src/Button.module.css index 9535d0c..8478924 100644 --- a/packages/button/src/Button.module.css +++ b/packages/button/src/Button.module.css @@ -10,62 +10,15 @@ font-weight: bold; cursor: pointer; transition: all 0.2s ease-in-out; + background-color: var(--background-color); + border: var(--border); + color: var(--color); } -/* Colors - CSS 변수 정의 */ -.primary { - --button-bg: var(--primary-color); - --button-color: var(--black); - --button-hover-bg: #00d2d2; - --button-hover-color: var(--black); -} - -.black { - --button-bg: var(--black); - --button-color: var(--white); - --button-hover-bg: #2d3748; - --button-hover-color: var(--white); -} - -.white { - --button-bg: var(--white); - --button-color: var(--black); - --button-hover-bg: #cbd5e0; - --button-hover-color: var(--black); -} - -.filled { - background-color: var(--button-bg); - color: var(--button-color); - border: none; - - &:hover:not(:disabled) { - background-color: var(--button-hover-bg); - color: var(--button-hover-color); - } -} - -.outline { - background-color: transparent; - border: 1px solid var(--button-bg); - color: var(--button-bg); - - &:hover:not(:disabled) { - background-color: var(--button-bg); - color: var(--button-color); - } -} - -.weak { - background-color: transparent; - border: none; - color: var(--button-bg); - - &:hover:not(:disabled) { - background-color: var(--button-bg); - opacity: 0.1; - color: var(--button-bg); - } +.button:hover { + background-color: var(--hover-background-color); + color: var(--hover-color); + opacity: var(--hover-opacity); } /* States */ diff --git a/packages/button/src/Button.stories.tsx b/packages/button/src/Button.stories.tsx index 0994fca..ce70952 100644 --- a/packages/button/src/Button.stories.tsx +++ b/packages/button/src/Button.stories.tsx @@ -31,7 +31,8 @@ type Story = StoryObj; export const Basic: Story = { args: { children: 'Button', - color: "primary" + color: 'primary', + variant: 'filled', }, }; @@ -41,9 +42,15 @@ export const Colors: Story = { }, render: (args) => (
- - - + + +
), }; @@ -55,9 +62,15 @@ export const Variants: Story = { }, render: (args) => (
- - - + + +
), }; @@ -70,7 +83,9 @@ export const States: Story = { render: (args) => (
- +
), }; diff --git a/packages/button/src/Button.test.tsx b/packages/button/src/Button.test.tsx index b0732a4..fd3c7f5 100644 --- a/packages/button/src/Button.test.tsx +++ b/packages/button/src/Button.test.tsx @@ -14,18 +14,18 @@ test('모서리가 8px radius 형태이다.', () => { expect(screen.getByRole('button')).toHaveStyle({ borderRadius: '8px' }); }); -test('variant를 주입하지 않으면 filled(배경색 #2D3748)를 기본 형태로 설정한다.', () => { +test('variant를 주입하지 않으면 filled(배경색 #00ffff)를 기본 형태로 설정한다.', () => { render(); expect(screen.getByRole('button')).toHaveStyle({ - backgroundColor: '#2D3748', + backgroundColor: '#00ffff', }); }); -test('color가 primary인 경우 배경색 #00FFFF 형태를 적용한다.', () => { +test('color가 primary인 경우 배경색 #00ffff 형태를 적용한다.', () => { render(); expect(screen.getByRole('button')).toHaveStyle({ - backgroundColor: '#00FFFF', + backgroundColor: '#00ffff', }); -}); \ No newline at end of file +}); diff --git a/packages/button/src/Button.tsx b/packages/button/src/Button.tsx index eb6dd60..dd3edbe 100644 --- a/packages/button/src/Button.tsx +++ b/packages/button/src/Button.tsx @@ -1,14 +1,19 @@ -import { Slot } from "@radix-ui/react-slot"; -import { clsx as cx } from "clsx"; -import type { ComponentProps, CSSProperties, ReactNode } from "react"; -import { forwardRef } from "react"; -import styles from "./Button.module.css"; +import { Slot } from '@radix-ui/react-slot'; +import { clsx as cx } from 'clsx'; +import { + type CSSProperties, + type ComponentProps, + type ReactNode, + forwardRef, +} from 'react'; +import { match } from 'ts-pattern'; +import styles from './Button.module.css'; -type ButtonColor = "primary" | "black" | "white"; +type ButtonColor = 'primary' | 'black' | 'white'; -type ButtonVariant = "filled" | "outline" | "weak"; +type ButtonVariant = 'filled' | 'outline' | 'weak'; -export interface ButtonProps extends ComponentProps<"button"> { +export interface ButtonProps extends ComponentProps<'button'> { color?: ButtonColor; variant?: ButtonVariant; disabled?: boolean; @@ -20,40 +25,32 @@ export interface ButtonProps extends ComponentProps<"button"> { export const Button = forwardRef( function Button( { - color = "primary", - variant = "filled", + color = 'primary', + variant = 'filled', asChild, disabled, - className, + className: _className, style: _style, children, ...rest }, - ref + ref, ) { - const Comp = asChild ? Slot : "button"; - - const commonClassName = cx( + const Comp = asChild ? Slot : 'button'; + const className = cx( styles.button, - styles[color], - styles[variant], - { - [styles.disabled]: disabled, - }, - className + { [styles.disabled]: disabled }, + _className, ); - const style = { ..._style, - "--primary-color": "#00FFFF", - "--black": "black", - "--white": "white", + ...getButtonStyle({ color, variant }), } as CSSProperties; return ( ( {children} ); - } + }, ); + +function getButtonStyle({ + color, + variant, +}: { color: ButtonColor; variant: ButtonVariant }) { + const primaryColor = '#00ffff'; + const blackColor = 'black'; + const whiteColor = 'white'; + const transparentColor = 'transparent'; + + const backgroundColor = match([color, variant]) + .with(['primary', 'filled'], () => primaryColor) + .with(['black', 'filled'], () => blackColor) + .with(['white', 'filled'], () => whiteColor) + .otherwise(() => transparentColor); + const border = match([color, variant]) + .with(['primary', 'outline'], () => `1px solid ${primaryColor}`) + .with(['black', 'outline'], () => `1px solid ${blackColor}`) + .with(['white', 'outline'], () => `1px solid ${whiteColor}`) + .otherwise(() => 'none'); + const fontColor = match([color, variant]) + .with(['primary', 'filled'], () => blackColor) + .with(['primary', 'outline'], ['primary', 'weak'], () => primaryColor) + .with(['black', 'filled'], () => whiteColor) + .with(['black', 'outline'], ['black', 'weak'], () => blackColor) + .with(['white', 'filled'], () => blackColor) + .with(['white', 'outline'], ['white', 'weak'], () => whiteColor) + .exhaustive(); + const hoverBackgroundColor = match([color, variant]) + .with(['primary', 'filled'], () => '#00d2d2') + .with(['primary', 'outline'], ['primary', 'weak'], () => primaryColor) + .with(['black', 'filled'], () => '#2d3748') + .with(['black', 'outline'], ['black', 'weak'], () => blackColor) + .with(['white', 'filled'], () => '#cbd5e0') + .with(['white', 'outline'], ['white', 'weak'], () => whiteColor) + .exhaustive(); + const hoverFontColor = match([color, variant]) + .with(['primary', 'filled'], ['primary', 'outline'], () => blackColor) + .with(['primary', 'weak'], () => primaryColor) + .with(['black', 'filled'], ['black', 'outline'], () => whiteColor) + .with(['black', 'weak'], () => blackColor) + .with(['white', 'filled'], ['white', 'outline'], () => blackColor) + .with(['white', 'weak'], () => whiteColor) + .exhaustive(); + const hoverOpacity = variant === 'weak' ? 0.1 : 1; + + return { + '--background-color': backgroundColor, + '--border': border, + '--color': fontColor, + '--hover-background-color': hoverBackgroundColor, + '--hover-color': hoverFontColor, + '--hover-opacity': hoverOpacity, + }; +} diff --git a/packages/button/tsup.config.ts b/packages/button/tsup.config.ts index c533199..ee4b117 100644 --- a/packages/button/tsup.config.ts +++ b/packages/button/tsup.config.ts @@ -1,8 +1,3 @@ -import { defineConfig } from 'tsup'; +import defaultConfig from '../../tsup.config'; -export default defineConfig({ - entry: ['src/index.ts'], - clean: true, - dts: true, - format: ['esm', 'cjs'], -}); +export default defaultConfig; diff --git a/packages/button/vitest.config.ts b/packages/button/vitest.config.ts index 2e5a4f7..a917827 100644 --- a/packages/button/vitest.config.ts +++ b/packages/button/vitest.config.ts @@ -1,16 +1,11 @@ -import { defineConfig } from 'vitest/config'; +import { defineProject, mergeConfig } from 'vitest/config'; +import defaultConfig from '../../vitest.config'; -export default defineConfig({ - test: { - coverage: { - include: ['./src/**/*.{ts,tsx}'], - exclude: ['./src/**/*.stories.tsx', './src/env.d.ts', './src/index.ts'], +export default mergeConfig( + defaultConfig, + defineProject({ + test: { + setupFiles: './vitest.setup.ts', }, - css: true, - environment: 'happy-dom', - globals: true, - passWithNoTests: true, - setupFiles: './vitest.setup.ts', - watch: false, - }, -}); + }), +); diff --git a/packages/button/vitest.setup.ts b/packages/button/vitest.setup.ts index a6253fa..7b0828b 100644 --- a/packages/button/vitest.setup.ts +++ b/packages/button/vitest.setup.ts @@ -1 +1 @@ -import '@testing-library/jest-dom/vitest'; \ No newline at end of file +import '@testing-library/jest-dom'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a35e38c..5ec99a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -326,17 +326,20 @@ importers: specifier: 'catalog:' version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) - packages/card: + packages/button: dependencies: '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.13)(react@18.3.1) - '@sipe-team/tokens': + '@sipe-team/typography': specifier: workspace:* - version: link:../tokens + version: link:../typography clsx: specifier: ^2.1.1 version: 2.1.1 + ts-pattern: + specifier: ^5.6.0 + version: 5.6.0 devDependencies: '@storybook/addon-essentials': specifier: 'catalog:' @@ -390,54 +393,51 @@ importers: specifier: 'catalog:' version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) - packages/button: + packages/card: dependencies: '@radix-ui/react-slot': specifier: ^1.1.0 - version: 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@sipe-team/typography': + version: 1.1.0(@types/react@18.3.13)(react@18.3.1) + '@sipe-team/tokens': specifier: workspace:* - version: link:../typography + version: link:../tokens clsx: specifier: ^2.1.1 version: 2.1.1 devDependencies: - '@biomejs/biome': - specifier: 'catalog:' - version: 1.9.4 '@storybook/addon-essentials': specifier: 'catalog:' - version: 8.4.5(@types/react@18.3.12)(storybook@8.4.5(prettier@2.8.8)) + version: 8.4.6(@types/react@18.3.13)(storybook@8.4.6(prettier@2.8.8)) '@storybook/addon-interactions': specifier: 'catalog:' - version: 8.4.5(storybook@8.4.5(prettier@2.8.8)) + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) '@storybook/addon-links': specifier: 'catalog:' - version: 8.4.5(react@18.3.1)(storybook@8.4.5(prettier@2.8.8)) + version: 8.4.6(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) '@storybook/blocks': specifier: 'catalog:' - version: 8.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.5(prettier@2.8.8)) + version: 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) '@storybook/react': specifier: 'catalog:' - version: 8.4.5(@storybook/test@8.4.5(storybook@8.4.5(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.5(prettier@2.8.8))(typescript@5.6.3) + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2) '@storybook/react-vite': specifier: 'catalog:' - version: 8.4.5(@storybook/test@8.4.5(storybook@8.4.5(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.2)(storybook@8.4.5(prettier@2.8.8))(typescript@5.6.3)(vite@5.4.10(@types/node@22.8.1)) + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)) '@storybook/test': specifier: 'catalog:' - version: 8.4.5(storybook@8.4.5(prettier@2.8.8)) + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react': specifier: ^18.3.12 - version: 18.3.12 + version: 18.3.13 happy-dom: specifier: 'catalog:' - version: 15.7.4 + version: 15.11.7 react: specifier: ^18.3.1 version: 18.3.1 @@ -446,16 +446,16 @@ importers: version: 13.0.0 storybook: specifier: 'catalog:' - version: 8.4.5(prettier@2.8.8) + version: 8.4.6(prettier@2.8.8) tsup: specifier: 'catalog:' - version: 8.3.5(jiti@2.3.3)(postcss@8.4.47)(typescript@5.6.3) + version: 8.3.5(jiti@2.4.1)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) typescript: specifier: 'catalog:' - version: 5.6.3 + version: 5.7.2 vitest: specifier: 'catalog:' - version: 2.1.4(@types/node@22.8.1)(happy-dom@15.7.4) + version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) packages/divider: dependencies: @@ -3966,6 +3966,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-pattern@5.6.0: + resolution: {integrity: sha512-SL8u60X5+LoEy9tmQHWCdPc2hhb2pKI6I1tU5Jue3v8+iRqZdcT3mWPwKKJy1fMfky6uha82c8ByHAE8PMhKHw==} + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -7288,6 +7291,8 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-pattern@5.6.0: {} + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3