|
| 1 | +import { |
| 2 | + useState, |
| 3 | +} from 'react' |
| 4 | +import { |
| 5 | + Canvas, |
| 6 | + Meta, |
| 7 | + Story, |
| 8 | +} from '@storybook/addon-docs' |
| 9 | +import base from 'paths.macro' |
| 10 | +import { |
| 11 | + getTitle, |
| 12 | +} from 'Utils/storyUtils' |
| 13 | +import { |
| 14 | + Avatar, |
| 15 | +} from 'Components/Avatars/Avatar' |
| 16 | +import { |
| 17 | + Button, |
| 18 | + ButtonSize, |
| 19 | + ButtonColorVariant, |
| 20 | + ButtonStyleVariant, |
| 21 | +} from 'Components/Button' |
| 22 | +import { |
| 23 | + ListItem, |
| 24 | +} from 'Components/ListItem' |
| 25 | +import { |
| 26 | + Overlay, |
| 27 | + OverlayPosition, |
| 28 | +} from 'Components/Overlay' |
| 29 | +import { |
| 30 | + SectionLabel, |
| 31 | +} from 'Components/SectionLabel' |
| 32 | +import { |
| 33 | + StackItem, |
| 34 | + HStack, |
| 35 | + VStack, |
| 36 | +} from 'Components/Stack' |
| 37 | +import { |
| 38 | + StatusType, |
| 39 | +} from 'Components/Status' |
| 40 | +import { |
| 41 | + Text, |
| 42 | +} from 'Components/Text' |
| 43 | +import { |
| 44 | + styled, |
| 45 | + Typography, |
| 46 | +} from 'Foundation' |
| 47 | +import { |
| 48 | + OverviewCTA, |
| 49 | + OverviewFloating, |
| 50 | + UsageAsync, |
| 51 | + UsageComposite, |
| 52 | + UsageCTA, |
| 53 | + UsageCTA2, |
| 54 | + UsageDropdown, |
| 55 | + UsageVariousContentsComposite, |
| 56 | + UsageVariousContentsCustom, |
| 57 | + UsageVariousContentsIconOnly, |
| 58 | + UsageWebLinks, |
| 59 | + VariantsColor, |
| 60 | + VariantsSize, |
| 61 | + VariantsStyle, |
| 62 | +} from './Button.stories' |
| 63 | + |
| 64 | +<Meta |
| 65 | + title={getTitle(base)} |
| 66 | + parameters={{ |
| 67 | + viewMode: 'docs', |
| 68 | + }} |
| 69 | +/> |
| 70 | + |
| 71 | +# Button |
| 72 | + |
| 73 | +## Overview |
| 74 | + |
| 75 | +버튼은 클릭을 통해 사용자에게 액션을 제공하는 컴포넌트입니다. |
| 76 | + |
| 77 | +버튼은 크게 4가지 사이즈로 나뉩니다. 그 중 M이 가장 보편적이며, 공간 절약을 위해 S가 쓰입니다. List나 Input 등 액션이 필요할 때는 XS도 사용할 수 있습니다. 가입, 초대 UX 등 굉장히 강조해야 할 버튼의 경우, L, XL 사이즈를 사용할 수 있습니다. |
| 78 | + |
| 79 | +<Canvas> |
| 80 | + <Story story={OverviewCTA} /> |
| 81 | +</Canvas> |
| 82 | + |
| 83 | +<Canvas> |
| 84 | + <Story story={OverviewFloating} /> |
| 85 | +</Canvas> |
| 86 | + |
| 87 | +## Usage |
| 88 | + |
| 89 | +### Recipe: Call to action |
| 90 | + |
| 91 | +- 한 화면 내에서 CTA 버튼은 가능한 한, 가장 중요한 버튼 1개만 primary로 지정합니다. 후순위로 colored button을 같이 넣어야 할 경우에는 secondary로 넣습니다. 그 외에는 secondary, tetiary button을 조합하여 넣습니다. |
| 92 | +- 버튼 사이의 간격은 0 또는 6으로 합니다. |
| 93 | + |
| 94 | +<Canvas> |
| 95 | + <Story story={UsageCTA} /> |
| 96 | +</Canvas> |
| 97 | + |
| 98 | + |
| 99 | +<Canvas> |
| 100 | + <Story story={UsageCTA2} /> |
| 101 | +</Canvas> |
| 102 | + |
| 103 | +### Recipe: Web links |
| 104 | + |
| 105 | +- 보통의 경우, tertiary button을 적절한 icon과 함께 사용합니다. |
| 106 | + |
| 107 | +<Canvas> |
| 108 | + <Story story={UsageWebLinks} /> |
| 109 | +</Canvas> |
| 110 | + |
| 111 | +### Recipe: Composite Usage |
| 112 | + |
| 113 | +- XS 크기의 버튼은 ListItem, SectionLabel 등 다른 컴포넌트에 함께 사용될 수 있습니다. 컴포넌트 좌우측에서 액션으로 사용됩니다. |
| 114 | + |
| 115 | +<Story story={UsageComposite} /> |
| 116 | + |
| 117 | +### Recipe: Button with various contents |
| 118 | + |
| 119 | +- 기본적으로 `leftContent`, `rightContent` prop에 icon name에 해당하는 string을 지정하여 좌우측에 아이콘이 들어가는 형태를 표현합니다. |
| 120 | + |
| 121 | +<Canvas> |
| 122 | + <Story story={UsageVariousContentsComposite} /> |
| 123 | +</Canvas> |
| 124 | + |
| 125 | +- 아이콘만 들어가는 버튼의 경우, `leftContent` prop만 사용합니다. |
| 126 | + |
| 127 | +<Canvas> |
| 128 | + <Story story={UsageVariousContentsIconOnly} /> |
| 129 | +</Canvas> |
| 130 | + |
| 131 | +- 아이콘 이외에 커스텀 컴포넌트가 들어가야 하는 상황에는, `leftContent`, `rightContent` prop에 `JSX.Element` 값을 지정할 수 있습니다. |
| 132 | + |
| 133 | +<Canvas> |
| 134 | + <Story story={UsageVariousContentsCustom} /> |
| 135 | +</Canvas> |
| 136 | + |
| 137 | +### Recipe: Button with asynchronous actions |
| 138 | + |
| 139 | +- 비동기적인 액션을 처리하는 경우, 액션이 처리중인 상태를 사용자에게 노출해야 하고, 액션이 처리중이라면 버튼을 비활성화하여 액션이 중복 처리되는 것을 막는 것이 보통의 경우입니다. `loading`, `disabled` prop을 통해 이 usecase를 구현할 수 있습니다. |
| 140 | + |
| 141 | +```tsx |
| 142 | +const AsyncActionButton = () => { |
| 143 | + const [isFetching, setFetching] = useState(false) |
| 144 | + const handleClick = () => { |
| 145 | + setFetching(true) |
| 146 | + setTimeout(() => { |
| 147 | + setFetching(false) |
| 148 | + }, 1000) |
| 149 | + } |
| 150 | + return ( |
| 151 | + <Button |
| 152 | + leftContent="play" |
| 153 | + text="Click Me!" |
| 154 | + colorVariant={ButtonColorVariant.Cobalt} |
| 155 | + styleVariant={ButtonStyleVariant.Primary} |
| 156 | + loading={isFetching} |
| 157 | + disabled={isFetching} |
| 158 | + onClick={handleClick} |
| 159 | + /> |
| 160 | + ) |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +<Canvas> |
| 165 | + <Story story={UsageAsync} /> |
| 166 | +</Canvas> |
| 167 | + |
| 168 | +### Recipe: Button with dropdown |
| 169 | + |
| 170 | +- 버튼을 눌러 dropdown, select 등 별도의 UI를 노출하는 경우, 해당 UI가 노출된 상태에서는 버튼이 "눌린 상태"를 유지하는 것이 자연스럽습니다. |
| 171 | +- `active` prop을 통해 버튼이 "눌린 상태" 임을 표현할 수 있습니다. |
| 172 | + |
| 173 | +```tsx |
| 174 | +const OpenDropdownButton = () => { |
| 175 | + const [isOpen, setIsOpen] = useState(false) |
| 176 | + |
| 177 | + return ( |
| 178 | + <div> |
| 179 | + <Button |
| 180 | + text="Select" |
| 181 | + rightContent="triangle-down" |
| 182 | + active={isOpen} |
| 183 | + colorVariant={ButtonColorVariant.MonochromeLight} |
| 184 | + styleVariant={ButtonStyleVariant.Tertiary} |
| 185 | + onClick={() => setIsOpen(true)} |
| 186 | + /> |
| 187 | + |
| 188 | + { ... } |
| 189 | + </div> |
| 190 | + ) |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +<Canvas> |
| 195 | + <Story story={UsageDropdown} /> |
| 196 | +</Canvas> |
| 197 | + |
| 198 | +## Variants |
| 199 | + |
| 200 | +### Color Variants |
| 201 | + |
| 202 | +- `ButtonColorVariant` enum을 통해 정의되며, `colorVariant` prop으로 지정할 수 있습니다. |
| 203 | + |
| 204 | +<Story story={VariantsColor} /> |
| 205 | + |
| 206 | +### Style Variants |
| 207 | + |
| 208 | +- `ButtonStyleVariant` enum을 통해 정의되며, `styleVariant` prop으로 지정할 수 있습니다. |
| 209 | + |
| 210 | +<Story story={VariantsStyle} /> |
| 211 | + |
| 212 | +### Size |
| 213 | + |
| 214 | +- `ButtonSize` enum을 통해 정의되며, `size` prop으로 지정할 수 있습니다. |
| 215 | + |
| 216 | +<Story story={VariantsSize} /> |
| 217 | + |
| 218 | +## API |
| 219 | + |
| 220 | +- `ButtonProps`는 `BezierComponentProps`를 지원합니다. |
| 221 | + |
| 222 | +<details> |
| 223 | +<summary><h3>ButtonSize</h3></summary> |
| 224 | + |
| 225 | +```ts |
| 226 | +enum ButtonSize { |
| 227 | + XS, |
| 228 | + S, |
| 229 | + M, |
| 230 | + L, |
| 231 | + XL, |
| 232 | +} |
| 233 | +``` |
| 234 | +</details> |
| 235 | + |
| 236 | +<details> |
| 237 | +<summary><h3>ButtonColorVariant</h3></summary> |
| 238 | + |
| 239 | +```ts |
| 240 | +enum ButtonColorVariant { |
| 241 | + Blue, |
| 242 | + Red, |
| 243 | + Green, |
| 244 | + Cobalt, |
| 245 | + Orange, |
| 246 | + Pink, |
| 247 | + Purple, |
| 248 | + MonochromeLight, |
| 249 | + MonochromeDark, |
| 250 | + |
| 251 | + // @deprecated |
| 252 | + Monochrome, |
| 253 | +} |
| 254 | +``` |
| 255 | +</details> |
| 256 | + |
| 257 | +<details> |
| 258 | +<summary><h3>ButtonStyleVariant</h3></summary> |
| 259 | + |
| 260 | +```ts |
| 261 | +enum ButtonStyleVariant { |
| 262 | + Primary, |
| 263 | + Secondary, |
| 264 | + Tertiary, |
| 265 | + Floating, |
| 266 | +} |
| 267 | +``` |
| 268 | +</details> |
| 269 | + |
| 270 | +<details> |
| 271 | +<summary><h3>ButtonProps</h3></summary> |
| 272 | + |
| 273 | +```ts |
| 274 | +interface ButtonProps { |
| 275 | + /** |
| 276 | + * `type` attribute of typical HTML button. |
| 277 | + * |
| 278 | + * You may want to set `type` to `submit` to the button |
| 279 | + * which is used as a submit button in `<form>` component. |
| 280 | + * |
| 281 | + * @default 'button' |
| 282 | + */ |
| 283 | + type?: HTMLButtonElement['type'] |
| 284 | + |
| 285 | + /** |
| 286 | + * The text content in the button. |
| 287 | + * |
| 288 | + * Do not pass `text` prop if it is an icon-only button. |
| 289 | + */ |
| 290 | + text?: string |
| 291 | + |
| 292 | + /** |
| 293 | + * The content displayed at left of the text content in the button. |
| 294 | + */ |
| 295 | + leftContent?: IconName | React.ReactNode |
| 296 | + |
| 297 | + /** |
| 298 | + * The content displayed at right of the text content in the button. |
| 299 | + */ |
| 300 | + rightContent?: IconName | React.ReactNode |
| 301 | + |
| 302 | + /** |
| 303 | + * If `loading` is true, spinner will be shown, replacing the content. |
| 304 | + * |
| 305 | + * @default false |
| 306 | + */ |
| 307 | + loading?: boolean |
| 308 | + |
| 309 | + /** |
| 310 | + * If `active` is true, the button will be styled as if it is hovered. |
| 311 | + * |
| 312 | + * You may want to use this prop for a button which opens dropdown, etc. |
| 313 | + * |
| 314 | + * @default false |
| 315 | + */ |
| 316 | + active?: boolean |
| 317 | + |
| 318 | + /** |
| 319 | + * Whether the button is disabled, and unable to interact. |
| 320 | + * |
| 321 | + * @default false |
| 322 | + */ |
| 323 | + disabled?: boolean |
| 324 | + |
| 325 | + /** |
| 326 | + * The style variant. |
| 327 | + * |
| 328 | + * @default ButtonStyleVariant.Primary |
| 329 | + */ |
| 330 | + styleVariant?: ButtonStyleVariant |
| 331 | + |
| 332 | + /** |
| 333 | + * The color variant. |
| 334 | + * |
| 335 | + * @default ButtonColorVariant.Blue |
| 336 | + */ |
| 337 | + colorVariant?: ButtonColorVariant |
| 338 | + |
| 339 | + /** |
| 340 | + * The size variant. |
| 341 | + * |
| 342 | + * @default ButtonSize.M |
| 343 | + */ |
| 344 | + size?: ButtonSize |
| 345 | + |
| 346 | + /** |
| 347 | + * The handler to be executed when the button is clicked. |
| 348 | + */ |
| 349 | + onClick?: React.MouseEventHandler |
| 350 | + |
| 351 | + /** |
| 352 | + * The handler to be executed when the mouse enters the button. |
| 353 | + */ |
| 354 | + onMouseEnter?: React.MouseEventHandler |
| 355 | + |
| 356 | + /** |
| 357 | + * The handler to be executed when the mouse leaves the button. |
| 358 | + */ |
| 359 | + onMouseLeave?: React.MouseEventHandler |
| 360 | + |
| 361 | + /** |
| 362 | + * The handler to be executed when the button is unfocused. |
| 363 | + */ |
| 364 | + onBlur?: React.MouseEventHandler |
| 365 | +} |
| 366 | +``` |
| 367 | +</details> |
| 368 | + |
| 369 | +## Version |
| 370 | + |
| 371 | +- Available since v0.3.28 |
0 commit comments