Skip to content

Commit

Permalink
fix(button): handle dynamic styles within typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
noahluftyang committed Jan 5, 2025
1 parent cef6905 commit 4f1f1bc
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 150 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-seas-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sipe-team/button": patch
---

fix(button): handle dynamic styles within typescript
File renamed without changes.
22 changes: 12 additions & 10 deletions packages/button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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:",
Expand Down Expand Up @@ -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
}
61 changes: 7 additions & 54 deletions packages/button/src/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
31 changes: 23 additions & 8 deletions packages/button/src/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
children: 'Button',
color: "primary"
color: 'primary',
variant: 'filled',
},
};

Expand All @@ -41,9 +42,15 @@ export const Colors: Story = {
},
render: (args) => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button {...args} color="primary">Primary</Button>
<Button {...args} color="black">Black</Button>
<Button {...args} color="white">White</Button>
<Button {...args} color="primary">
Primary
</Button>
<Button {...args} color="black">
Black
</Button>
<Button {...args} color="white">
White
</Button>
</div>
),
};
Expand All @@ -55,9 +62,15 @@ export const Variants: Story = {
},
render: (args) => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button {...args} variant="filled">Filled</Button>
<Button {...args} variant="outline">Outline</Button>
<Button {...args} variant="weak">Weak</Button>
<Button {...args} variant="filled">
Filled
</Button>
<Button {...args} variant="outline">
Outline
</Button>
<Button {...args} variant="weak">
Weak
</Button>
</div>
),
};
Expand All @@ -70,7 +83,9 @@ export const States: Story = {
render: (args) => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button {...args}>Normal</Button>
<Button {...args} disabled>Disabled</Button>
<Button {...args} disabled>
Disabled
</Button>
</div>
),
};
10 changes: 5 additions & 5 deletions packages/button/src/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ test('모서리가 8px radius 형태이다.', () => {
expect(screen.getByRole('button')).toHaveStyle({ borderRadius: '8px' });
});

test('variant를 주입하지 않으면 filled(배경색 #2D3748)를 기본 형태로 설정한다.', () => {
test('variant를 주입하지 않으면 filled(배경색 #00ffff)를 기본 형태로 설정한다.', () => {
render(<Button>테스트</Button>);

expect(screen.getByRole('button')).toHaveStyle({
backgroundColor: '#2D3748',
backgroundColor: '#00ffff',
});
});

test('color가 primary인 경우 배경색 #00FFFF 형태를 적용한다.', () => {
test('color가 primary인 경우 배경색 #00ffff 형태를 적용한다.', () => {
render(<Button color="primary">테스트</Button>);

expect(screen.getByRole('button')).toHaveStyle({
backgroundColor: '#00FFFF',
backgroundColor: '#00ffff',
});
});
});
106 changes: 79 additions & 27 deletions packages/button/src/Button.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,46 +25,93 @@ export interface ButtonProps extends ComponentProps<"button"> {
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
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 (
<Comp
ref={ref}
className={commonClassName}
className={className}
style={style}
disabled={disabled}
{...rest}
>
{children}
</Comp>
);
}
},
);

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,
};
}
9 changes: 2 additions & 7 deletions packages/button/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -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;
23 changes: 9 additions & 14 deletions packages/button/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
}),
);
2 changes: 1 addition & 1 deletion packages/button/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import '@testing-library/jest-dom/vitest';
import '@testing-library/jest-dom';
Loading

0 comments on commit 4f1f1bc

Please sign in to comment.