-
Notifications
You must be signed in to change notification settings - Fork 1
Feat(design-system): Button 컴포넌트 구현 #32
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 all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,108 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
| import Button from './Button'; | ||
|
|
||
| // Storybook에 컴포넌트를 어떻게 표시할지 정의합니다. | ||
| const meta: Meta<typeof Button> = { | ||
| title: 'UI/Button', | ||
| title: 'Components/Button', | ||
| component: Button, | ||
| tags: ['autodocs'], | ||
| parameters: { | ||
| layout: 'centered', | ||
| docs: { | ||
| description: { | ||
| component: | ||
| '**애플리케이션 전반에서 사용될 기본 버튼입니다.** `variant`로 주/보조 스타일을, `size`로 크기를, `isDisabled`로 비활성화 상태를 제어합니다.', | ||
| }, | ||
| }, | ||
| }, | ||
| argTypes: { | ||
| variant: { | ||
| control: 'inline-radio', | ||
| options: ['primary', 'secondary'], | ||
| }, | ||
| size: { | ||
| control: 'select', | ||
| options: ['small', 'medium', 'large'], | ||
| }, | ||
| isDisabled: { | ||
| control: 'boolean', | ||
| }, | ||
| children: { | ||
| control: 'text', | ||
| }, | ||
| onClick: { | ||
| action: 'clicked', | ||
| description: '버튼 클릭 시 호출되는 이벤트 핸들러입니다.', | ||
| }, | ||
| className: { table: { disable: true } }, | ||
| }, | ||
|
|
||
| args: { | ||
| variant: 'primary', | ||
| size: 'medium', | ||
| isDisabled: false, | ||
| children: 'Button', | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof Button>; | ||
|
|
||
| export const Primary: Story = { | ||
| name: 'variant: primary', | ||
| args: { | ||
| variant: 'primary', | ||
| children: 'Primary Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Secondary: Story = { | ||
| name: 'variant: secondary', | ||
| args: { | ||
| variant: 'secondary', | ||
| children: 'Secondary Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Large: Story = { | ||
| name: 'size: large', | ||
| args: { | ||
| size: 'large', | ||
| children: 'Large Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Small: Story = { | ||
| name: 'size: small', | ||
| args: { | ||
| size: 'small', | ||
| children: 'Small Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Disabled: Story = { | ||
| name: 'state: disabled', | ||
| args: { | ||
| isDisabled: true, | ||
| }, | ||
| render: (args) => ( | ||
| <div style={{ display: 'flex', gap: '16px' }}> | ||
| <Button {...args} variant="primary"> | ||
| Primary | ||
| </Button> | ||
| <Button {...args} variant="secondary"> | ||
| Secondary | ||
| </Button> | ||
| </div> | ||
| ), | ||
| }; | ||
|
|
||
| export const WithAlertOnClick: Story = { | ||
| name: 'onClick with Alert', | ||
| args: { | ||
| children: 'onClick test', | ||
| onClick: () => { | ||
| alert('버튼이 클릭되었습니다.'); | ||
| }, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,50 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cn } from '@/lib'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cva } from 'class-variance-authority'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant?: 'primary' | 'secondary'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size?: 'small' | 'medium' | 'large'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isDisabled?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Button = ({ children, ...props }: ButtonProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buttonVariants = cva( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'cursor-pointer rounded-[0.4rem] disabled:cursor-not-allowed w-full', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+12
Collaborator
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. w-full로 화면에 따라 그냥 자동 반응형으로 맞춰지니 레이아웃 맞추기 편리하겠네요! 혹시 그럼 따로 버튼 width 설정이 필요한 경우는 무조건 부모 컨테이너로 계속 감싸야하는거죠? 버튼 크기가 상황에 따라 계속 달라진다면,, width 값을 props로 줘서 제어하는 거랑 필요시에 계속 width 있는 컨테이너로 wrapping는 것 중 어느 것이 더 유연성 있는 방향일지 고민되는 포인트 같습니다!
Member
Author
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. 맞습니다! 그렇기 때문에 재림님은 어떻게 생각하시나요??
Collaborator
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. 아하 그럼, 사실 상 width 커스텀 시 후자(wrapping)랑 전자(className) 방안이 다 되는 식으로 열려있군용
Member
Author
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. 만약 전자로 하게 되면 어떤 식으로 넘겨야 할까요?
Collaborator
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. 개인적으로 어차피 props에 넣어서 커스텀 유연성을 높이는 의미를 유지하려면, 아예 단위 자체로 '10rem'으로 넘겨서 그자체로 스타일 먹여주는게 좋을 것 같아요! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variants: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'bg-gray900 text-white-bg hover:bg-gray800 active:bg-gray900 disabled:bg-gray200 disabled:text-font-ltgray-4', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secondary: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'bg-white-bg text-font-black-1 hover:bg-gray200 active:bg-white-bg disabled:bg-gray200 disabled:text-font-ltgray-4 outline outline-gray200', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| small: 'px-[1.2rem] py-[0.8rem] sub5-sb', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| medium: 'px-[1.2rem] py-[1.2rem] sub5-sb', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| large: 'px-[1.6rem] py-[1.5rem] sub3-sb', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultVariants: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant: 'primary', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size: 'medium', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Button = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant = 'primary', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isDisabled = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...props | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+39
Collaborator
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. 질문있습니당
Member
Author
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. 일반 tailwind css 스타일 주듯이 똑같이 넘겨주시면 됩니다!! 기본 스타일은 거의 지정이 되어있어서 PR 내용처럼 width 정도 주면 될 것 같아요 👍 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ButtonProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" {...props}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={cn(buttonVariants({ variant, size }), className)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={isDisabled} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+47
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. 중요: disabled 우선순위가 현재 아래처럼 -const Button = ({
- variant = 'primary',
- size,
- children,
- className,
- isDisabled = false,
- ...props
-}: ButtonProps) => {
+const Button = ({
+ variant = 'primary',
+ size,
+ children,
+ className,
+ isDisabled,
+ type,
+ disabled,
+ ...rest
+}: ButtonProps) => {
return (
<button
- type="button"
- className={cn(buttonVariants({ variant, size }), className)}
- disabled={isDisabled}
- {...props}
+ type={type ?? 'button'}
+ className={cn(buttonVariants({ variant, size }), className)}
+ disabled={isDisabled ?? disabled}
+ {...rest}
>
{children}
</button>
);
};추가 제안(선택): ref 전달이 필요한 시나리오를 위해 +import React from 'react';
-const Button = ({ ... }: ButtonProps) => {
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+ ({ ...props }, ref) => {
+ const { /* 위 병합 로직 동일 */ } = props;
+ return (
+ <button ref={ref} /* ... */ />
+ );
+ }
+);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
💡 Verification agent
🧩 Analysis chain
cn import 경로(바렐) 확인 필요
@/lib가cn을 재노출(re-export)하지 않으면 빌드가 실패합니다. 현재 구현대로라면@/lib/utils에서 직접 가져오거나,@/lib/index.ts에서cn을 내보내도록 보장해야 합니다.아래 스크립트로 재노출 여부를 점검해 주세요. 기대값: lib 폴더 어딘가에서
cn이 export 되거나,index.*에서 utils를 재노출.만약 재노출이 없다면 다음 중 하나로 수정해 주세요.
또는
packages/design-system/src/lib/index.ts에서:+ export * from './utils';🏁 Script executed:
Length of output: 391
아래 스크립트로 다시 한 번
cn의 선언/재노출 위치를 점검해주세요.– 이번에는 파일 타입 제한을 제거해 범용 검색을 수행합니다.
🏁 Script executed:
Length of output: 414
아래 스크립트로
index.ts(x)에서utils재노출 여부를 다시 확인합니다.🏁 Script executed:
Length of output: 592
cn 재노출(바렐) 누락 확인 – 조치 필요
검증 결과,
packages/design-system/src/lib/utils.ts에서export const cn구현은 확인되었으나,packages/design-system/src/lib/index.ts에utils재노출이 빠져 있어import { cn } from '@/lib'구문이 빌드 오류를 일으킵니다. 아래 두 가지 중 한 가지 방법으로 반드시 수정해 주세요.• 직접 utils 경로에서 가져오기
• 파일:
packages/design-system/src/components/button/Button.tsx(라인 1)• 변경 예시:
diff - import { cn } from '@/lib'; + import { cn } from '@/lib/utils';• lib 인덱스 파일에 utils 재노출 추가
• 파일:
packages/design-system/src/lib/index.ts• 변경 예시:
diff + export * from './utils';📝 Committable suggestion
🤖 Prompt for AI Agents