From 3ae1a76b73a35f34da77c94715b09a12a296e979 Mon Sep 17 00:00:00 2001 From: Seongbin Kim Date: Sun, 19 Jul 2026 16:45:25 +0900 Subject: [PATCH] =?UTF-8?q?refactor(fe):=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=ED=96=89=EC=9D=84=20NavigationRow?= =?UTF-8?q?=EB=A1=9C=20=EA=B3=B5=ED=86=B5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라벨과 오른쪽 chevron을 가진 행 버튼을 도메인 중립 UI 컴포넌트 NavigationRow로 묶는다. 설정 탭의 SettingsNavigationItem과 인물 프로필의 인라인 행 3개가 같은 모양을 각자 구현해 스타일이 어긋나 있었다. - src/components/ui/navigation-row.tsx 신설: tone(default/destructive), withDivider, disabled를 받고 ListGroupItem 래퍼를 내부에서 렌더한다. - 설정 탭 2행과 인물 프로필 작업 섹션 3행을 NavigationRow로 이관한다. - SettingsNavigationItem과 스토리를 삭제하고 커버리지를 UI/NavigationRow로 옮긴다. - 누름 피드백을 hover:bg-muted/70과 active:opacity-70로 통일한다. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../settings-navigation-item.stories.tsx | 37 -------------- .../settings/settings-navigation-item.tsx | 27 ---------- .../components/ui/navigation-row.stories.tsx | 47 ++++++++++++++++++ frontend/src/components/ui/navigation-row.tsx | 49 +++++++++++++++++++ .../activities/person/person-profile-view.tsx | 47 ++++++------------ frontend/src/stackflow/tabs/settings-tab.tsx | 6 +-- 6 files changed, 115 insertions(+), 98 deletions(-) delete mode 100644 frontend/src/components/settings/settings-navigation-item.stories.tsx delete mode 100644 frontend/src/components/settings/settings-navigation-item.tsx create mode 100644 frontend/src/components/ui/navigation-row.stories.tsx create mode 100644 frontend/src/components/ui/navigation-row.tsx diff --git a/frontend/src/components/settings/settings-navigation-item.stories.tsx b/frontend/src/components/settings/settings-navigation-item.stories.tsx deleted file mode 100644 index 66c0f13..0000000 --- a/frontend/src/components/settings/settings-navigation-item.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { ListGroup } from '@/components/ui/list-group' -import { SettingsNavigationItem } from '@/components/settings/settings-navigation-item' -import type { Meta, StoryObj } from '@storybook/react-vite' - -const meta = { - title: 'Settings/SettingsNavigationItem', - component: SettingsNavigationItem, - tags: ['autodocs'], - args: { - label: '알림 설정', - onClick: () => {}, - }, - // 항목은 ListGroup(둥근 배경·구분선 컨텍스트) 안에서 쓰이므로 감싸서 실제 모습을 보여준다. - decorators: [ - (Story) => ( -
- - - -
- ), - ], -} satisfies Meta - -export default meta - -type Story = StoryObj - -export const Default: Story = {} - -export const WithoutDivider: Story = { - args: { withDivider: false }, -} - -export const LongText: Story = { - args: { label: '오랜만에 연락한 사람에게 보낼 안부 리마인더 알림 설정' }, -} diff --git a/frontend/src/components/settings/settings-navigation-item.tsx b/frontend/src/components/settings/settings-navigation-item.tsx deleted file mode 100644 index 438b9c0..0000000 --- a/frontend/src/components/settings/settings-navigation-item.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ChevronRight } from 'lucide-react' -import { ListGroupItem } from '@/components/ui/list-group-item' - -export function SettingsNavigationItem({ - label, - onClick, - withDivider = true, -}: { - label: string - onClick: () => void - withDivider?: boolean -}) { - return ( - - - - ) -} diff --git a/frontend/src/components/ui/navigation-row.stories.tsx b/frontend/src/components/ui/navigation-row.stories.tsx new file mode 100644 index 0000000..d87445e --- /dev/null +++ b/frontend/src/components/ui/navigation-row.stories.tsx @@ -0,0 +1,47 @@ +import { ListGroup } from '@/components/ui/list-group' +import { NavigationRow } from '@/components/ui/navigation-row' +import type { Meta, StoryObj } from '@storybook/react-vite' + +const meta = { + title: 'UI/NavigationRow', + component: NavigationRow, + tags: ['autodocs'], + args: { + label: '프로필 수정', + onClick: () => {}, + }, + // 행은 ListGroup(둥근 배경·구분선 컨텍스트) 안에서 쓰이므로 감싸서 실제 모습을 보여준다. + decorators: [ + (Story) => ( +
+ + + +
+ ), + ], +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} + +export const Destructive: Story = { + args: { label: '인물 삭제', tone: 'destructive', withDivider: false }, +} + +export const WithoutDivider: Story = { + args: { withDivider: false }, +} + +export const Disabled: Story = { + args: { label: '인물 삭제', tone: 'destructive', disabled: true }, +} + +export const LongText: Story = { + args: { + label: '오랜만에 연락한 사람에게 보낼 안부 리마인더를 지금 설정하기', + }, +} diff --git a/frontend/src/components/ui/navigation-row.tsx b/frontend/src/components/ui/navigation-row.tsx new file mode 100644 index 0000000..2fed6b2 --- /dev/null +++ b/frontend/src/components/ui/navigation-row.tsx @@ -0,0 +1,49 @@ +import { ChevronRight } from 'lucide-react' +import { ListGroupItem } from '@/components/ui/list-group-item' + +// 오른쪽 chevron을 가진 라벨 행 버튼. label + onClick만 받고 라우팅/도메인은 호출부가 소유한다. +// tone='destructive'는 인물 삭제 등 되돌릴 수 없는 동작의 시각 경고(라벨·chevron을 destructive 색)로만 쓴다. +// 터치 앱이라 hover(포인터 기기)와 active(누름) 피드백을 함께 유지한다. +export function NavigationRow({ + label, + onClick, + tone = 'default', + withDivider = true, + disabled = false, +}: { + label: string + onClick: () => void + tone?: 'default' | 'destructive' + withDivider?: boolean + disabled?: boolean +}) { + const isDestructive = tone === 'destructive' + + return ( + + + + ) +} diff --git a/frontend/src/stackflow/activities/person/person-profile-view.tsx b/frontend/src/stackflow/activities/person/person-profile-view.tsx index 5ce9ac1..7d65690 100644 --- a/frontend/src/stackflow/activities/person/person-profile-view.tsx +++ b/frontend/src/stackflow/activities/person/person-profile-view.tsx @@ -9,6 +9,7 @@ import { MonogramAvatar } from '@/components/ui/monogram-avatar' import { ListGroup } from '@/components/ui/list-group' import { ListGroupItem } from '@/components/ui/list-group-item' import { ListGroupLabel } from '@/components/ui/list-group-label' +import { NavigationRow } from '@/components/ui/navigation-row' import { StatusMessage } from '@/components/ui/status-message' import { TagChip } from '@/components/ui/tag-chip' import { optimizedImageUrl } from '@/lib/image-url' @@ -224,37 +225,21 @@ export function PersonProfileView({
작업 - - - - - - - - - + push('Record', { personId })} + /> + push('PersonEdit', { personId })} + /> + del.setOpen(true)} + />
diff --git a/frontend/src/stackflow/tabs/settings-tab.tsx b/frontend/src/stackflow/tabs/settings-tab.tsx index 6ef7fbd..e8222da 100644 --- a/frontend/src/stackflow/tabs/settings-tab.tsx +++ b/frontend/src/stackflow/tabs/settings-tab.tsx @@ -3,10 +3,10 @@ import { useFlow } from '@stackflow/react' import { RotateCcw } from 'lucide-react' import { useState } from 'react' import { MongleLogo } from '@/components/brand/mongle-logo' -import { SettingsNavigationItem } from '@/components/settings/settings-navigation-item' import { useTheme } from '@/components/theme-provider' import { Button } from '@/components/ui/button' import { ConfirmPopup } from '@/components/ui/confirm-popup' +import { NavigationRow } from '@/components/ui/navigation-row' import { PageTitle } from '@/components/ui/page-title' import { ListGroup } from '@/components/ui/list-group' import { ListGroupItem } from '@/components/ui/list-group-item' @@ -48,11 +48,11 @@ export function SettingsTab() {
- push('HomeSettings', {})} /> - push('TagSettings', {})}