diff --git a/.github/workflows/publish-storybook.yml b/.github/workflows/publish-storybook.yml index 0cd1b212..0257e326 100644 --- a/.github/workflows/publish-storybook.yml +++ b/.github/workflows/publish-storybook.yml @@ -2,6 +2,9 @@ name: Publish Storybook # https://www.chromatic.com/docs/github-actions/ on: + push: + branches: + - main workflow_dispatch: inputs: pr_number: diff --git a/src/features/character-management/ui/CharacterItem/index.styles.ts b/src/features/character-management/ui/CharacterItem/index.styles.ts index 42f1096c..d35e000a 100644 --- a/src/features/character-management/ui/CharacterItem/index.styles.ts +++ b/src/features/character-management/ui/CharacterItem/index.styles.ts @@ -28,7 +28,8 @@ export const LockedCharacterCard = styled(CardContainerBase)` padding-bottom: 2.25rem; /* 의도적으로 토큰으로 정의되지 않은 값 사용 */ padding-left: ${getToken('padding.5')}; padding-right: ${getToken('padding.5')}; - border: 1px dashed #d2d4d5; /* HACK: 토큰에 정의되어 있지 않아 임시로 하드코딩함 */ + border: 1px dashed ${getToken('border.normal')}; + opacity: 0.5; `; export const CharacterImage = styled.img` diff --git a/src/features/character-management/ui/CharacterItem/index.tsx b/src/features/character-management/ui/CharacterItem/index.tsx index 5257a6bb..341951ea 100644 --- a/src/features/character-management/ui/CharacterItem/index.tsx +++ b/src/features/character-management/ui/CharacterItem/index.tsx @@ -6,8 +6,8 @@ import * as S from './index.styles'; function LockedCharacterCard() { return ( - {/* HACK : 정의되지 않은 토큰이라 #D2D4D5를 그대로 사용 */} - + {/* HACK: gray.90(#c7c9cb)에 대응하는 아이콘용 semantic 토큰 없음 */} + ); } diff --git a/src/features/character-management/ui/StarChip/StarChip.styles.ts b/src/features/character-management/ui/StarChip/StarChip.styles.ts index 2e507691..479dc7dd 100644 --- a/src/features/character-management/ui/StarChip/StarChip.styles.ts +++ b/src/features/character-management/ui/StarChip/StarChip.styles.ts @@ -18,6 +18,5 @@ export const Star = styled(SvgStar)<{ $active: boolean }>` color: ${({ $active }) => $active ? getToken('fg.accent-yellow.normal') - : // HACK: 비활성 별 색상(구 gray[100]=#d2d4d5)에 대응하는 semantic 토큰이 없어 근사값 사용 - getToken('fg.inverse.neutral')}; + : getToken('fill.alternative')}; `; diff --git a/src/features/expense-management/ui/ExpenseAmountInput/ExpenseAmountInput.tsx b/src/features/expense-management/ui/ExpenseAmountInput/ExpenseAmountInput.tsx index 82ffa042..0aa6b5d5 100644 --- a/src/features/expense-management/ui/ExpenseAmountInput/ExpenseAmountInput.tsx +++ b/src/features/expense-management/ui/ExpenseAmountInput/ExpenseAmountInput.tsx @@ -2,10 +2,10 @@ import { useState } from 'react'; import { ActionArea, BottomSheet, - Button, Input, Keypad, KeyValue, + Chip, PriceDisplay, } from '@/shared/design-system/ui'; import { @@ -86,18 +86,14 @@ function ExpenseAmountInput({ {QUICK_ADD_BUTTONS.map(({ label, amount }) => ( - + /> ))} - + {memberExpenses.map((member) => ( - ` align-items: center; cursor: pointer; color: ${({ $isActive }) => - $isActive - ? getToken('fg.primary.normal') - : getToken( - 'fill.normal-disable' - )}; /* HACK: #ACAFB2로 정의된 토큰이 없어 의미상 유사한 토큰을 임의로 사용함 */ + $isActive ? getToken('fg.primary.normal') : getToken('fg.normal-disable')}; > svg { display: ${({ $isActive }) => ($isActive ? 'block' : 'none')}; } diff --git a/src/pages/expenseDetail/ui/ExpenseTimelineContent/index.tsx b/src/pages/expenseDetail/ui/ExpenseTimelineContent/index.tsx index bd177a4c..182e97df 100644 --- a/src/pages/expenseDetail/ui/ExpenseTimelineContent/index.tsx +++ b/src/pages/expenseDetail/ui/ExpenseTimelineContent/index.tsx @@ -1,4 +1,4 @@ -import { NameChip } from '@/shared/design-system/ui'; +import { Chip } from '@/shared/design-system/ui'; import { ExpenseDetail } from '@/entities/expense/model/expense.type'; import * as S from './index.styles'; @@ -21,7 +21,7 @@ function ExpenseTimelineContent({ expense }: ExpenseTimelineContentProps) { {expense.groupMembers.map((name) => ( - + ))} diff --git a/src/shared/design-system/ui/Button/Button.stories.tsx b/src/shared/design-system/ui/Button/Button.stories.tsx index d37bfd30..1e0fdeda 100644 --- a/src/shared/design-system/ui/Button/Button.stories.tsx +++ b/src/shared/design-system/ui/Button/Button.stories.tsx @@ -43,35 +43,28 @@ type Story = StoryObj; export const Default: Story = {}; +const gridStyle = { + display: 'grid', + gridTemplateColumns: '3rem repeat(6, auto)', + gap: 8, + alignItems: 'center', + justifyItems: 'start', +} as const; + export const Showcase: Story = { render: () => (
-
+
{VARIANTS.map((variant) => ( - + {variant} ))} - disabled + disabled
{SIZES.map((size) => ( -
+
{size} {VARIANTS.map((variant) => (
), }; - -export const Disabled: Story = { - render: () => ( -
- {VARIANTS.map((variant) => ( - - ))} -
- ), -}; diff --git a/src/shared/design-system/ui/Button/Button.styles.ts b/src/shared/design-system/ui/Button/Button.styles.ts index 174d999c..39e3bd90 100644 --- a/src/shared/design-system/ui/Button/Button.styles.ts +++ b/src/shared/design-system/ui/Button/Button.styles.ts @@ -10,15 +10,18 @@ const sizeStyles = { medium: css` padding: ${getToken('padding.4')} ${getToken('padding.5')}; ${applyTypography('typography.body.medium-semibold')} + border-radius: ${getToken('radius.lg')}; `, small: css` padding: ${getToken('padding.3')} ${getToken('padding.5')}; ${applyTypography('typography.body.small-semibold')} + border-radius: ${getToken('radius.lg')}; `, xsmall: css` height: 1.75rem; padding: 0 ${getToken('padding.4')}; ${applyTypography('typography.caption.small-medium')} + border-radius: ${getToken('radius.md')}; `, }; @@ -64,7 +67,6 @@ export const Button = styled.button` justify-content: center; align-items: center; gap: ${getToken('gap.2')}; - border-radius: ${getToken('radius.full')}; border: none; cursor: pointer; white-space: nowrap; diff --git a/src/shared/design-system/ui/NameChip/NameChip.stories.tsx b/src/shared/design-system/ui/Chip/Chip.stories.tsx similarity index 54% rename from src/shared/design-system/ui/NameChip/NameChip.stories.tsx rename to src/shared/design-system/ui/Chip/Chip.stories.tsx index 4bfba40d..8406da67 100644 --- a/src/shared/design-system/ui/NameChip/NameChip.stories.tsx +++ b/src/shared/design-system/ui/Chip/Chip.stories.tsx @@ -1,19 +1,19 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { NameChip } from './NameChip'; -import type { NameChipVariant, NameChipSize } from './NameChip'; +import { Chip } from './Chip'; +import type { ChipVariant, ChipSize } from './Chip'; -const VARIANTS: NameChipVariant[] = [ +const VARIANTS: ChipVariant[] = [ 'selected', 'unselected', 'disabled', 'red', 'black', ]; -const SIZES: NameChipSize[] = ['m', 's']; +const SIZES: ChipSize[] = ['m', 's']; -const meta: Meta = { - title: 'Components/NameChip', - component: NameChip, +const meta: Meta = { + title: 'Components/Chip', + component: Chip, tags: ['autodocs'], argTypes: { variant: { @@ -36,7 +36,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; @@ -50,11 +50,30 @@ export const Showcase: Story = { > {size} {VARIANTS.map((variant) => ( - + ))} +
+ ))} +
+ ), +}; + +export const AsButton: Story = { + render: () => ( +
+ {SIZES.map((size) => ( +
+ {size} + {VARIANTS.map((variant) => ( + alert(`${variant} 클릭`)} /> ))}
diff --git a/src/shared/design-system/ui/NameChip/NameChip.styles.ts b/src/shared/design-system/ui/Chip/Chip.styles.ts similarity index 89% rename from src/shared/design-system/ui/NameChip/NameChip.styles.ts rename to src/shared/design-system/ui/Chip/Chip.styles.ts index e53be736..48d87dc4 100644 --- a/src/shared/design-system/ui/NameChip/NameChip.styles.ts +++ b/src/shared/design-system/ui/Chip/Chip.styles.ts @@ -5,9 +5,10 @@ import { applyTypography, } from '@/shared/design-system'; -interface StyledNameChipProps { +interface StyledChipProps { $variant: 'selected' | 'unselected' | 'disabled' | 'red' | 'black'; $size: 'm' | 's'; + $clickable?: boolean; } // HACK: s size는 12px Medium이지만 해당 semantic token 없음. @@ -52,13 +53,19 @@ const variantStyles = { `, }; -export const Chip = styled.div` +export const ChipRoot = styled.div` display: inline-flex; align-items: center; justify-content: center; gap: ${getToken('gap.1')}; border-radius: ${getToken('radius.full')}; white-space: nowrap; + ${({ $clickable }) => + $clickable && + css` + border: none; + cursor: pointer; + `} ${({ $size }) => sizeStyles[$size]} ${({ $variant }) => variantStyles[$variant]} `; diff --git a/src/shared/design-system/ui/Chip/Chip.tsx b/src/shared/design-system/ui/Chip/Chip.tsx new file mode 100644 index 00000000..ccda80c0 --- /dev/null +++ b/src/shared/design-system/ui/Chip/Chip.tsx @@ -0,0 +1,31 @@ +import type { MouseEventHandler } from 'react'; +import * as S from './Chip.styles'; + +type ChipVariant = 'selected' | 'unselected' | 'disabled' | 'red' | 'black'; +type ChipSize = 'm' | 's'; + +interface ChipProps { + label: string; + variant?: ChipVariant; + size?: ChipSize; + onClick?: MouseEventHandler; +} + +function Chip(props: ChipProps) { + const { label, variant = 'selected', size = 'm', onClick } = props; + + return ( + + {label} + + ); +} + +export { Chip }; +export type { ChipProps, ChipVariant, ChipSize }; diff --git a/src/shared/design-system/ui/Chip/index.ts b/src/shared/design-system/ui/Chip/index.ts new file mode 100644 index 00000000..f8c964b4 --- /dev/null +++ b/src/shared/design-system/ui/Chip/index.ts @@ -0,0 +1,2 @@ +export { Chip } from './Chip'; +export type { ChipProps, ChipVariant, ChipSize } from './Chip'; diff --git a/src/shared/design-system/ui/Input/Input.stories.tsx b/src/shared/design-system/ui/Input/Input.stories.tsx index 1c28cfed..ed3cc2fb 100644 --- a/src/shared/design-system/ui/Input/Input.stories.tsx +++ b/src/shared/design-system/ui/Input/Input.stories.tsx @@ -15,6 +15,7 @@ const meta: Meta = { label: { control: 'text' }, required: { control: 'boolean' }, placeholder: { control: 'text' }, + helpText: { control: 'text' }, value: { control: 'text' }, }, args: { @@ -56,6 +57,25 @@ export const Showcase: Story = { ), }; +export const WithHelpText: Story = { + render: () => ( +
+ {STATES.map((state) => ( + + ))} +
+ ), +}; + export const PriceVariant: Story = { render: () => (
` + ${applyTypography('typography.caption.xsmall')} + font-weight: ${getTypographyToken('typography.caption.small-medium') + .fontWeight}; + color: ${({ $state }) => + $state === 'error' ? getToken('fg.accent-red.normal') : '#444950'}; +`; diff --git a/src/shared/design-system/ui/Input/Input.tsx b/src/shared/design-system/ui/Input/Input.tsx index 225e201d..466112ad 100644 --- a/src/shared/design-system/ui/Input/Input.tsx +++ b/src/shared/design-system/ui/Input/Input.tsx @@ -1,5 +1,6 @@ import { useId, forwardRef } from 'react'; import type { ReactNode, ChangeEventHandler, InputHTMLAttributes } from 'react'; +import SvgSystemDanger from '@/shared/assets/svgs/icon/SystemDanger'; import * as S from './Input.styles'; type InputState = 'default' | 'error' | 'disabled'; @@ -11,6 +12,7 @@ interface InputProps state?: InputState; variant?: InputVariant; trailingIcon?: ReactNode; + helpText?: string; value?: string; onChange?: ChangeEventHandler; } @@ -24,6 +26,7 @@ const Input = forwardRef(function Input( state = 'default', variant = 'default', trailingIcon, + helpText, value, onChange, onClick, @@ -67,7 +70,16 @@ const Input = forwardRef(function Input( )} - {/* TODO: 헬프텍스트 - 디자인 확정 후 추가 예정 */} + {helpText && variant === 'default' && ( + + {state === 'error' && ( + + + + )} + {helpText} + + )} ); }); diff --git a/src/shared/design-system/ui/NameChip/NameChip.tsx b/src/shared/design-system/ui/NameChip/NameChip.tsx deleted file mode 100644 index 43d46248..00000000 --- a/src/shared/design-system/ui/NameChip/NameChip.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as S from './NameChip.styles'; - -// HACK : ExpenseTimelineContent 에서 사용하는 NameChip이 정의되어 있지 않아서 임의로 black variant를 추가함. -type NameChipVariant = 'selected' | 'unselected' | 'disabled' | 'red' | 'black'; -type NameChipSize = 'm' | 's'; - -interface NameChipProps { - label: string; - variant?: NameChipVariant; - size?: NameChipSize; -} - -function NameChip(props: NameChipProps) { - const { label, variant = 'selected', size = 'm' } = props; - - return ( - - {label} - - ); -} - -export { NameChip }; -export type { NameChipProps, NameChipVariant, NameChipSize }; diff --git a/src/shared/design-system/ui/NameChip/index.ts b/src/shared/design-system/ui/NameChip/index.ts deleted file mode 100644 index 16086b1f..00000000 --- a/src/shared/design-system/ui/NameChip/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { NameChip } from './NameChip'; -export type { NameChipProps, NameChipVariant, NameChipSize } from './NameChip'; diff --git a/src/shared/design-system/ui/index.ts b/src/shared/design-system/ui/index.ts index 5cf32d89..017348c2 100644 --- a/src/shared/design-system/ui/index.ts +++ b/src/shared/design-system/ui/index.ts @@ -19,7 +19,8 @@ export { Keypad } from './Keypad'; export type { KeyValue } from './Keypad'; export { Modal } from './Modal'; export type { ModalProps } from './Modal'; -export { NameChip } from './NameChip'; +export { Chip } from './Chip'; +export type { ChipProps, ChipVariant, ChipSize } from './Chip'; export { PaidChip } from './PaidChip'; export type { PaidChipStatus } from './PaidChip'; export type { HeaderProps } from './Header';