Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/button-v2-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@sipe-team/button": major
"@sipe-team/tokens": minor
---

Redesign Button component based on 5th generation design system

- **BREAKING**: Rename `ButtonVariant.filled` to `ButtonVariant.fill`
- **BREAKING**: Expand `ButtonSize` from `sm | lg` to `sm | md | lg | xl`
- Add `leftIcon` and `rightIcon` props for icon support
- Apply 5th design colors via `createVar()` (button-scoped CSS variables)
- Add interaction states: hover (gradient), pressed (`#FE4E07`), disabled (`gray500/600`)
- Fix disabled CSS specificity bug by moving styles into recipe base selectors
- Add `theme5th` color token to `@sipe-team/tokens`
93 changes: 65 additions & 28 deletions packages/button/src/Button.css.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,115 @@
import { vars } from '@sipe-team/tokens';
import { color, vars } from '@sipe-team/tokens';

import { style } from '@vanilla-extract/css';
import { createVar, style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { ButtonSize, ButtonVariant } from './Button';

export const disabled = style({
opacity: 0.4,
cursor: 'not-allowed',
pointerEvents: 'none',
export const buttonOrange = createVar();
export const buttonGradient = createVar();
export const buttonRed = createVar();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 쓰지말고,

#258

에서,

packages/button/src/Button.css.ts

vars.color.accent.* 같은 걸로 쓸 수 있을지 검토해주세요

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지훈님 말씀처럼 가능한 토큰 시스템 이용해주시면 v2 token으로 마이그레이션할 때도 수월할 것 같아요!


export const iconWrapper = style({
display: 'flex',
alignItems: 'center',
flexShrink: 0,
});

export const iconLayout = style({
gap: vars.spacing.component.md,
});

export const button = recipe({
base: {
vars: {
[buttonOrange]: '#FF7C27',
[buttonGradient]: 'linear-gradient(225deg, #FF4500 0%, #FFB24D 100%)',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 추후 토큰이 그라디언트 자체로 끼워지는건가요?
color.gradient?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Yeom-JinHo 넵넵 color.5th.gradient 처럼 5기 전용 그라디언트 토큰으로 들어가게 될 것 같습니다 hover 상태를 그라디언트 색상으로 진행하고 싶어하시더라고요!!

[buttonRed]: '#FE4E07',
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 마찬가지로,
#258
에서 이미 semantic token 을 구축했으니,

여기만 독자적인 스타일을 쓰는 게 아니라,
시맨틱 토큰이 활용한 지 검토해주세요!

display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: vars.radius.component.md,
fontStyle: 'normal',
fontWeight: vars.typography.fontWeight.semiBold,
lineHeight: '150%',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
border: 'none',
fontFamily: vars.typography.fontFamily,
':focus-visible': {
outline: `2px solid ${vars.color.border.focus}`,
outline: `2px solid ${buttonOrange}`,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포커스링은 브랜드 컬러보다는,
blue 계열이 일반적으로 더 적합한 것 같습니다 (WCAG 구분 가능성)

@3o14 님 크로스체크 부탁드립니다

outlineOffset: '2px',
},
selectors: {
'&:disabled, &[aria-disabled="true"]': {
backgroundColor: color.gray500,
color: color.gray600,
cursor: 'not-allowed',
pointerEvents: 'none',
border: 'none',
background: color.gray500,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backgroundColor: color.gray500,
가 이미 있어서 스타일이 중복되는 것 같습니다

},
},
},
variants: {
variant: {
[ButtonVariant.filled]: {
backgroundColor: vars.color.accent.default,
color: vars.color.foreground.onAccent,
border: 'none',
[ButtonVariant.fill]: {
backgroundColor: buttonOrange,
color: '#000',
':hover': {
backgroundColor: vars.color.accent.hover,
background: buttonGradient,
},
':active': {
background: buttonRed,
},
},
[ButtonVariant.outline]: {
backgroundColor: 'transparent',
border: `1px solid ${vars.color.accent.default}`,
color: vars.color.accent.default,
':hover': {
backgroundColor: vars.color.accent.default,
color: vars.color.foreground.onAccent,
},
border: `1px solid ${buttonOrange}`,
color: buttonOrange,
},
[ButtonVariant.ghost]: {
backgroundColor: 'transparent',
border: 'none',
color: vars.color.accent.default,
color: buttonOrange,
':hover': {
backgroundColor: vars.color.accent.subtle,
opacity: 0.8,
},
':active': {
color: buttonRed,
},
},
},
size: {
[ButtonSize.sm]: {
height: '32px',
padding: `0 ${vars.spacing.component.sm}`,
fontSize: vars.typography.fontSize['200'],
lineHeight: vars.typography.lineHeight.compact,
padding: `0 ${vars.spacing.component.xs}`,
borderRadius: vars.radius.component.md,
fontSize: vars.typography.fontSize['050'],
},
[ButtonSize.md]: {
height: '40px',
padding: '0 12px',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 마찬가지로.
vars.spacing.component.md 처럼
시맨틱 토큰에 해당 내용이 있으니 확인해서 사용해주시면 좋을 것 같습니다.

borderRadius: '6px',
fontSize: vars.typography.fontSize['100'],
},
[ButtonSize.lg]: {
height: '48px',
padding: `0 ${vars.spacing.component.lg}`,
fontSize: vars.typography.fontSize['400'],
lineHeight: vars.typography.lineHeight.regular,
padding: `0 ${vars.spacing.component.sm}`,
borderRadius: '6px',
fontSize: vars.typography.fontSize['200'],
},
[ButtonSize.xl]: {
height: '64px',
padding: `0 ${vars.spacing.component.md}`,
borderRadius: vars.radius.component.lg,
fontSize: vars.typography.fontSize['500'],
},
},
},
compoundVariants: [],
defaultVariants: {
variant: ButtonVariant.filled,
variant: ButtonVariant.fill,
size: ButtonSize.lg,
},
});
146 changes: 134 additions & 12 deletions packages/button/src/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,70 @@ import type { Meta, StoryObj } from '@storybook/react';

import { Button } from './Button';

const ArrowRightIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
);

const SearchIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
);

const DownloadIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" x2="12" y1="15" y2="3" />
</svg>
);

const ChevronRightIcon = () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>
);

const meta = {
title: 'Components/Button',
component: Button,
Expand All @@ -11,12 +75,12 @@ const meta = {
argTypes: {
variant: {
description: 'The visual style of the button',
options: ['filled', 'outline', 'ghost'],
options: ['fill', 'outline', 'ghost'],
control: { type: 'radio' },
},
size: {
description: 'The size of the button',
options: ['sm', 'lg'],
options: ['sm', 'md', 'lg', 'xl'],
control: { type: 'radio' },
},
disabled: {
Expand All @@ -32,7 +96,7 @@ type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
children: 'Button',
variant: 'filled',
variant: 'fill',
size: 'lg',
},
};
Expand All @@ -42,9 +106,9 @@ export const Variants: Story = {
children: 'Button',
},
render: (args) => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button {...args} variant="filled">
Filled
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<Button {...args} variant="fill">
Fill
</Button>
<Button {...args} variant="outline">
Outline
Expand All @@ -59,30 +123,88 @@ export const Variants: Story = {
export const Sizes: Story = {
args: {
children: 'Button',
variant: 'filled',
variant: 'fill',
},
render: (args) => (
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<Button {...args} size="sm">
Small
</Button>
<Button {...args} size="md">
Medium
</Button>
<Button {...args} size="lg">
Large
</Button>
<Button {...args} size="xl">
Extra Large
</Button>
</div>
),
};

export const States: Story = {
export const Disabled: Story = {
args: {
children: 'Button',
},
render: (args) => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button {...args}>Normal</Button>
<Button {...args} disabled>
Disabled
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<Button {...args} variant="fill" disabled>
Fill Disabled
</Button>
<Button {...args} variant="outline" disabled>
Outline Disabled
</Button>
<Button {...args} variant="ghost" disabled>
Ghost Disabled
</Button>
</div>
),
};

export const WithLeftIcon: Story = {
args: {
children: 'Search',
variant: 'fill',
size: 'xl',
leftIcon: <SearchIcon />,
},
};

export const WithRightIcon: Story = {
args: {
children: 'Next',
variant: 'fill',
size: 'xl',
rightIcon: <ArrowRightIcon />,
},
};

export const WithBothIcons: Story = {
args: {
children: 'Download',
variant: 'fill',
size: 'xl',
leftIcon: <DownloadIcon />,
rightIcon: <ChevronRightIcon />,
},
};

export const AllVariantsAndSizes: Story = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{(['fill', 'outline', 'ghost'] as const).map((variant) => (
<div key={variant}>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>{variant}</div>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
{(['sm', 'md', 'lg', 'xl'] as const).map((size) => (
<Button key={size} variant={variant} size={size}>
{size.toUpperCase()}
</Button>
))}
</div>
</div>
))}
</div>
),
};
Loading
Loading