Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a819d32
chore(ui): 동작 부품 shadcn base-nova 코드 받기
KangShinGyu98 Oct 31, 2026
8713ae3
chore(deps): TanStack Form 추가
KangShinGyu98 Oct 31, 2026
0425918
chore(ui): Base UI 대화상자·팝업이 앱 위에 뜨도록 루트 설정
KangShinGyu98 Oct 31, 2026
9a4832d
feat(icons): check·sign/logout 아이콘 추가
KangShinGyu98 Oct 31, 2026
3a84f96
style(ui): Button을 디자인 시스템 Button에 맞춤
KangShinGyu98 Oct 31, 2026
cfbcceb
style(ui): Dialog를 디자인 시스템 Modal에 맞춤
KangShinGyu98 Oct 31, 2026
d2a38a1
style(ui): AlertDialog를 디자인 시스템 Alert Dialog에 맞춤
KangShinGyu98 Oct 31, 2026
97089b6
style(ui): Drawer를 디자인 시스템 Bottom Sheet에 맞춤
KangShinGyu98 Oct 31, 2026
f732b8a
style(ui): DropdownMenu를 디자인 시스템 Dropdown Menu에 맞춤
KangShinGyu98 Oct 31, 2026
3b4270c
style(ui): Select를 디자인 시스템 Select에 맞춤
KangShinGyu98 Oct 31, 2026
0c68aa5
style(ui): Combobox를 디자인 시스템 Select Field에 맞추고 input-group 삭제
KangShinGyu98 Oct 31, 2026
6644ef5
style(ui): Popover 표면을 Modal과 같게 맞춤
KangShinGyu98 Oct 31, 2026
61f4b7b
style(ui): Tabs를 디자인 시스템 Tabs에 맞춤
KangShinGyu98 Oct 31, 2026
57c9a83
chore(deps): shadcn add가 함께 설치한 cn 패키지 제거
KangShinGyu98 Oct 31, 2026
9fbb59a
docs(conventions): Button·Base UI 팝업·대화상자 폼 원칙 반영
KangShinGyu98 Oct 31, 2026
cf724c8
feat(ui): 반응형 대화상자와 본문 스크롤 DialogBody 추가
KangShinGyu98 Oct 31, 2026
aa1915f
refactor(settings): 확인창 ConfirmDialog를 ui AlertDialog 위로 옮기기
KangShinGyu98 Oct 31, 2026
6c0ce5e
refactor(expenses): 가계부 대화상자·선택기를 ui 부품과 TanStack Form으로
KangShinGyu98 Oct 31, 2026
df7668b
refactor(expenses): 가계부 패널 버튼·메뉴·탭·예산 대화상자 교체
KangShinGyu98 Oct 31, 2026
b3a3920
refactor(plan): PC 일정 대화상자·메뉴를 ui 부품으로
KangShinGyu98 Oct 31, 2026
b85899e
refactor(plan): 모바일 일정 시트를 ui Drawer로 옮기고 MobileBottomSheet 삭제
KangShinGyu98 Oct 31, 2026
d7e3adc
refactor(place): 장소·북마크 대화상자·메뉴·탭을 ui 부품으로
KangShinGyu98 Oct 31, 2026
68333a0
refactor(map): 평점 필터·날짜 선택을 ui Select·Popover·Dialog로
KangShinGyu98 Oct 31, 2026
2a7971b
refactor(rooms): 여행방·홈 대화상자·메뉴를 ui 부품으로
KangShinGyu98 Oct 31, 2026
79dfff3
refactor(layout): 헤더·구성원·설정의 메뉴·대화상자를 ui 부품으로
KangShinGyu98 Oct 31, 2026
15bf999
refactor(packing): 준비물 대화상자·메뉴·시트를 ui 부품으로
KangShinGyu98 Oct 31, 2026
fdc8674
refactor(ui): 쓰지 않게 된 옛 대화상자·시트 부품 삭제
KangShinGyu98 Oct 31, 2026
6412011
style(analytics): 쿠키 설정 대화상자 폭을 sm(384)으로 줄이기
KangShinGyu98 Nov 1, 2026
28aa95b
style(analytics): 쿠키 설정에 대화상자 취소·저장 버튼 줄 두기
KangShinGyu98 Nov 1, 2026
062ad0c
style(rooms): 초대하기 대화상자 폭을 md(448)로 넓히기
KangShinGyu98 Nov 1, 2026
c2f0e00
style(settings): 회원 탈퇴 글자 버튼에 손가락 커서 추가
KangShinGyu98 Nov 1, 2026
19fd9b7
style(analytics): 쿠키 설정 대화상자 폭을 md(448)로 한 단계 넓히기
KangShinGyu98 Nov 1, 2026
ca49e19
style(ui): 확인창 본문 글자를 16px(body/md)로 고정
KangShinGyu98 Nov 1, 2026
52fe2fe
style(expenses): 예산 수정 대화상자 폭을 sm(384)으로 줄이기
KangShinGyu98 Nov 1, 2026
c5e0ec2
style(expenses): 카테고리 필터 칸을 184로 넓혀 '전체 카테고리' 줄바꿈 막기
KangShinGyu98 Nov 1, 2026
dc99191
style(chat): PC 채팅 입력칸을 3줄 높이로 키우기
KangShinGyu98 Nov 1, 2026
2e1ffe0
fix(expenses): PC 가계부 일차 필터가 넘치면 스크롤 막대로 옆으로 넘기게
KangShinGyu98 Nov 1, 2026
22408d1
fix(expenses): 모바일 비용 편집 통화 칸에서 코드 3글자가 잘리지 않게
KangShinGyu98 Nov 1, 2026
dc44ff4
Merge branch 'refactor/shadcn-base-ui' into refactor/shadcn-behavior
KangShinGyu98 Nov 1, 2026
01b7112
fix(packing): 저장할 수 없는 상태에서 준비물 삭제 확인 버튼을 비활성으로
KangShinGyu98 Nov 1, 2026
ad7b363
fix(expenses): 정산 요약 대화상자의 조회 재시도 진행을 progress로
KangShinGyu98 Nov 1, 2026
6321550
refactor(rooms): 여행 정보 수정 칸을 form.Field로 그리고 칸별 오류 표시
KangShinGyu98 Nov 1, 2026
15c3ec2
test(plan): 일정 시간·여행 정보 수정·방 메뉴·북마크 메뉴 테스트를 Base UI 화면 기준으로
KangShinGyu98 Nov 1, 2026
6bf03c2
test(packing): 준비물·확인창 테스트를 Base UI 화면 기준으로
KangShinGyu98 Nov 1, 2026
dabccd6
test(analytics): 쿠키 설정·모바일 헤더 메뉴·지도 평점 필터 테스트를 Base UI 화면 기준으로
KangShinGyu98 Nov 1, 2026
f0c5b58
test(expenses): 가계부 패널·분석 탭·메뉴·예산 테스트를 Base UI 화면 기준으로
KangShinGyu98 Nov 1, 2026
5b40da9
test(expenses): 비용 편집·카테고리 Select·통화 Combobox 테스트를 Base UI 화면 기준으로
KangShinGyu98 Nov 1, 2026
8b002c2
test(expenses): 가계부 Provider·딤·비용 목록 테스트를 Base UI 화면 기준으로, 옛 끌기 테스트 삭제
KangShinGyu98 Nov 1, 2026
ea85edd
fix(rooms): 여행 정보 수정 대화상자가 저장 중에 Esc·딤·X로 닫히지 않게
KangShinGyu98 Nov 1, 2026
576ac96
fix(date-picker): 칸 아래 달력에서 Tab이 페이지 맨 앞으로 빠져나가지 않게 포커스 가두기
KangShinGyu98 Nov 1, 2026
d5299db
refactor(ui): 팝업 이벤트 경계와 Sonar 품질 경고 정리
minbros Nov 2, 2026
2207be8
chore(merge): 선행 UI PR의 Sonar 수정을 동기화
minbros Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ import { MapPinIcon } from "@/assets/icons";

### 3-3. 이벤트 피드백
- 사용자 액션에는 결과 피드백이 있어야 한다
- alert는 toast, confirm은 `ConfirmDialog`를 쓰고 요구조건에 맞춘다(shadcn 부품으로 옮기는 기준은 [핵심 원칙 1-1](docs/code-conventions/principles.md))
- alert는 toast, confirm은 `ConfirmDialog`(ui `AlertDialog` 위)를 쓰고 요구조건에 맞춘다(shadcn 부품으로 옮기는 기준은 [핵심 원칙 1-1](docs/code-conventions/principles.md))
- 비동기 동작 중 중복 실행 방지(핵심 원칙 2-4)


Expand Down
4 changes: 2 additions & 2 deletions docs/code-conventions/anti-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- ✓ prop을 더하기 전에 계산 → slot → `variant` → 화면 훅 순서로 본다. `<PlaceCard variant="compact" action={<AddToScheduleButton placeId={place.id} />} />`(원칙 2-2·1-4)

### 6. 있는 부품 다시 만들기
- 신호: 새 `fixed inset-0` 모달·바텀시트·로딩 표시, `SettingsActionButton`·`alertDialogButtonClass`가 맞는 자리에 복붙한 버튼 클래스, 새로 만든 범용 Button, `lucide-react`·새 아이콘 패키지
- ✓ `src/components/ui/`의 부품(`Spinner`·`Skeleton` 등)과 `ConfirmDialog`·`SettingsDialog`·`MobileBottomSheet`를 먼저 쓴다. 버튼은 `SettingsActionButton`·`alertDialogButtonClass`가 맞으면 쓰고, 아니면 그 자리 `<button>`에 토큰 클래스로 쓴다(범용 Button은 보류). 아이콘은 `@/assets/icons`(원칙 1-1, Design.md 2장).
- 신호: 새 `fixed inset-0` 모달·바텀시트·로딩 표시, 손으로 만든 메뉴·셀렉트·탭, `Button` 대신 복붙한 버튼 클래스, `useState`로 하나씩 든 대화상자 폼 값·오류, `lucide-react`·새 아이콘 패키지
- ✓ `src/components/ui/`의 부품(`Button`·`Dialog`·`AlertDialog`·`Drawer`·`DropdownMenu`·`Select`·`Combobox`·`Popover`·`Tabs`·`Spinner`·`Skeleton` 등)과 확인창 `ConfirmDialog`를 먼저 쓴다. 대화상자 폼은 TanStack Form(원칙 2-9). 아이콘은 `@/assets/icons`(원칙 1-1, Design.md 2장).

### 7. 미리 만든 추상화
- 신호: 사용처가 하나인 래퍼·옵션 객체·범용 컴포넌트, `useMount`·`useUpdateEffect` 같은 생명주기 훅, 쓰는 곳 없는 설정 레이어. 로직을 나누는 화면 훅은 사용처가 하나여도 걸리지 않는다.
Expand Down
56 changes: 43 additions & 13 deletions docs/code-conventions/principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,39 @@

### 1-1. 부품은 한 번 만들고 조립한다 (Headless UI)

목표는 동작(포커스 가두기·키보드·aria·포털)은 shadcn(Base UI) 프리미티브가, 모양은 디자인 시스템 토큰이 맡는 것이다. 둘을 합친 부품을 `src/components/ui/`에 한 번 두고 화면은 조립만 한다. shadcn은 `base-nova` 스타일(`components.json`)로 받고, 옮기는 동안 기존 공용 부품과 병행한다.
목표는 동작(포커스 가두기·키보드·aria·포털)은 shadcn(Base UI) 프리미티브가, 모양은 디자인 시스템 토큰이 맡는 것이다. 둘을 합친 부품을 `src/components/ui/`에 한 번 두고 화면은 조립만 한다. shadcn은 `base-nova` 스타일(`components.json`)로 받는다.

| 상황 | 할 일 |
| :-- | :-- |
| 맞는 부품이 있다(`src/components/ui/`의 shadcn 부품, `src/components/<도메인>/`의 `ConfirmDialog`·`SettingsDialog`·`MobileBottomSheet`·`SettingsActionButton` 등) | 그대로 쓴다. 같은 역할의 shadcn 부품이 생기면 새로 쓰는 곳은 그쪽을 쓰고, 기존 사용처는 아래 4행대로 따로 옮긴다 |
| 맞는 공용 부품이 없는 동작 부품(드롭다운·팝오버·탭·셀렉트)이 필요하다 | shadcn으로 만든다. `src/components/ui/`에 아직 없는 부품이면 받기 전에 묻는다(새 파일·의존성이 생긴다). 미루면 공용 셸에서 동작 처리(portal·포커스 가두기·Escape·aria)만 가져오고, 클래스·토큰·아이콘은 3-6과 Design.md를 따른다 |
| 맞는 부품이 있다(`src/components/ui/`의 `Button`·`Dialog`·`AlertDialog`·`Drawer`(바텀시트)·`ResponsiveDialog`(PC는 대화상자, 모바일 기기는 바텀시트)·`DropdownMenu`·`Select`·`Combobox`·`Popover`·`Tabs`·`Field`·`Input` 등, 확인창은 `ConfirmDialog`) | 그대로 쓴다. 같은 역할의 shadcn 부품이 생기면 새로 쓰는 곳은 그쪽을 쓰고, 기존 사용처는 아래 4행대로 따로 옮긴다 |
| 맞는 부품이 없는 동작 부품이 필요하다 | shadcn으로 만든다. `src/components/ui/`에 아직 없는 부품이면 받기 전에 묻는다(새 파일·의존성이 생긴다). 클래스·토큰·아이콘은 3-6과 Design.md를 따른다 |
| 동작 없는 표시 부품이 필요하다 | Tailwind로 만든다 |
| 기존 공용 부품이나 그 사용처를 shadcn으로 바꾼다 | 기능·버그 작업에 섞지 않고 따로 한다 |

- 변형은 `variant`·`size` 같은 유니온 prop으로 받고, 값은 Figma 컴포넌트 속성 값에 맞춘다. 변형별 클래스는 cva로 정의한다(3-6).
- 화면은 부품의 색·글자를 덮지 않는다. 바깥에서 주는 `className`은 배치(폭·정렬·위치)에만 쓴다.
- 공용 Button은 사이즈 기획이 끝나지 않아 보류 중이다. 범용 Button 부품을 새로 만들지 않는다.
- 버튼은 `ui/button`의 `Button`(Figma Button: `variant` solid·weak·outline × `tone` neutral·brand·critical × `size` lg 56·md 48·sm 36·xs 32)을 쓴다. 대화상자 버튼은 취소 `variant="weak"`, 확인 기본(Neutral Solid), 삭제·탈퇴 같은 위험 동작 `tone="critical"`이다. 처리 중은 문구 대신 `loading`(progress)으로 보이고 `loadingLabel`로 읽힌다.
- shadcn 코드를 받으면 디자인 시스템에 맞춘다.
- 색·그림자·포커스 클래스(`secondary`·`*-foreground`·`muted`·`accent`·`input`·`ring`·`shadow-*`)는 시맨틱 토큰으로 바꾼다. 이름이 같아도 뜻이 다르고(`secondary`는 라임), 없는 이름은 CSS가 생기지 않는다.
- `shadcn init`은 하지 않고 `add`로만 받는다. init이 `globals.css`에 넣는 색 변수(`:root`·`@theme inline`)는 기존 `primary`·`border`·`background` 토큰을 앱 전체에서 덮는다.
- 아이콘은 `lucide-react` 대신 `@/assets/icons`, `cn`은 `@/lib/utils`의 것을 쓴다. 받은 코드의 `import { cn } from "cn"`은 CLI가 바꾸지 않는다. `cn` 패키지는 타이포 토큰을 색으로 보고 지운다.
- `dark:` 클래스는 지운다. 다크 모드 설정이 없어 OS가 다크 모드면 적용된다.
- z값은 같은 역할의 공용 셸(대화상자 `SettingsDialog`, 시트 `MobileBottomSheet`)에 맞추고, 그 안에서 여는 팝오버·셀렉트는 셸보다 위에 둔다.
- Base UI 팝업(대화상자·시트·메뉴·셀렉트·팝오버)은 모두 z-50이다. 앱은 `isolate`로 묶여 있어(`app-chrome-shell`) body로 포털되는 팝업이 앱의 z와 섞이지 않고, 같은 z라 나중에 연 팝업(대화상자 안 셀렉트·확인창)이 위에 뜬다. 앱 안에서 z를 키워 팝업 위로 올리지 않는다.

```tsx
// ✗ 화면마다 모달을 직접 만든다. 포커스 가두기·ESC·aria가 빠지고 z값이 제각각이 된다
<div className="fixed inset-0 z-[999] bg-black/50" onClick={close}>…</div>

// ✓ 지금: 공용 셸을 쓴다(포커스 가두기·ESC·aria 포함)
{isInviteOpen && <SettingsDialog title="초대" onClose={closeInvite}>…</SettingsDialog>}

// ✓ shadcn으로 옮긴 뒤: 동작은 Base UI, 모양은 토큰을 입힌 ui 부품
<Dialog>
<DialogTrigger className="…">초대</DialogTrigger>
<DialogContent>…</DialogContent>
// ✓ 동작은 Base UI, 모양은 토큰을 입힌 ui 부품. 저장 중에는 closeDisabled로 Esc·딤·X를 막는다
<Dialog open={isInviteOpen} onOpenChange={setIsInviteOpen} closeDisabled={isSending}>
<DialogContent>
<DialogHeader><DialogTitle>초대</DialogTitle></DialogHeader>
…
<DialogFooter>
<DialogClose render={<Button variant="weak" />}>취소</DialogClose>
<Button loading={isSending} loadingLabel="보내는 중" onClick={send}>보내기</Button>
</DialogFooter>
</DialogContent>
</Dialog>
```

Expand Down Expand Up @@ -140,7 +143,7 @@ function PlaceRow({ place }: { place: Place }) {

boolean prop은 하나 늘 때마다 가능한 조합이 두 배가 되고, 그중 일부는 있을 수 없는 상태다. 변형은 이름 있는 값이나 합성으로 드러낸다.

- 구조·동작을 바꾸는 boolean prop 대신 children·slot이나 변형별 컴포넌트를 쓴다. 표시 변형은 boolean 여럿 대신 유니온 prop(`variant`·`size`)으로 받는다(`MobileBottomSheet`의 `variant: "menu" | "form"`).
- 구조·동작을 바꾸는 boolean prop 대신 children·slot이나 변형별 컴포넌트를 쓴다. 표시 변형은 boolean 여럿 대신 유니온 prop(`variant`·`size`)으로 받는다(`Badge`의 `variant: "weak" | "solid" | "outline"`·`tone`).
- renderX prop 대신 children으로 넘긴다.
- 쿼리 상태를 `isPending`·`isError` prop으로 쪼개 넘기지 않는다. 분기는 컨테이너에서 끝낸다(1-2).

Expand Down Expand Up @@ -424,6 +427,33 @@ const sorted = data.places.sort(byOrder);
const sorted = [...data.places].sort(byOrder);
```

### 2-9. 폼은 TanStack Form, 검증은 칸마다 (Forms)

입력 값·검증·제출 상태를 `useState`로 하나씩 들면 오류가 폼 아래 문장 하나로 뭉치고, 어느 칸이 틀렸는지 화면이 모른다. 대화상자·시트 안의 폼은 `@tanstack/react-form`으로 쓴다.

- 값은 `useForm({ defaultValues })`가 갖고, 칸은 `form.Field`로 그린다. 검증은 칸마다 `validators`에 함수로 둔다(zod는 아직 쓰지 않는다 — 나중에 같은 자리에 스키마를 넣는다). 검증 함수는 `src/lib`의 순수 함수를 부른다.
- 칸 오류는 그 칸에 `aria-invalid`와 `FieldError`로 그린다. Select처럼 Figma에 오류 상태가 없는 칸도 Text Field 오류 모양을 쓴다. 권한·저장 실패·충돌처럼 칸이 없는 오류는 폼 아래 문장으로 둔다.
- 제출은 `form.handleSubmit()` → `onSubmit`에서 mutation의 `mutateAsync`. 버튼 `loading`과 대화상자 `closeDisabled`는 그 mutation의 `isPending` 하나로 정한다(2-4).

```tsx
const form = useForm({
defaultValues: { budget: initial.budgetKrw ?? "" },
onSubmit: ({ value }) => saveBudget.mutateAsync({ budgetKrw: value.budget }),
});

<form.Field name="budget" validators={{ onSubmit: ({ value }) => budgetError(value) }}>
{(field) => (
<Field>
<FieldLabel htmlFor={field.name}>예산 (KRW)</FieldLabel>
<Input id={field.name} value={field.state.value} onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={field.state.meta.errors.length > 0} />
<FieldError>{field.state.meta.errors[0]}</FieldError>
</Field>
)}
</form.Field>
<Button type="submit" loading={saveBudget.isPending} loadingLabel="저장 중">저장</Button>
```

## 3. 퍼블리싱

### 3-1. Auto Layout을 그대로 옮긴다 (Layout First)
Expand Down
95 changes: 95 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@ncdai/react-wheel-picker": "^1.2.3",
"@next/third-parties": "^16.2.1",
"@stomp/stompjs": "^7.3.0",
"@tanstack/react-form": "^1.33.5",
"@tanstack/react-query": "^5.95.2",
"@tanstack/react-query-devtools": "^5.95.2",
"@vis.gl/react-google-maps": "^1.8.2",
Expand Down
5 changes: 2 additions & 3 deletions public/rooms/figma/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
- 모바일: https://www.figma.com/design/3JnrQOfRePmycccIBfiPnZ?node-id=3314-5275
- 추출일: 2026-09-20
- `trip-cover.png`: Figma 원본 카드 사진. 현재 방 목록에서는 `public/rooms/covers/`의 사진 9장 중 방 ID로 선택한 이미지를 사용한다.
- `menu.svg`, `plus.svg`: Figma에서 내려받은 원본 아이콘.
- `plus.svg`: Figma에서 내려받은 원본 아이콘.
- `plus-mobile.svg`: 모바일 생성 버튼의 20px 원본 아이콘.
- `user.svg`, `logout.svg`: 프로필 오버레이 원본 아이콘 (`3456:5418`).
- `invite.svg`, `trash.svg`: 방 더보기 오버레이 원본 아이콘 (`3449:3613`).
- `invite.svg`: 방 더보기 오버레이 원본 아이콘 (`3449:3613`). 더보기 버튼·프로필 메뉴·삭제 아이콘은 디자인 시스템 아이콘(`@/assets/icons`)을 쓴다.

광고 배너 영역에는 [마케팅 시안](https://www.figma.com/design/asf1pKmEosGS4xUx1qn3MV/%EB%A7%88%EC%BC%80%ED%8C%85?node-id=0-1)의 데스크톱·모바일 완성 배너 이미지를 각각 그대로 사용한다. 에셋은 만료되는 Figma 다운로드 URL 대신 로컬 파일을 참조한다.

Expand Down
7 changes: 0 additions & 7 deletions public/rooms/figma/logout.svg

This file was deleted.

7 changes: 0 additions & 7 deletions public/rooms/figma/menu.svg

This file was deleted.

Loading