diff --git a/frontend/src/components/brand/mongle-icon.stories.tsx b/frontend/src/components/brand/mongle-icon.stories.tsx index 4593d56..e50125a 100644 --- a/frontend/src/components/brand/mongle-icon.stories.tsx +++ b/frontend/src/components/brand/mongle-icon.stories.tsx @@ -1,4 +1,4 @@ -import { MongleIcon } from '@/components/brand/mongle-logo' +import { MongleIcon } from '@/components/brand/mongle-icon' import type { Meta, StoryObj } from '@storybook/react-vite' const meta = { diff --git a/frontend/src/components/brand/mongle-icon.tsx b/frontend/src/components/brand/mongle-icon.tsx new file mode 100644 index 0000000..3d947ab --- /dev/null +++ b/frontend/src/components/brand/mongle-icon.tsx @@ -0,0 +1,15 @@ +import { cn } from '@/lib/utils' + +export function MongleIcon({ className }: { className?: string }) { + return ( + + + + + ) +} diff --git a/frontend/src/components/brand/mongle-logo.tsx b/frontend/src/components/brand/mongle-logo.tsx index eccc52b..f7938d5 100644 --- a/frontend/src/components/brand/mongle-logo.tsx +++ b/frontend/src/components/brand/mongle-logo.tsx @@ -1,19 +1,6 @@ +import { MongleIcon } from '@/components/brand/mongle-icon' import { cn } from '@/lib/utils' -export function MongleIcon({ className }: { className?: string }) { - return ( - - - - - ) -} - export function MongleLogo({ className, iconClassName, diff --git a/frontend/src/components/person/field-label.stories.tsx b/frontend/src/components/person/field-label.stories.tsx new file mode 100644 index 0000000..eb49e6e --- /dev/null +++ b/frontend/src/components/person/field-label.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { FieldLabel } from '@/components/person/field-label' + +const meta = { + title: 'Person/FieldLabel', + component: FieldLabel, + tags: ['autodocs'], + args: { + children: '만남 태그', + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} diff --git a/frontend/src/components/person/field-label.tsx b/frontend/src/components/person/field-label.tsx new file mode 100644 index 0000000..4e82ff5 --- /dev/null +++ b/frontend/src/components/person/field-label.tsx @@ -0,0 +1,15 @@ +import type { ComponentProps } from 'react' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' + +export function FieldLabel({ + className, + children, + ...props +}: ComponentProps) { + return ( + + ) +} diff --git a/frontend/src/components/person/person-fields.stories.tsx b/frontend/src/components/person/list-field.stories.tsx similarity index 64% rename from frontend/src/components/person/person-fields.stories.tsx rename to frontend/src/components/person/list-field.stories.tsx index db0f52d..f76250f 100644 --- a/frontend/src/components/person/person-fields.stories.tsx +++ b/frontend/src/components/person/list-field.stories.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import type { ComponentProps } from 'react' import type { Meta, StoryObj } from '@storybook/react-vite' -import { ListField, RelationTypeField } from '@/components/person/person-fields' +import { ListField } from '@/components/person/list-field' function ListFieldDemo(args: ComponentProps) { const [items, setItems] = useState(args.items) @@ -12,19 +12,8 @@ function ListFieldDemo(args: ComponentProps) { ) } -function RelationTypeDemo({ initial = '' }: { initial?: string }) { - const [value, setValue] = useState(initial) - return ( -
- -
- ) -} - -// 한 파일에 ListField/RelationTypeField 두 컴포넌트를 담는다(원본이 person-fields.tsx 한 파일). -// meta.component은 ListField 기준이고, RelationType 스토리는 render로 직접 그린다. const meta = { - title: 'Person/PersonFields', + title: 'Person/ListField', component: ListField, tags: ['autodocs'], args: { @@ -62,11 +51,3 @@ export const ListFieldCompact: Story = { export const ListFieldMaxItems: Story = { args: { maxItems: 3, items: ['하나', '둘', '셋'] }, } - -export const RelationTypeDefault: Story = { - render: () => , -} - -export const RelationTypeSelected: Story = { - render: () => , -} diff --git a/frontend/src/components/person/person-fields.tsx b/frontend/src/components/person/list-field.tsx similarity index 72% rename from frontend/src/components/person/person-fields.tsx rename to frontend/src/components/person/list-field.tsx index 91d092a..b9d33cb 100644 --- a/frontend/src/components/person/person-fields.tsx +++ b/frontend/src/components/person/list-field.tsx @@ -1,25 +1,10 @@ -import type { ComponentProps } from 'react' import { useState } from 'react' import { X } from 'lucide-react' -import { Label } from '@/components/ui/label' import { Button } from '@/components/ui/button' -import { ListGroupInset } from '@/components/ui/list-group' -import { TagChip } from '@/components/ui/tag-chip' +import { FieldLabel } from '@/components/person/field-label' import { isImeComposing } from '@/lib/keyboard' import { cn } from '@/lib/utils' -function FieldLabel({ - className, - children, - ...props -}: ComponentProps) { - return ( - - ) -} - export function ListField({ label, items, @@ -140,55 +125,3 @@ export function ListField({ ) } - -export const RELATION_TYPE_SUGGESTIONS = [ - '친구', - '회사 동료', - '가족', - '연인', - '스터디원', - '이웃', - '지인', -] as const - -export function RelationTypeField({ - value, - onChange, - inset = false, - hideLabel = false, -}: { - value: string - onChange: (value: string) => void - inset?: boolean - hideLabel?: boolean -}) { - const chips = ( -
- {RELATION_TYPE_SUGGESTIONS.map((suggestion) => ( - { - onChange(value === suggestion ? '' : suggestion) - }} - > - {suggestion} - - ))} -
- ) - - return ( -
- {hideLabel ? null : 만남 태그} - {inset ? ( - {chips} - ) : ( - chips - )} -
- ) -} diff --git a/frontend/src/components/person/person-edit-form.tsx b/frontend/src/components/person/person-edit-form.tsx index 54950bc..53c0e3d 100644 --- a/frontend/src/components/person/person-edit-form.tsx +++ b/frontend/src/components/person/person-edit-form.tsx @@ -1,7 +1,8 @@ import { useRef, useState } from 'react' import type { FormEvent } from 'react' import { DateWheel } from '@/components/person/date-wheel' -import { ListField, RelationTypeField } from '@/components/person/person-fields' +import { ListField } from '@/components/person/list-field' +import { RelationTypeField } from '@/components/person/relation-type-field' import { GENDER_OPTIONS, ProfileHero, diff --git a/frontend/src/components/person/person-tab-nav.tsx b/frontend/src/components/person/person-tab-nav.tsx index 244709c..606f9ec 100644 --- a/frontend/src/components/person/person-tab-nav.tsx +++ b/frontend/src/components/person/person-tab-nav.tsx @@ -1,4 +1,4 @@ -import { ListGroupInset } from '@/components/ui/list-group' +import { ListGroupInset } from '@/components/ui/list-group-inset' import { SegmentedControl } from '@/components/ui/segmented-control' import type { PersonView } from '@/stackflow/stackflow.config' diff --git a/frontend/src/components/person/relation-type-field.stories.tsx b/frontend/src/components/person/relation-type-field.stories.tsx new file mode 100644 index 0000000..ccb18e7 --- /dev/null +++ b/frontend/src/components/person/relation-type-field.stories.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import type { Meta, StoryObj } from '@storybook/react-vite' +import { RelationTypeField } from '@/components/person/relation-type-field' + +function RelationTypeDemo({ initial = '' }: { initial?: string }) { + const [value, setValue] = useState(initial) + return ( +
+ +
+ ) +} + +const meta = { + title: 'Person/RelationTypeField', + component: RelationTypeField, + tags: ['autodocs'], + args: { + value: '', + onChange: () => {}, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const RelationTypeDefault: Story = { + render: () => , +} + +export const RelationTypeSelected: Story = { + render: () => , +} diff --git a/frontend/src/components/person/relation-type-field.tsx b/frontend/src/components/person/relation-type-field.tsx new file mode 100644 index 0000000..38dee9d --- /dev/null +++ b/frontend/src/components/person/relation-type-field.tsx @@ -0,0 +1,56 @@ +import { FieldLabel } from '@/components/person/field-label' +import { ListGroupInset } from '@/components/ui/list-group-inset' +import { TagChip } from '@/components/ui/tag-chip' +import { cn } from '@/lib/utils' + +export const RELATION_TYPE_SUGGESTIONS = [ + '친구', + '회사 동료', + '가족', + '연인', + '스터디원', + '이웃', + '지인', +] as const + +export function RelationTypeField({ + value, + onChange, + inset = false, + hideLabel = false, +}: { + value: string + onChange: (value: string) => void + inset?: boolean + hideLabel?: boolean +}) { + const chips = ( +
+ {RELATION_TYPE_SUGGESTIONS.map((suggestion) => ( + { + onChange(value === suggestion ? '' : suggestion) + }} + > + {suggestion} + + ))} +
+ ) + + return ( +
+ {hideLabel ? null : 만남 태그} + {inset ? ( + {chips} + ) : ( + chips + )} +
+ ) +} diff --git a/frontend/src/components/settings/settings-navigation-item.tsx b/frontend/src/components/settings/settings-navigation-item.tsx index feacf4a..438b9c0 100644 --- a/frontend/src/components/settings/settings-navigation-item.tsx +++ b/frontend/src/components/settings/settings-navigation-item.tsx @@ -1,5 +1,5 @@ import { ChevronRight } from 'lucide-react' -import { ListGroupItem } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' export function SettingsNavigationItem({ label, diff --git a/frontend/src/components/timeline/timeline-filters.stories.tsx b/frontend/src/components/timeline/timeline-category-filters.stories.tsx similarity index 61% rename from frontend/src/components/timeline/timeline-filters.stories.tsx rename to frontend/src/components/timeline/timeline-category-filters.stories.tsx index 44df921..b6a3dbb 100644 --- a/frontend/src/components/timeline/timeline-filters.stories.tsx +++ b/frontend/src/components/timeline/timeline-category-filters.stories.tsx @@ -1,14 +1,7 @@ import { useState } from 'react' import type { Meta, StoryObj } from '@storybook/react-vite' -import type { - ChipResponse, - PersonResponse, -} from '@/apis/generated/mongle-api.schemas' -import { - TimelineCategoryFilters, - TimelineFilterReset, - TimelinePersonFilters, -} from '@/components/timeline/timeline-filters' +import type { ChipResponse } from '@/apis/generated/mongle-api.schemas' +import { TimelineCategoryFilters } from '@/components/timeline/timeline-category-filters' const CATEGORY_CHIPS: ChipResponse[] = [ { @@ -45,42 +38,11 @@ const CATEGORY_CHIPS: ChipResponse[] = [ }, ] -const PERSONS: PersonResponse[] = [ - { - id: 1, - name: '엄마', - gender: 'FEMALE', - favorite: true, - relationTags: [], - likes: [], - cautions: [], - }, - { - id: 2, - name: '김민수', - gender: 'MALE', - profileImageUrl: 'https://picsum.photos/seed/minsu/200', - favorite: false, - relationTags: [], - likes: [], - cautions: [], - }, - { - id: 3, - name: '이지은', - gender: 'FEMALE', - favorite: false, - relationTags: [], - likes: [], - cautions: [], - }, -] - const toggle = (ids: number[], id: number) => ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id] const meta = { - title: 'Timeline/Filters', + title: 'Timeline/CategoryFilters', component: TimelineCategoryFilters, tags: ['autodocs'], // 각 스토리는 render로 상태를 직접 관리한다. 여기 args는 필수 prop 타입만 채운다. @@ -135,24 +97,3 @@ export const AllSelected: Story = { ) }, } - -export const Persons: Story = { - render: () => { - const [selected, setSelected] = useState([1]) - return ( - setSelected((prev) => toggle(prev, id))} - /> - ) - }, -} - -export const ResetVisible: Story = { - render: () => {}} />, -} - -export const ResetHidden: Story = { - render: () => {}} />, -} diff --git a/frontend/src/components/timeline/timeline-category-filters.tsx b/frontend/src/components/timeline/timeline-category-filters.tsx new file mode 100644 index 0000000..fde1eca --- /dev/null +++ b/frontend/src/components/timeline/timeline-category-filters.tsx @@ -0,0 +1,39 @@ +import type { ChipResponse } from '@/apis/generated/mongle-api.schemas' +import { TagChip } from '@/components/ui/tag-chip' + +export function TimelineCategoryFilters({ + chips, + selectedIds, + onToggle, +}: { + chips: ChipResponse[] + selectedIds: number[] + onToggle: (chipId: number) => void +}) { + if (chips.length === 0) return null + + return ( +
+

+ 카테고리 +

+
+ {chips.map((chip) => { + const selected = selectedIds.includes(chip.id) + return ( + onToggle(chip.id)} + data-amp-mask={chip.personal || undefined} + > + {chip.label} + + ) + })} +
+
+ ) +} diff --git a/frontend/src/components/timeline/timeline-filter-drawer.tsx b/frontend/src/components/timeline/timeline-filter-drawer.tsx index dc8ee2f..e0c50ba 100644 --- a/frontend/src/components/timeline/timeline-filter-drawer.tsx +++ b/frontend/src/components/timeline/timeline-filter-drawer.tsx @@ -3,11 +3,9 @@ import type { ChipResponse, PersonResponse, } from '@/apis/generated/mongle-api.schemas' -import { - TimelineCategoryFilters, - TimelineFilterReset, - TimelinePersonFilters, -} from '@/components/timeline/timeline-filters' +import { TimelineCategoryFilters } from '@/components/timeline/timeline-category-filters' +import { TimelineFilterReset } from '@/components/timeline/timeline-filter-reset' +import { TimelinePersonFilters } from '@/components/timeline/timeline-person-filters' import { Button } from '@/components/ui/button' import { Drawer, diff --git a/frontend/src/components/timeline/timeline-filter-reset.stories.tsx b/frontend/src/components/timeline/timeline-filter-reset.stories.tsx new file mode 100644 index 0000000..1e72708 --- /dev/null +++ b/frontend/src/components/timeline/timeline-filter-reset.stories.tsx @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { TimelineFilterReset } from '@/components/timeline/timeline-filter-reset' + +const meta = { + title: 'Timeline/FilterReset', + component: TimelineFilterReset, + tags: ['autodocs'], + args: { + visible: true, + onReset: () => {}, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const ResetVisible: Story = { + render: () => {}} />, +} + +export const ResetHidden: Story = { + render: () => {}} />, +} diff --git a/frontend/src/components/timeline/timeline-filter-reset.tsx b/frontend/src/components/timeline/timeline-filter-reset.tsx new file mode 100644 index 0000000..6cfbf8d --- /dev/null +++ b/frontend/src/components/timeline/timeline-filter-reset.tsx @@ -0,0 +1,19 @@ +export function TimelineFilterReset({ + visible, + onReset, +}: { + visible: boolean + onReset: () => void +}) { + if (!visible) return null + + return ( + + ) +} diff --git a/frontend/src/components/timeline/timeline-filters.test.tsx b/frontend/src/components/timeline/timeline-filters.test.tsx index ed94194..564fc1f 100644 --- a/frontend/src/components/timeline/timeline-filters.test.tsx +++ b/frontend/src/components/timeline/timeline-filters.test.tsx @@ -1,9 +1,7 @@ import { cleanup, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it } from 'vitest' -import { - TimelineCategoryFilters, - TimelinePersonFilters, -} from './timeline-filters' +import { TimelineCategoryFilters } from './timeline-category-filters' +import { TimelinePersonFilters } from './timeline-person-filters' import type { ChipResponse, PersonResponse, diff --git a/frontend/src/components/timeline/timeline-person-filters.stories.tsx b/frontend/src/components/timeline/timeline-person-filters.stories.tsx new file mode 100644 index 0000000..28bcc77 --- /dev/null +++ b/frontend/src/components/timeline/timeline-person-filters.stories.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react' +import type { Meta, StoryObj } from '@storybook/react-vite' +import type { PersonResponse } from '@/apis/generated/mongle-api.schemas' +import { TimelinePersonFilters } from '@/components/timeline/timeline-person-filters' + +const PERSONS: PersonResponse[] = [ + { + id: 1, + name: '엄마', + gender: 'FEMALE', + favorite: true, + relationTags: [], + likes: [], + cautions: [], + }, + { + id: 2, + name: '김민수', + gender: 'MALE', + profileImageUrl: 'https://picsum.photos/seed/minsu/200', + favorite: false, + relationTags: [], + likes: [], + cautions: [], + }, + { + id: 3, + name: '이지은', + gender: 'FEMALE', + favorite: false, + relationTags: [], + likes: [], + cautions: [], + }, +] + +const toggle = (ids: number[], id: number) => + ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id] + +const meta = { + title: 'Timeline/PersonFilters', + component: TimelinePersonFilters, + tags: ['autodocs'], + // 각 스토리는 render로 상태를 직접 관리한다. 여기 args는 필수 prop 타입만 채운다. + args: { + persons: PERSONS, + selectedIds: [], + onToggle: () => {}, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = { + render: () => { + const [selected, setSelected] = useState([1]) + return ( + setSelected((prev) => toggle(prev, id))} + /> + ) + }, +} diff --git a/frontend/src/components/timeline/timeline-filters.tsx b/frontend/src/components/timeline/timeline-person-filters.tsx similarity index 50% rename from frontend/src/components/timeline/timeline-filters.tsx rename to frontend/src/components/timeline/timeline-person-filters.tsx index 006ec28..a98cbe1 100644 --- a/frontend/src/components/timeline/timeline-filters.tsx +++ b/frontend/src/components/timeline/timeline-person-filters.tsx @@ -1,49 +1,9 @@ -import type { - ChipResponse, - PersonResponse, -} from '@/apis/generated/mongle-api.schemas' +import type { PersonResponse } from '@/apis/generated/mongle-api.schemas' import { MonogramAvatar } from '@/components/ui/monogram-avatar' import { TagChip } from '@/components/ui/tag-chip' import { formatPersonName } from '@/lib/format' import { cn } from '@/lib/utils' -export function TimelineCategoryFilters({ - chips, - selectedIds, - onToggle, -}: { - chips: ChipResponse[] - selectedIds: number[] - onToggle: (chipId: number) => void -}) { - if (chips.length === 0) return null - - return ( -
-

- 카테고리 -

-
- {chips.map((chip) => { - const selected = selectedIds.includes(chip.id) - return ( - onToggle(chip.id)} - data-amp-mask={chip.personal || undefined} - > - {chip.label} - - ) - })} -
-
- ) -} - export function TimelinePersonFilters({ persons, selectedIds, @@ -88,23 +48,3 @@ export function TimelinePersonFilters({ ) } - -export function TimelineFilterReset({ - visible, - onReset, -}: { - visible: boolean - onReset: () => void -}) { - if (!visible) return null - - return ( - - ) -} diff --git a/frontend/src/components/ui/list-group-footer.stories.tsx b/frontend/src/components/ui/list-group-footer.stories.tsx new file mode 100644 index 0000000..f26cda7 --- /dev/null +++ b/frontend/src/components/ui/list-group-footer.stories.tsx @@ -0,0 +1,34 @@ +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupFooter } from '@/components/ui/list-group-footer' +import { ListGroupItem } from '@/components/ui/list-group-item' +import type { Meta, StoryObj } from '@storybook/react-vite' + +const meta = { + title: 'UI/ListGroupFooter', + component: ListGroupFooter, + tags: ['autodocs'], + args: { + children: '자주 연락하는 사람일수록 위쪽에 정렬됩니다.', + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} + +export const BelowGroup: Story = { + render: () => ( +
+ + 엄마 + 아빠 + 동생 + + + 자주 연락하는 사람일수록 위쪽에 정렬됩니다. + +
+ ), +} diff --git a/frontend/src/components/ui/list-group-footer.tsx b/frontend/src/components/ui/list-group-footer.tsx new file mode 100644 index 0000000..c19d91f --- /dev/null +++ b/frontend/src/components/ui/list-group-footer.tsx @@ -0,0 +1,7 @@ +export function ListGroupFooter({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ) +} diff --git a/frontend/src/components/ui/list-group-inset.stories.tsx b/frontend/src/components/ui/list-group-inset.stories.tsx new file mode 100644 index 0000000..3a11721 --- /dev/null +++ b/frontend/src/components/ui/list-group-inset.stories.tsx @@ -0,0 +1,37 @@ +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupInset } from '@/components/ui/list-group-inset' +import { ListGroupItem } from '@/components/ui/list-group-item' +import type { Meta, StoryObj } from '@storybook/react-vite' + +const meta = { + title: 'UI/ListGroupInset', + component: ListGroupInset, + tags: ['autodocs'], + args: { + children: ( +

+ 오랜만에 만났다. 요즘 이직 준비 중이라고 했다. +

+ ), + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} + +export const InListGroup: Story = { + render: () => ( + + + +

+ 오랜만에 만났다. 요즘 이직 준비 중이라고 했다. +

+
+
+
+ ), +} diff --git a/frontend/src/components/ui/list-group-inset.tsx b/frontend/src/components/ui/list-group-inset.tsx new file mode 100644 index 0000000..dadcddf --- /dev/null +++ b/frontend/src/components/ui/list-group-inset.tsx @@ -0,0 +1,20 @@ +import { cn } from '@/lib/utils' + +export function ListGroupInset({ + children, + className, +}: { + children: React.ReactNode + className?: string +}) { + return ( +
+ {children} +
+ ) +} diff --git a/frontend/src/components/ui/list-group-item.stories.tsx b/frontend/src/components/ui/list-group-item.stories.tsx new file mode 100644 index 0000000..b152f33 --- /dev/null +++ b/frontend/src/components/ui/list-group-item.stories.tsx @@ -0,0 +1,36 @@ +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' +import type { Meta, StoryObj } from '@storybook/react-vite' + +const meta = { + title: 'UI/ListGroupItem', + component: ListGroupItem, + tags: ['autodocs'], + args: { + children: '엄마', + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = { + render: () => ( + + 엄마 + 김민수 + 이서연 + + ), +} + +export const WithoutDivider: Story = { + render: () => ( + + + 구분선이 없는 마지막 항목 + + + ), +} diff --git a/frontend/src/components/ui/list-group-item.tsx b/frontend/src/components/ui/list-group-item.tsx new file mode 100644 index 0000000..758bd40 --- /dev/null +++ b/frontend/src/components/ui/list-group-item.tsx @@ -0,0 +1,23 @@ +import { cn } from '@/lib/utils' + +export function ListGroupItem({ + children, + className, + withDivider = true, +}: { + children: React.ReactNode + className?: string + withDivider?: boolean +}) { + return ( +
+ {children} +
+ ) +} diff --git a/frontend/src/components/ui/list-group-label.stories.tsx b/frontend/src/components/ui/list-group-label.stories.tsx new file mode 100644 index 0000000..544730a --- /dev/null +++ b/frontend/src/components/ui/list-group-label.stories.tsx @@ -0,0 +1,32 @@ +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' +import { ListGroupLabel } from '@/components/ui/list-group-label' +import type { Meta, StoryObj } from '@storybook/react-vite' + +const meta = { + title: 'UI/ListGroupLabel', + component: ListGroupLabel, + tags: ['autodocs'], + args: { + children: '가족', + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} + +export const AboveGroup: Story = { + render: () => ( +
+ 가족 + + 엄마 + 아빠 + 동생 + +
+ ), +} diff --git a/frontend/src/components/ui/list-group-label.tsx b/frontend/src/components/ui/list-group-label.tsx new file mode 100644 index 0000000..98eb685 --- /dev/null +++ b/frontend/src/components/ui/list-group-label.tsx @@ -0,0 +1,7 @@ +export function ListGroupLabel({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ) +} diff --git a/frontend/src/components/ui/list-group.stories.tsx b/frontend/src/components/ui/list-group.stories.tsx index 685e99f..df61b95 100644 --- a/frontend/src/components/ui/list-group.stories.tsx +++ b/frontend/src/components/ui/list-group.stories.tsx @@ -1,10 +1,5 @@ -import { - ListGroup, - ListGroupFooter, - ListGroupInset, - ListGroupItem, - ListGroupLabel, -} from '@/components/ui/list-group' +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' import type { Meta, StoryObj } from '@storybook/react-vite' const meta = { @@ -29,33 +24,3 @@ export const Default: Story = { ), } - -export const WithLabelAndFooter: Story = { - render: () => ( -
- 가족 - - 엄마 - 아빠 - 동생 - - - 자주 연락하는 사람일수록 위쪽에 정렬됩니다. - -
- ), -} - -export const Inset: Story = { - render: () => ( - - - -

- 오랜만에 만났다. 요즘 이직 준비 중이라고 했다. -

-
-
-
- ), -} diff --git a/frontend/src/components/ui/list-group.tsx b/frontend/src/components/ui/list-group.tsx index c969380..50acf36 100644 --- a/frontend/src/components/ui/list-group.tsx +++ b/frontend/src/components/ui/list-group.tsx @@ -1,13 +1,5 @@ import { cn } from '@/lib/utils' -export function ListGroupLabel({ children }: { children: React.ReactNode }) { - return ( -

- {children} -

- ) -} - export function ListGroup({ children, className, @@ -26,52 +18,3 @@ export function ListGroup({ ) } - -export function ListGroupItem({ - children, - className, - withDivider = true, -}: { - children: React.ReactNode - className?: string - withDivider?: boolean -}) { - return ( -
- {children} -
- ) -} - -export function ListGroupFooter({ children }: { children: React.ReactNode }) { - return ( -

- {children} -

- ) -} - -export function ListGroupInset({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) { - return ( -
- {children} -
- ) -} diff --git a/frontend/src/stackflow/activities/person-new-activity.tsx b/frontend/src/stackflow/activities/person-new-activity.tsx index 75e1511..9508ad0 100644 --- a/frontend/src/stackflow/activities/person-new-activity.tsx +++ b/frontend/src/stackflow/activities/person-new-activity.tsx @@ -6,7 +6,8 @@ import { AnimatePresence, motion, useReducedMotion } from 'motion/react' import { ChevronLeft, Save } from 'lucide-react' import { useRef, useState } from 'react' import { DateWheel } from '@/components/person/date-wheel' -import { ListField, RelationTypeField } from '@/components/person/person-fields' +import { ListField } from '@/components/person/list-field' +import { RelationTypeField } from '@/components/person/relation-type-field' import { GENDER_OPTIONS, ProfileHero, diff --git a/frontend/src/stackflow/activities/person/person-profile-view.tsx b/frontend/src/stackflow/activities/person/person-profile-view.tsx index db87d17..5ce9ac1 100644 --- a/frontend/src/stackflow/activities/person/person-profile-view.tsx +++ b/frontend/src/stackflow/activities/person/person-profile-view.tsx @@ -6,11 +6,9 @@ import { eventQuery, personQuery } from '@/apis/queries' import { DeletePersonConfirm } from '@/components/person/delete-person-confirm' import { usePersonDelete } from '@/components/person/use-person-delete' import { MonogramAvatar } from '@/components/ui/monogram-avatar' -import { - ListGroup, - ListGroupItem, - ListGroupLabel, -} from '@/components/ui/list-group' +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' +import { ListGroupLabel } from '@/components/ui/list-group-label' import { StatusMessage } from '@/components/ui/status-message' import { TagChip } from '@/components/ui/tag-chip' import { optimizedImageUrl } from '@/lib/image-url' diff --git a/frontend/src/stackflow/activities/person/person-timeline-view.tsx b/frontend/src/stackflow/activities/person/person-timeline-view.tsx index ffa2076..ed5f61e 100644 --- a/frontend/src/stackflow/activities/person/person-timeline-view.tsx +++ b/frontend/src/stackflow/activities/person/person-timeline-view.tsx @@ -2,10 +2,8 @@ import { useQuery } from '@tanstack/react-query' import { useFlow } from '@stackflow/react' import { useMemo, useRef, useState } from 'react' import { ActivityFlowChart } from '@/components/timeline/activity-flow-chart' -import { - TimelineCategoryFilters, - TimelineFilterReset, -} from '@/components/timeline/timeline-filters' +import { TimelineCategoryFilters } from '@/components/timeline/timeline-category-filters' +import { TimelineFilterReset } from '@/components/timeline/timeline-filter-reset' import { fromEventResponse, TimelineEventCard, @@ -20,7 +18,8 @@ import { EmptyStateAction, EmptyStateDescription, } from '@/components/ui/empty-state' -import { ListGroup, ListGroupItem } from '@/components/ui/list-group' +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' import { StatusMessage } from '@/components/ui/status-message' import { chipQuery, eventQuery, personQuery } from '@/apis/queries' import { formatPersonName } from '@/lib/format' diff --git a/frontend/src/stackflow/activities/settings/tag-type-panel.tsx b/frontend/src/stackflow/activities/settings/tag-type-panel.tsx index d56b1f2..4498351 100644 --- a/frontend/src/stackflow/activities/settings/tag-type-panel.tsx +++ b/frontend/src/stackflow/activities/settings/tag-type-panel.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { Button } from '@/components/ui/button' import { ConfirmPopup } from '@/components/ui/confirm-popup' import { Input } from '@/components/ui/input' -import { ListGroupInset } from '@/components/ui/list-group' +import { ListGroupInset } from '@/components/ui/list-group-inset' import type { ChipResponse, ChipResponseType, diff --git a/frontend/src/stackflow/tabs/people-tab.tsx b/frontend/src/stackflow/tabs/people-tab.tsx index 5465ec8..a00757f 100644 --- a/frontend/src/stackflow/tabs/people-tab.tsx +++ b/frontend/src/stackflow/tabs/people-tab.tsx @@ -20,12 +20,10 @@ import { Input } from '@/components/ui/input' import { SegmentedControl } from '@/components/ui/segmented-control' import { StatusMessage } from '@/components/ui/status-message' import { TagChip } from '@/components/ui/tag-chip' -import { - ListGroup, - ListGroupInset, - ListGroupItem, - ListGroupLabel, -} from '@/components/ui/list-group' +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupInset } from '@/components/ui/list-group-inset' +import { ListGroupItem } from '@/components/ui/list-group-item' +import { ListGroupLabel } from '@/components/ui/list-group-label' import { formatLastMetRelative, formatPersonName } from '@/lib/format' import { cn } from '@/lib/utils' diff --git a/frontend/src/stackflow/tabs/settings-tab.tsx b/frontend/src/stackflow/tabs/settings-tab.tsx index b588a1c..6ef7fbd 100644 --- a/frontend/src/stackflow/tabs/settings-tab.tsx +++ b/frontend/src/stackflow/tabs/settings-tab.tsx @@ -8,11 +8,9 @@ import { useTheme } from '@/components/theme-provider' import { Button } from '@/components/ui/button' import { ConfirmPopup } from '@/components/ui/confirm-popup' import { PageTitle } from '@/components/ui/page-title' -import { - ListGroup, - ListGroupItem, - ListGroupLabel, -} from '@/components/ui/list-group' +import { ListGroup } from '@/components/ui/list-group' +import { ListGroupItem } from '@/components/ui/list-group-item' +import { ListGroupLabel } from '@/components/ui/list-group-label' import { Switch } from '@/components/ui/switch' import { userMutation } from '@/apis/mutations' import { featureEvents, resetAnalytics, trackFeature } from '@/lib/analytics'