@hjmds/design-contracts 1.41.2 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/affix.d.ts +18 -0
- package/dist/affix.d.ts.map +1 -1
- package/dist/affix.js +13 -1
- package/dist/affix.js.map +1 -1
- package/dist/alert-dialog.d.ts +32 -0
- package/dist/alert-dialog.d.ts.map +1 -1
- package/dist/alert-dialog.js +52 -0
- package/dist/alert-dialog.js.map +1 -1
- package/dist/behaviors.d.ts +6 -5
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +1 -1
- package/dist/behaviors.js.map +1 -1
- package/dist/catalog.d.ts +168 -15
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +4 -4
- package/dist/catalog.js.map +1 -1
- package/dist/character-count.d.ts +103 -0
- package/dist/character-count.d.ts.map +1 -0
- package/dist/character-count.js +68 -0
- package/dist/character-count.js.map +1 -0
- package/dist/chart.d.ts +30 -0
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +18 -0
- package/dist/chart.js.map +1 -1
- package/dist/collection-rail.d.ts +13 -1
- package/dist/collection-rail.d.ts.map +1 -1
- package/dist/collection-rail.js +9 -2
- package/dist/collection-rail.js.map +1 -1
- package/dist/communication.d.ts +25 -0
- package/dist/communication.d.ts.map +1 -1
- package/dist/communication.js +3 -0
- package/dist/communication.js.map +1 -1
- package/dist/component-recipes.d.ts +172 -2
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +122 -8
- package/dist/component-recipes.js.map +1 -1
- package/dist/design-system-provider.d.ts +13 -2
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +19 -3
- package/dist/design-system-provider.js.map +1 -1
- package/dist/foundations.d.ts +15 -0
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +7 -0
- package/dist/foundations.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +33 -0
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +36 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/image.d.ts +13 -0
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +17 -0
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/recipes.d.ts +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -1
- package/dist/recipes.js.map +1 -1
- package/dist/screen-patterns.d.ts +95 -0
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +83 -2
- package/dist/screen-patterns.js.map +1 -1
- package/dist/selection-helpers.d.ts +6 -0
- package/dist/selection-helpers.d.ts.map +1 -1
- package/dist/selection-helpers.js +12 -0
- package/dist/selection-helpers.js.map +1 -1
- package/dist/sidebar.d.ts +17 -2
- package/dist/sidebar.d.ts.map +1 -1
- package/dist/sidebar.js +8 -2
- package/dist/sidebar.js.map +1 -1
- package/dist/steps.d.ts +19 -19
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +21 -9
- package/dist/steps.js.map +1 -1
- package/dist/timeline.d.ts +41 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +47 -1
- package/dist/timeline.js.map +1 -1
- package/dist/top.d.ts +21 -2
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +17 -1
- package/dist/top.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/affix.md +7 -0
- package/docs/avatar-fallback.md +19 -0
- package/docs/brand-boundary.md +24 -2
- package/docs/chart.md +7 -0
- package/docs/collection-rail.md +4 -1
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +10 -6
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +12 -5
- package/docs/image.md +5 -0
- package/docs/list-row.md +8 -0
- package/docs/optional-adapters.md +3 -0
- package/docs/overlay-stack.md +27 -0
- package/docs/screen-patterns.md +39 -2
- package/docs/sidebar.md +15 -0
- package/docs/steps.md +23 -19
- package/docs/theme-palette.md +3 -3
- package/docs/theming.md +1 -0
- package/docs/timeline.md +18 -1
- package/docs/top.md +7 -0
- package/docs/usage/README.md +9 -2
- package/docs/usage/components/affix.md +18 -4
- package/docs/usage/components/alert-dialog.md +28 -0
- package/docs/usage/components/avatar.md +30 -9
- package/docs/usage/components/badge.md +20 -5
- package/docs/usage/components/chart.md +19 -1
- package/docs/usage/components/chat-message.md +5 -0
- package/docs/usage/components/chat-room-actions.md +58 -0
- package/docs/usage/components/chat-room-create.md +58 -0
- package/docs/usage/components/chat-room-details.md +58 -0
- package/docs/usage/components/chat-room-invite.md +58 -0
- package/docs/usage/components/chat-room-members.md +58 -0
- package/docs/usage/components/chat-room-profile.md +58 -0
- package/docs/usage/components/checkbox-group.md +24 -1
- package/docs/usage/components/choice-row.md +19 -4
- package/docs/usage/components/composition-layout.md +6 -2
- package/docs/usage/components/counter-badge.md +2 -2
- package/docs/usage/components/design-system-provider.md +2 -1
- package/docs/usage/components/divider.md +12 -1
- package/docs/usage/components/field.md +51 -0
- package/docs/usage/components/icon-button.md +48 -4
- package/docs/usage/components/icon-tile.md +13 -2
- package/docs/usage/components/image.md +25 -0
- package/docs/usage/components/list-row.md +8 -4
- package/docs/usage/components/list.md +1 -1
- package/docs/usage/components/notification-item.md +5 -1
- package/docs/usage/components/permission-screen.md +25 -4
- package/docs/usage/components/screen-layout.md +27 -1
- package/docs/usage/components/segmented-control.md +16 -3
- package/docs/usage/components/select.md +1 -0
- package/docs/usage/components/settings-screen.md +5 -4
- package/docs/usage/components/sheet.md +2 -1
- package/docs/usage/components/sidebar.md +34 -6
- package/docs/usage/components/steps.md +29 -8
- package/docs/usage/components/surface.md +2 -1
- package/docs/usage/components/text-area.md +2 -1
- package/docs/usage/components/timeline.md +25 -4
- package/docs/usage/components/top.md +15 -2
- package/docs/usage/compositions/chat-room.md +110 -0
- package/docs/usage/screens/common-settings.md +3 -2
- package/docs/usage/screens/flow-permission.md +3 -3
- package/docs/usage/tokens/elevation-opacity.md +14 -2
- package/package.json +7 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [Sidebar](../../sidebar.md), `src/sidebar.ts`(`sidebarRecipe`, `validateSidebarDescriptor`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/탐색/사이드바`, `배포/컴포넌트/탐색/사이드바 전환`
|
|
10
10
|
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
| 이름 | 역할 | Web | Native |
|
|
29
29
|
| --- | --- | --- | --- |
|
|
30
30
|
| `Sidebar` | 기본 | `@hjmds/react`, `/sidebar` | — |
|
|
31
|
+
| `SidebarLinkRenderProps` · `SidebarSize` | 타입 — `renderLink` 인자, 폭 단계 | `@hjmds/react`, `/sidebar` | — |
|
|
31
32
|
|
|
32
33
|
## 최소 사용 예
|
|
33
34
|
|
|
@@ -51,6 +52,29 @@ import { Sidebar } from "@hjmds/react/sidebar";
|
|
|
51
52
|
/>
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
Next.js App Router 같은 client router에서는 `renderLink`로 항목 링크를 넘긴다(개발 소스·미게시). 함수 prop이라
|
|
56
|
+
Sidebar를 그리는 제품 컴포넌트는 Client Component여야 한다(현재 경로로 `currentId`를 고르는 셸이 보통 이미 그렇다).
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Web (Next.js App Router, "use client" 파일)
|
|
60
|
+
import NextLink from "next/link";
|
|
61
|
+
import { usePathname } from "next/navigation";
|
|
62
|
+
import { Sidebar } from "@hjmds/react/sidebar";
|
|
63
|
+
import type { SidebarItemDescriptor } from "@hjmds/design-contracts/components/sidebar";
|
|
64
|
+
|
|
65
|
+
export function ProductNav({ items }: { items: readonly SidebarItemDescriptor<RouteId>[] }) {
|
|
66
|
+
const pathname = usePathname();
|
|
67
|
+
return (
|
|
68
|
+
<Sidebar
|
|
69
|
+
size="compact"
|
|
70
|
+
descriptor={{ accessibilityLabel: t("web.nav"), currentId: routeIdOf(pathname), groups: [{ id: "main", items }] }}
|
|
71
|
+
renderLink={(props) => <NextLink {...props} />}
|
|
72
|
+
renderIcon={(item) => <NavGlyph id={item.id} />}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
54
78
|
Native: 없음. 폰은 BottomNavigation, 태블릿 split view는 navigator가 맡는다.
|
|
55
79
|
|
|
56
80
|
## 축과 기본값
|
|
@@ -61,11 +85,13 @@ Native: 없음. 폰은 BottomNavigation, 태블릿 split view는 navigator가
|
|
|
61
85
|
| --- | --- | --- | --- |
|
|
62
86
|
| `descriptor` | `{ accessibilityLabel: string; currentId: Id \| null; groups: readonly { id, label?, items }[] }` | 필수 | 그룹 `label`이 없으면 구분선만 있는 묶음 |
|
|
63
87
|
| 항목 | `{ id, label: string, destination?: { kind: "internal" \| "external"; href: string }, badgeCount?: number, disabled? }` | — | `destination`이 없으면 `href` 없는 `<a>`(아래 함정) |
|
|
64
|
-
| `collapsed` · `defaultCollapsed` | `boolean` | `false` | 접힘 폭 72,
|
|
88
|
+
| `collapsed` · `defaultCollapsed` | `boolean` | `false` | 접힘 폭 72, 펼침은 `size` |
|
|
89
|
+
| `size` | `regular` 260 · `compact` 220 | `regular` | 펼친 폭 단계(`sidebarRecipe.sizes`). 그룹 없이 짧은 라벨 3~5개인 셸 rail은 `compact`. 개발 소스·미게시 |
|
|
65
90
|
| `onCollapsedChange` | `(collapsed: boolean) => void` | — | 접기 버튼을 누를 때 |
|
|
66
91
|
| `collapseLabels` | `{ collapse: string; expand: string }` | — | 줄 때만 접기 버튼을 그린다 |
|
|
67
92
|
| `appearance` | `standard` · `bounce` · `hook` · `proximity` | `standard` | 장식만 바뀌고 링크 영역·키보드 순서는 같다. provider의 동작 줄이기에서는 움직임이 꺼지고, provider가 없으면 움직임 없이 그린다 |
|
|
68
|
-
| `onNavigate` | `(id: Id) => void` | — | 항목을 누를 때 알림. 이동은 `href
|
|
93
|
+
| `onNavigate` | `(id: Id) => void` | — | 항목을 누를 때 알림. 이동은 `href` 또는 `renderLink`의 router가 한다 |
|
|
94
|
+
| `renderLink` | `(props: SidebarLinkRenderProps) => ReactElement` | 일반 `<a>` | client router 어댑터. props(`href`·`className`·`aria-current`·접힘 `aria-label`·`onClick`·`children` 등)를 모두 자기 링크에 펼친다. 비활성·`destination` 없는 항목은 어댑터를 거치지 않는다. `BottomNavigation.renderLink`와 같은 계약. 개발 소스·미게시 |
|
|
69
95
|
| `renderIcon` | `(item) => ReactNode` | — | 접힌 레일에 필수 |
|
|
70
96
|
| `renderBadge` | `(count: number, item) => ReactNode` | 숫자 배지 | — |
|
|
71
97
|
| `className` · `layoutStyle` | 문자열 · 배치 전용 style 객체 | — | 루트 배치만. 폭·색은 recipe 소유 |
|
|
@@ -74,7 +100,7 @@ Native: 없음. 폰은 BottomNavigation, 태블릿 split view는 navigator가
|
|
|
74
100
|
|
|
75
101
|
| 항목 | 값 | 근거 |
|
|
76
102
|
| --- | --- | --- |
|
|
77
|
-
| 크기 | 폭
|
|
103
|
+
| 크기 | 폭 단계: 펼침 `regular` 260 · `compact` 220, 접힘 72. 높이는 부모를 채운다(`min-block-size: 100%`). 항목 최소 높이 44(`control.minTouchTarget`), radius `radius.md` 12. 접기 버튼 44×44 | `sidebarRecipe.sizes`·`widths`·`itemMinHeight`·`itemRadius`, `.hjm-sidebar__toggle` |
|
|
78
104
|
| 간격 | 안쪽 위아래 `spacing.sm` 12 · 좌우 `spacing.xs` 8. 그룹 사이 `spacing.md` 16, 항목 사이 `spacing.xxs` 4. 항목 좌우 `spacing.sm` 12, 아이콘·라벨·배지 사이 `spacing.sm` 12. 그룹 제목 좌우 `spacing.sm` 12 · 위아래 `spacing.xxs` 4 | `sidebarRecipe`, `collectionItemContract` |
|
|
79
105
|
| 순서·정렬 | 앱 셸 시작 쪽(LTR 왼쪽) 세로 열. 셸의 열 배치·반응형 전환은 [Layout](layout.md)이 맡는다. 접기 버튼(`collapseLabels`를 줄 때만)이 맨 위 끝 쪽, 그 아래 그룹. 항목은 [아이콘][라벨][배지], 라벨이 남은 폭을 채운다. 끝 쪽 경계선 1px | `.hjm-sidebar`, `src/sidebar.tsx` |
|
|
80
106
|
| 고정·스크롤 | 항목이 넘치면 Sidebar 자체가 스크롤된다(`overflow: auto`) | `.hjm-sidebar` |
|
|
@@ -102,8 +128,10 @@ Native: 없음. 폰은 BottomNavigation, 태블릿 split view는 navigator가
|
|
|
102
128
|
|
|
103
129
|
## 함정
|
|
104
130
|
|
|
105
|
-
- 항목은 `href`가 있는 일반 `<a>`이고 클릭 시 기본 이동을 막지 않는다. `onNavigate`는 알림 콜백이지
|
|
106
|
-
이동을 대신하지 않는다. client router 전환(Next.js 등)
|
|
131
|
+
- `renderLink` 없이 항목은 `href`가 있는 일반 `<a>`이고 클릭 시 기본 이동을 막지 않는다. `onNavigate`는 알림 콜백이지
|
|
132
|
+
이동을 대신하지 않는다. client router 전환(Next.js 등)은 `renderLink`로 연결하고, `onNavigate`에서
|
|
133
|
+
`preventDefault`로 이동을 가로채지 않는다.
|
|
134
|
+
- 어댑터가 props 일부만 넘기면 현재 위치·접힘 이름·비활성 차단이 빠진다. `{...props}`를 그대로 펼친다.
|
|
107
135
|
- `destination`을 빼면 `href` 없는 `<a>`가 되어 키보드 초점을 받지 못한다. 이동 항목에는 `destination`을 둔다.
|
|
108
136
|
|
|
109
137
|
### 프로필 모서리·제목 소비
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
|
-
- 적용: 1.
|
|
7
|
-
- 검토일: 2026-10-
|
|
6
|
+
- 적용: 미게시(1.41.2 이후)
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [Steps](../../steps.md), [StepPlayer](../../step-player.md), `src/steps.ts`(`stepsRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/탐색/단계 탐색`, `배포/컴포넌트/상태와 알림/단계별 진행 표시`
|
|
10
10
|
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
가입·주문 처리 단계가 여기에 속한다. 순서가 있는 설계 설명은 `navigationMode: "free"`로 자유롭게 탐색할 수 있다. 기본은 표시 전용이고, `onStepPress`를 주면 **이미 지난 단계만** 눌러 그 단계로
|
|
15
15
|
돌아갈 수 있다(1.31.0, 단계형 작성).
|
|
16
16
|
|
|
17
|
+
기본 `focus`는 현재 번호만 중립색 원으로 채우고 완료 체크·예정 번호의 무게를 낮춘다.
|
|
18
|
+
본문 공간이 필요하면 `compact`, 현재 단계 본문을 같은 목록 안에서 펼치려면 `vertical`을 선택한다.
|
|
19
|
+
|
|
17
20
|
## 쓰지 않을 때
|
|
18
21
|
|
|
19
22
|
| 상황 | 대신 쓸 것 |
|
|
@@ -75,7 +78,10 @@ import { Steps } from "@hjmds/react-native/steps";
|
|
|
75
78
|
| `descriptor.steps[]` | `{ id, label, description? }` | — | — |
|
|
76
79
|
| `statusLabels` | `{ pending, current, complete, error }`(모두 `string`) | — (필수) | 상태를 읽는 문구 |
|
|
77
80
|
| `composeAccessibleName` | `(info: { position: number, total: number, label: string }) => string` | — (필수) | `position`은 1부터 |
|
|
78
|
-
| `
|
|
81
|
+
| `presentation` | `focus` · `compact` · `vertical` | `focus` | 같은 커서와 이동 규칙을 세 표현으로 제공 |
|
|
82
|
+
| `overviewLabel` | `string` | — | compact의 전체 단계 펼침 버튼에 쓰는 제품 번역 문구. compact에서는 필수 |
|
|
83
|
+
| `renderStepContent` | `(id) => ReactNode` | 없음 | vertical 전용 현재 단계 본문 슬롯. 다른 단계는 접힘; 초안은 상위 제품 상태로 보관 |
|
|
84
|
+
| `renderMark` | `(status: StepStatus, position: number) => ReactNode` | `✓`·`!`·번호 | 원 안 표시를 바꾸며 제품 그림 공간 44를 보존한다 |
|
|
79
85
|
| `onStepPress` | `(id) => void` | 없음 | 1.31.0. 기본 linear에서 커서 앞 단계(`revisitable`, 상태는 늘 `complete`)만 원·라벨 전체가 한 버튼이 되고 누르면 그 `id`를 넘긴다. linear의 현재·다음 단계는 표시 전용으로 남는다. free에서는 현재·다음도 버튼이다. 버튼 이름은 `composeAccessibleName` 결과, 상태 문구는 설명(Web `aria-describedby`·Native `accessibilityHint`). 흐름이 잠긴 동안(제출 중)은 prop을 빼면 모두 표시 전용이 된다 |
|
|
80
86
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
|
|
81
87
|
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
|
|
@@ -83,17 +89,26 @@ import { Steps } from "@hjmds/react-native/steps";
|
|
|
83
89
|
| `StepPlayer` `onPlayingChange`·`onReplay` | `(playing: boolean) => void`·`() => void` | — (필수) | 재생/일시정지·다시 보기 요청 |
|
|
84
90
|
| `StepPlayer` `labels` | `{ play, pause, replay, progress }` | — (필수) | — |
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
`focus`는 가로 레일, `compact`는 현재 순서·라벨·상태와 4 높이 분할 막대, `vertical`은 세로 레일이다.
|
|
93
|
+
compact는 `overviewLabel` 버튼으로 같은 단계 목록을 펼친 뒤 지난 단계를 누를 수 있다.
|
|
94
|
+
vertical의 `renderStepContent(id)`는 현재 커서에만 표시되며 버튼 내부에 중첩되지 않는다.
|
|
95
|
+
잘못된 presentation, 비어 있는 compact overviewLabel, vertical 외 renderStepContent는 오류다.
|
|
96
|
+
linear는 지난 단계만 돌아가고 free는 설명 읽기에서 모든 단계를 선택한다. handler를 생략하면 표시 전용이다.
|
|
97
|
+
제품은 커서와 초안을 controlled state로 소유한다. 예: `presentation="compact" overviewLabel={t("allSteps")}`,
|
|
98
|
+
`presentation="vertical" renderStepContent={id => renderControlledForm(id)}`.
|
|
99
|
+
실행 예제는 Web·Native의 `showcase/*/src/components/steps-presentation-preview.tsx`에 있다.
|
|
87
100
|
|
|
88
101
|
## 배치
|
|
89
102
|
|
|
90
103
|
| 항목 | 값 | 근거 |
|
|
91
104
|
| --- | --- | --- |
|
|
92
|
-
| 크기 | Web은 연결선이 있는 앞 단계가 남은 폭을 균등하게 나누고 마지막 단계는 내용 폭(전체 폭/단계 수 이내)만 사용한다. 첫·마지막 표지는 양끝에 맞춘다. Native도 마지막 칸은 내용 폭만 사용한다. 원(indicator) 44(`glyph.xxl`), 연결선 1(`stroke.default`),
|
|
93
|
-
| 간격 | 좌우 안쪽 여백 `spacing.md` 16(`stepsRecipe.paddingInline`), 원과 글자 사이 `spacing.xs` 8(`stepsRecipe.gap`) | `stepsRecipe.gap` |
|
|
94
|
-
| 순서·정렬 | 흐름 화면의 맨 위(머리 아래, 본문 위)에 가로
|
|
105
|
+
| 크기 | Web은 연결선이 있는 앞 단계가 남은 폭을 균등하게 나누고 마지막 단계는 내용 폭(전체 폭/단계 수 이내)만 사용한다. 첫·마지막 표지는 양끝에 맞춘다. Native도 마지막 칸은 내용 폭만 사용한다. 숫자 원(indicator) 32(`glyph.xl`), 제품 그림은 44(`glyph.xxl`), 연결선 1(`stroke.default`), 오류 원 테두리 2(`stroke.strong`), 현재 원은 중립 채움과 바깥 링. compact 막대 높이 4(`spacing.xxs`). 누를 수 있는 단계는 칸 전체가 버튼이고 최소 높이 44(`stepsRecipe.press.minTouchTarget`) | `stepsRecipe`, `styles.css` `.hjm-steps*`, `react-native/src/steps.tsx` |
|
|
106
|
+
| 간격 | 좌우 안쪽 여백 `spacing.md` 16(`stepsRecipe.paddingInline`), 가로 원과 글자 사이 `spacing.xs` 8(`stepsRecipe.gap`), 세로 표지 옆 본문 간격·단계 간격 `spacing.md` 16 | `stepsRecipe.gap` |
|
|
107
|
+
| 순서·정렬 | 흐름 화면의 맨 위(머리 아래, 본문 위)에 배치. focus는 가로 레일, compact는 요약과 펼침 버튼, vertical은 현재 본문을 표지 아래에 배치한다. 화면 아래 행동 버튼과 떨어뜨리고, 같은 수준 화면 사이 이동(탭)으로 쓰지 않는다 | — |
|
|
95
108
|
| 고정·스크롤 | — | — |
|
|
96
|
-
| 좁은 폭·큰 글자 | 라벨·설명은 줄바꿈된다(`overflow-wrap: anywhere`).
|
|
109
|
+
| 좁은 폭·큰 글자 | 라벨·설명은 줄바꿈된다(`overflow-wrap: anywhere`). 이름이 길거나 단계가 많으면 compact 또는 vertical을 선택한다 | `styles.css` `.hjm-steps__label` |
|
|
110
|
+
|
|
111
|
+
상태 전환은 `stepsRecipe.transitionDuration` = `motion.normal` 200ms. Web 표지·연결선 색과 눌림, Native 상태 변경의 짧은 opacity와 눌림으로 표현한다. reduced motion에서는 이동 연출을 생략한다.
|
|
97
112
|
|
|
98
113
|
## 꼭 지킬 것
|
|
99
114
|
|
|
@@ -124,3 +139,9 @@ import { Steps } from "@hjmds/react-native/steps";
|
|
|
124
139
|
2026-10-19 스핀트 실제 화면 피드백: 수평 행 gap으로 끊기던 연결선을 없애고 마지막 표지 앞의 연결 구간을 유지한다. Native도 마지막 열에 균등 여분 폭을 남기지 않아 첫·마지막 표지가 본문 양끝에 맞는다. 내부 문구 간격과 기본 이동 계약은 유지한다.
|
|
125
140
|
|
|
126
141
|
2026-10-21 스핀트 실제 화면 피드백: 24pt 원에 꽃 표시가 붙어 식별하기 어렵고 양끝이 화면 가장자리에 가까웠다. 공통 recipe의 원을 `glyph.xxl` 44pt로 키우고 좌우 안쪽 여백 `spacing.md` 16pt를 두었다. Web·Native에 동일 적용하며 제품 그림은 테두리 안 여유를 두고 배치한다. 개발 소스 변경이며 미게시다.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
2026-10-23 사용자 시안 선택: 포커스 레일을 기본으로, 컴팩트 챕터·펼쳐지는 단계를 옵션으로 요청했다.
|
|
145
|
+
원·연결선·완료와 현재의 시각 무게가 비슷하던 문제를 공통 recipe에서 고친다. 현재는 `content.primary` 채움과 `canvas` 글자,
|
|
146
|
+
완료·도달선은 중립색, 오류는 danger다. 숫자만 32로 줄이고 Spint 꽃 그림의 44 공간과 터치 최소값은 보존한다.
|
|
147
|
+
앞선 44 숫자·초록 완료·브랜드 현재 테두리 기본 표현을 대체하는 미게시 개발 변경이다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: `src/base-recipes.ts`(`surfaceRecipe`·`surfaceDefaults`·`surfaceGeometry`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/레이아웃/배경 영역`
|
|
10
10
|
|
|
@@ -65,6 +65,7 @@ import { Surface } from "@hjmds/react-native/primitives";
|
|
|
65
65
|
| `tone` | `default` · `raised` · `sunken` · `accent` · `subtle` · `selected` | `default` | `raised`는 그림자, `accent`는 primary 색 테두리. `selected`는 `surfaceSelected` 배경, 나머지는 테마 `bg` 바탕에 테두리·그림자로 위계를 나타낸다 |
|
|
66
66
|
| `padding` | `none` 또는 spacing 토큰(`xxs`~`xxxl`) | `none` | — |
|
|
67
67
|
| `radius` | `sm` · `md` · `lg` · `xl` · `full` | `lg`(16) | — |
|
|
68
|
+
| `elevation` | `none` · `raised` · `floating` · `overlay` | tone이 정함(`raised` tone → `raised`, 나머지 `none`) | 그림자 토큰 단계([그림자와 투명도](../tokens/elevation-opacity.md)). `none`은 raised tone의 그림자도 뺀다. 그림자가 있는 면은 내용을 자르지 않는다(Native `overflow: visible`). 떠 있는 안내·받침에 쓰고 className `shadow-*`나 감싸는 View로 그림자를 주지 않는다(미게시, 2026-10-23) |
|
|
68
69
|
| `bordered` | `boolean` | 생략 시 테두리 그림 | 모든 tone이 `borderAlways`. `bordered={false}`로만 끈다 |
|
|
69
70
|
| 자식 자르기 | — | `raised` 외 자름 | `raised`를 뺀 tone은 둥근 모서리 밖으로 넘친 자식을 자른다. `raised`는 그림자가 잘리지 않게 자르지 않는다 |
|
|
70
71
|
| `as`(Web) | `div` · `section` · `article` | `div` | — |
|
|
@@ -75,8 +75,9 @@ import { TextArea } from "@hjmds/react-native/inputs";
|
|
|
75
75
|
| `description` · `error` · `required` · `disabled` | Web `ReactNode`, Native `string` · `boolean` | — | Field와 같은 틀로 그려지고 보조기기 설명에 연결된다 |
|
|
76
76
|
| `busy`(Native) | `boolean` | `false` | — |
|
|
77
77
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 필드 전체(라벨·틀·설명) 배치. Web·Native 모두 |
|
|
78
|
+
| `characterCount` | `CharacterCountOptions` | — | 미게시. 틀 아래 글자 수 행(`n/max`와 상태 문장)을 그린다. [글자 수 표시](field.md#글자-수-표시-미게시) 참조. TextField와 같다 |
|
|
78
79
|
|
|
79
|
-
`leading`(흐린 장식 affix)은 TextArea에 없다(TextField 전용). 행동은 `leadingAction`·`trailing`에
|
|
80
|
+
`leading`(흐린 장식 affix)은 TextArea에 없다(TextField 전용). 행동은 `leadingAction`·`trailing`에 둔다. 글자 수는 `characterCount`로 그리고 제품이 `n/max` 행을 따로 만들지 않는다.
|
|
80
81
|
|
|
81
82
|
## 배치
|
|
82
83
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [Timeline](../../timeline.md), `src/timeline.ts`(`timelineRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/데이터 표시/타임라인`
|
|
10
10
|
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
이미 일어난 일을 시간 순서대로 보여 줄 때 쓴다. 경기 플레이 기록, 이적·변경 이력, 주문 처리
|
|
14
14
|
기록처럼 "무슨 일이 언제 있었나"를 위→아래 한 방향으로 나열한다. 현재 위치(커서)는 없다.
|
|
15
|
+
기록 하나를 골라 옆의 지도·상세를 바꿔야 하면 `selectedId`/`onSelectedIdChange`로 기록 자체를 선택 대상으로 만든다(개발 소스, 미게시).
|
|
15
16
|
|
|
16
17
|
## 쓰지 않을 때
|
|
17
18
|
|
|
18
19
|
| 상황 | 대신 쓸 것 |
|
|
19
20
|
| --- | --- |
|
|
20
21
|
| 흐름에서 지금 어디에 있는지(현재 단계) | [Steps](steps.md) |
|
|
21
|
-
| 항목을 눌러
|
|
22
|
+
| 항목을 눌러 다른 화면으로 이동 | [List](list.md), [ListRow](list-row.md) |
|
|
23
|
+
| 기록 옆에 번호·선택 `Button`을 두고 `tone`·`aria-pressed`로 선택을 흉내 | `selectedId`/`onSelectedIdChange`. 선택 표현·키보드·접근성 의미는 Timeline이 소유한다 |
|
|
22
24
|
| 항목이 하나도 없음 | Timeline을 마운트하지 않고 [EmptyState](empty-state.md) |
|
|
23
25
|
| 과거 기록을 더 불러옴 | Timeline 아래에 [LoadMore](load-more.md)를 합성 |
|
|
24
26
|
| 항목별 짧은 배지 | `renderMetadata` 슬롯에 제품의 [Badge](badge.md) 또는 [Tag](tag.md)를 연결 |
|
|
@@ -77,7 +79,9 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
77
79
|
| `items[].tone` | `neutral` · `info` · `success` · `attention` | `neutral` | `warning`·`danger`는 없다 |
|
|
78
80
|
| `composeAccessibleName`(필수) | `(info: { position: number, total: number, label: string }) => string` | — | position은 1부터 |
|
|
79
81
|
| `renderMetadata` | `(item: TimelineItemDescriptor) => ReactNode` | — | 제목 줄의 수동적 배지·메타데이터 |
|
|
80
|
-
| `renderActions` | `(item: TimelineItemDescriptor) => ReactNode` | — | 기록과 독립된 버튼. 행 전체를 누름 대상으로 만들지
|
|
82
|
+
| `renderActions` | `(item: TimelineItemDescriptor) => ReactNode` | — | 기록과 독립된 버튼. 행 전체를 누름 대상으로 만들지 않는다. 선택형 Timeline과 함께 쓰면 오류 |
|
|
83
|
+
| `selectedId` | `Id \| null` | — (수동 기록) | 주면 기록 하나를 고르는 제어형 선택이 된다. `null`은 선택 가능하지만 아직 고르지 않은 상태. 목록 밖 id는 오류. `onSelectedIdChange`와 함께 준다(개발 소스, 미게시) |
|
|
84
|
+
| `onSelectedIdChange` | `(id: Id) => void` | — | 클릭·탭, Web의 Enter/Space와 ↑/↓/Home/End(끝에서 순환하지 않고 선택이 포커스를 따른다)로 고른 기록 id. 이미 선택된 기록은 다시 알리지 않는다 |
|
|
81
85
|
| `renderList`(Native) | `({ items, renderItem, separator }) => ReactNode` | — | FlatList 등 가상화 host. 전달받은 renderItem과 separator를 사용하며 레일·본문을 다시 그리지 않는다 |
|
|
82
86
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
|
|
83
87
|
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
|
|
@@ -90,6 +94,7 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
90
94
|
| 크기 | 각 항목은 [레일 16][본문] 두 열. 레일은 지름 10의 dot과 다음 항목까지 이어지는 1px connector. 기록 본문은 누름 대상이 아니며 `renderActions`가 있으면 별도의 행동 열을 추가한다 | `timelineRecipe`, `.hjm-timeline__item`·`__dot`·`__connector` |
|
|
91
95
|
| 간격 | 항목 사이 `spacing.lg` 24, 각 항목 안쪽 `spacing.sm` 12, 레일과 본문 열 간격 `spacing.sm` 12, 본문 줄 간격 `spacing.xxs` 4 | `timelineRecipe.gap`, `.hjm-timeline*` |
|
|
92
96
|
| 순서·정렬 | 본문은 label·metadata → description → timestamp 순서다. dot은 label 첫 줄의 가운데에, 독립 행동은 기록 본문 높이의 가운데에 둔다. 강조 배경은 명시적 emphasized에만 적용한다 | `.hjm-timeline__heading`·`__dot`·`__timestamp`, Native `Timeline` |
|
|
97
|
+
| 선택 표현 | 선택형이면 기록 전체(안쪽 여백 12 포함)가 누름 대상이다. 선택된 기록은 `timelineRecipe.item.selected`의 `selection.background` 면과 `selection.border` 1 테두리(ListRow 선택과 같은 중립 선택 면)를 그린다. 강조(`emphasized`)보다 선택이 우선한다. Native는 모든 선택형 기록이 투명 1 테두리를 안쪽 여백 안에 가져 선택해도 글자가 움직이지 않는다 | `timelineRecipe.item.selected`, `.hjm-timeline__item[aria-selected="true"]` |
|
|
93
98
|
| 고정·스크롤 | 본문 스크롤 영역 안의 한 섹션으로 폭을 꽉 채운다. 더 불러오기는 Timeline 아래에 [LoadMore](load-more.md) | — |
|
|
94
99
|
| 좁은 폭·큰 글자 | 제목과 metadata는 좁으면 줄바꿈하고 timestamp는 별도 줄에 둔다 | `.hjm-timeline__heading`·`__label` |
|
|
95
100
|
|
|
@@ -99,6 +104,8 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
99
104
|
- 빈 배열, 중복 `id`, 앞뒤 공백이 있는 `id`, 빈 문자열 필드, 목록 밖 tone은 실행 중 오류를 던진다.
|
|
100
105
|
- 접근 이름의 어순은 언어마다 다르므로 `composeAccessibleName`은 i18n 키로 만든다.
|
|
101
106
|
- 제품 고유 색(구단 색 등)은 adapter에서 네 tone 중 하나로 먼저 매핑한다. dot 색을 직접 칠하지 않는다.
|
|
107
|
+
- 선택형 Web Timeline에는 `aria-label` 또는 `aria-labelledby`(listbox 이름)를 준다. 없으면 오류다.
|
|
108
|
+
- 기록 번호를 보여야 하면 `composeAccessibleName`과 `renderMetadata`로 연결한다. 선택 버튼을 따로 만들지 않는다.
|
|
102
109
|
- 배치는 `layoutStyle`로 한다. Web `className`/`style`과 Native에서 제거된 `style`로 dot·connector·글자 색을 덮지 않는다.
|
|
103
110
|
|
|
104
111
|
## 플랫폼 차이
|
|
@@ -108,6 +115,8 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
108
115
|
| 구조 | `<ol>` / `<li>`, dot·connector `aria-hidden` | `View` 행, rail·본문은 접근성 트리에서 숨김 |
|
|
109
116
|
| 항목 접근 이름 | `<li aria-label={accessibleName}>` | `accessibleName, description, timestamp`을 이어 붙인 `accessibilityLabel` |
|
|
110
117
|
| ref | `HTMLOListElement`로 전달 | 없음(함수 컴포넌트) |
|
|
118
|
+
| 선택형 의미 | `<ol role="listbox" aria-orientation="vertical">` / `<li role="option" aria-selected>`, description·timestamp는 `aria-describedby`. 선택된 기록(없으면 첫 기록)만 Tab 정지점 | 기록마다 `Pressable` `accessibilityRole="button"` + `accessibilityState.selected` |
|
|
119
|
+
| 선택형 키보드 | ↑/↓ 한 칸, Home/End 처음·끝, Enter/Space 현재 기록 선택. 선택이 포커스를 따른다 | 보조기기 탐색과 탭 |
|
|
111
120
|
|
|
112
121
|
## 근거
|
|
113
122
|
|
|
@@ -119,4 +128,16 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
119
128
|
|
|
120
129
|
2026-10-19 사용자가 비교 시안 A 기록형을 선택했다. 양 renderer에 기록별 emphasized, 설명 아래 시간, 24px 기록 간격·12px 안쪽 여백과 이어지는 레일을 반영했다. 현재 버전의 의미·문구·시간 포맷은 제품이 연결하며 다른 기록을 앞으로 진행할 단계로 해석하지 않는다.
|
|
121
130
|
|
|
122
|
-
2026-10-19 다에리 여정 일지의 지도 연결 기록은 Timeline의 레일·본문·시각과 독립적인 renderActions 지도 선택 Button을
|
|
131
|
+
2026-10-19 다에리 여정 일지의 지도 연결 기록은 Timeline의 레일·본문·시각과 독립적인 renderActions 지도 선택 Button을 사용했다(2026-10-23 아래 선택 축으로 대체). 원래 이벤트 시각을 보존하도록 선택적 items[].dateTime을 추가했다(Web time.dateTime; Native 표시 문구는 기존 timestamp). dateTime은 timestamp와 함께 사용한다. 이 변경도 개발 소스이며 미게시다.
|
|
132
|
+
|
|
133
|
+
2026-10-23 소비 감사: 다에리 웹 `JourneyPanel`과 앱 `JourneyLog`가 단계 번호를 `renderActions`의 `Button` tone 토글 +
|
|
134
|
+
`aria-pressed`/`accessibilityState.selected`로 나열해 지도 위치를 골랐다. [선택 지침](../SELECTION.md#조립-규칙필수)이 금지한
|
|
135
|
+
버튼 선택 흉내다. 기록 자체를 고르는 `selectedId`/`onSelectedIdChange`를 추가해 선택 표현(중립 선택 면)·키보드(listbox)·
|
|
136
|
+
보조기기 의미를 HJM이 소유한다. 선택은 진행 커서가 아니며 tone·connector 색을 바꾸지 않는다. 개발 소스이며 미게시다.
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// 다에리 이관 예: 번호 Button 대신 기록을 고른다
|
|
140
|
+
<Timeline aria-label={t("log.a11y", { count })} items={timelineRows} composeAccessibleName={compose}
|
|
141
|
+
selectedId={rows[selectedStep]?.id ?? null} onSelectedIdChange={id => setSelectedStep(indexOf(id))}
|
|
142
|
+
renderMetadata={renderIconAndProgress} />
|
|
143
|
+
```
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [Top](../../top.md), `src/top.ts`(`topRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/레이아웃/화면 제목과 설명`
|
|
10
10
|
|
|
@@ -55,7 +55,8 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
55
55
|
|
|
56
56
|
| prop | 값 | 기본값 | 설명 |
|
|
57
57
|
| --- | --- | --- | --- |
|
|
58
|
-
| `descriptor` | `{ title: string, eyebrow?: string, description?: string, size?, headingLevel? }` | — | 값을 주면 공백만 있는 문자열은 `TypeError` |
|
|
58
|
+
| `descriptor` | `{ title: string, eyebrow?: string, description?: string, size?, headingLevel?, titleLineBreaks? }` | — | 값을 주면 공백만 있는 문자열은 `TypeError` |
|
|
59
|
+
| `descriptor.titleLineBreaks` | `collapse` · `preserve` | `collapse` | 제목 안 `\n`. `collapse`는 공백으로 바꿔 폭에 따라 감고, `preserve`는 제품이 의도한 줄바꿈을 지킨다(Web `white-space: pre-line`, Native `\n`). 줄 앞뒤 공백과 빈 줄은 지운다. 두 값 모두 제목 이름은 줄을 공백 하나로 이은 한 문장이다(`resolveTopTitle`, 개발 소스·미게시) |
|
|
59
60
|
| `descriptor.size` | `large` · `medium` | `large` | `large`는 화면 첫 제목, `medium`은 시트·모달 안. 세 번째 크기는 없다 |
|
|
60
61
|
| `descriptor.headingLevel` | `1` · `2` · `3` | `1` | 한 라우트가 여러 화면을 담을 때만 낮춘다(Web만 반영) |
|
|
61
62
|
| `trailing` | `ReactNode`(요소 하나) | — | 제목 줄을 같이 쓰는 보조 행동. 공간이 모자라거나 큰 글자면 아래로 내려간다 |
|
|
@@ -86,6 +87,7 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
86
87
|
| --- | --- | --- |
|
|
87
88
|
| 제목 요소 | 실제 `h1`~`h3`(`headingLevel`) | `accessibilityRole="header"`(단계 없음, `headingLevel` 미사용) |
|
|
88
89
|
| `trailing` 내림 기준 | CSS 줄바꿈 | 글자 배율 1.6 이상이면 세로로 쌓음 |
|
|
90
|
+
| 줄바꿈 제목의 이름 | heading 내용의 공백 정규화로 한 문장 | `accessibilityLabel`에 줄을 공백으로 이은 문장 |
|
|
89
91
|
| ref | `forwardRef`(`header` 요소) | 없음 |
|
|
90
92
|
|
|
91
93
|
### 프로필 모서리·제목 소비
|
|
@@ -93,3 +95,14 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
93
95
|
2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level3`(24, 2026-10-17 큰 글자 단계 폐지 전에는 `level2` 32), `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
|
|
94
96
|
|
|
95
97
|
프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
|
|
98
|
+
|
|
99
|
+
## 근거
|
|
100
|
+
|
|
101
|
+
- `titleLineBreaks`(2026-10-23 소비 감사): 포트폴리오 사이트 히어로가 의도한 두 줄 제목(`"…제품으로,\n제품을…"`)을 `Heading` 안
|
|
102
|
+
`white-space: pre-line` span으로 그려 Top을 채택하지 못했다. 같은 `\n`을 Web은 공백으로, Native는 줄바꿈으로 그려 두 플랫폼도 달랐다.
|
|
103
|
+
줄 배열 대신 문자열 옵션을 고른 이유는 번역 문자열이 이미 `\n`을 담고 있고, 번역마다 줄 수가 달라도 제품이 쪼갤 필요가 없어서다.
|
|
104
|
+
기본 `collapse`로 Native도 Web처럼 공백으로 바꾼다. 의도한 줄바꿈은 `preserve`로만 남는다.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<Top descriptor={{ eyebrow: c.heroRole, title: c.heroTitle, description: c.heroBody, titleLineBreaks: "preserve" }} />
|
|
108
|
+
```
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# 채팅방 관리
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.43.0
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
|
+
- 근거: Telegram·Signal 실제 화면 비교와 채팅방 관리 공통화 요청
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/채팅방 관리`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
`@hjmds/react/communication`, `@hjmds/react-native/communication`에서 제공한다.
|
|
14
|
+
기존 ChatScreen은 메시지 기록·작성창의 화면 틀이다. 이 구성은 방 정보·개설·멤버 관리라는 별도 작업을 소유하며, ChatScreen 헤더의 방 정보 행동에서 연다.
|
|
15
|
+
|
|
16
|
+
## 구성 요소
|
|
17
|
+
|
|
18
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Avatar | 방·참여자 사진 | [Avatar](../components/avatar.md) |
|
|
21
|
+
| TextField | 이름·소개 초안 | [TextField](../components/text-field.md) |
|
|
22
|
+
| Menu | 멤버별 허용 행동 | [Menu](../components/menu.md) |
|
|
23
|
+
| AlertDialog | 나가기·삭제·권한 변경 확인 | [AlertDialog](../components/alert-dialog.md) |
|
|
24
|
+
|
|
25
|
+
- `ChatRoomDetails`: 가운데 방 사진·이름·인원·소개 → 편집 → 검색·공유 미디어·알림·초대 도구 → 참여자 → 파괴 행동 순서.
|
|
26
|
+
- `ChatRoomProfile`: 사진 선택/제거 → 이름·소개 → 저장. 제품이 제어하는 draft를 받는다.
|
|
27
|
+
- `ChatRoomCreate`: 참여자 검색·선택 후 사진·이름·소개를 설정하는 두 단계를 제공한다. 빈 이름이나 참여자 없는 생성은 막는다.
|
|
28
|
+
- `ChatRoomMembers`: 아바타·이름·역할 설명, 선택 또는 멤버별 앵커 메뉴. 권한이 있는 행동만 전달한다.
|
|
29
|
+
- `ChatRoomInvite`: 아직 참여하지 않은 사람을 선택하고 초대한다. disabled 멤버는 요청에서 제외한다.
|
|
30
|
+
- `ChatRoomActions`: 내 기록 삭제, 방 나가기, 방 자체 삭제를 별도 확인으로 처리한다. 관리 권한이 없는 삭제는 전달하지 않는다.
|
|
31
|
+
|
|
32
|
+
## 배치
|
|
33
|
+
|
|
34
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| 바깥 틀 | Stack | 부모 화면 본문 | gap xl |
|
|
37
|
+
| 방 정보 | Avatar·Text | 상단 중앙 | Avatar xlarge 64, gap sm |
|
|
38
|
+
| 참여자 | Avatar·Checkbox·Menu | 본문 | gap md |
|
|
39
|
+
| 나가기·삭제 | ListRow·AlertDialog | 최하단 | 기존 row recipe |
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
[방 사진 xlarge: 64]
|
|
43
|
+
방 이름 · 인원
|
|
44
|
+
[뒤로] [정보 편집]
|
|
45
|
+
소개
|
|
46
|
+
[검색] [사진·파일] [알림] [초대]
|
|
47
|
+
참여자
|
|
48
|
+
[사진] 이름 · 역할 [메뉴]
|
|
49
|
+
채팅방 나가기
|
|
50
|
+
내 대화 기록 삭제
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Stack gap은 그룹 `xl`, 입력/목록 `md`, 내부 `sm`이다. Avatar·Button·ListRow·Menu·AlertDialog의 기존 recipe를 재사용한다.
|
|
54
|
+
최종 삭제 확인은 danger AlertDialog이며 취소·pending·실패·중복 확인 방지는 기존 공통 동작을 따른다.
|
|
55
|
+
|
|
56
|
+
## 흐름과 상태
|
|
57
|
+
|
|
58
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| 기본 | 방 정보와 권한별 도구 | 화면 host가 진입을 소유 |
|
|
61
|
+
| 진행 중 | 요청 버튼 busy, 편집 잠금 | 중복 요청 방지 |
|
|
62
|
+
| 실패 | 입력 유지, 오류 문구 | alert 안내 후 재시도 가능 |
|
|
63
|
+
|
|
64
|
+
1. 방 정보에서 편집 또는 초대를 연다.
|
|
65
|
+
2. 제품이 사진·이름·멤버 초안을 제어한다.
|
|
66
|
+
3. 성공 응답 뒤 제품이 닫기·이동을 결정한다.
|
|
67
|
+
|
|
68
|
+
사진 선택/업로드는 `onPickPhoto`에서 제품이 수행하고 저장 가능한 URL을 반환한다. 취소는 null이다. 미디어 권한·서버 업로드를 HJM이 추정하지 않는다. `onValueChange`는 초안 변경, `onSave`/`onCreate`/`onInvite`의 resolve는 서버 성공을 뜻한다. 실패는 reject하며 초안을 지우지 않는다. 서버 결과를 받은 제품이 닫힘·이동·성공 안내를 결정한다.
|
|
69
|
+
|
|
70
|
+
방을 바꿀 때 구성에 `key={roomId}`를 지정한다. 방 데이터·사진 URL·구성원 ID·역할·지역화 문구·권한과 마지막 관리자 이탈 정책은 제품이 소유한다. 서버에서도 권한을 검증해야 한다. 모든 버튼을 노출하는 권한 기본값은 없다.
|
|
71
|
+
`deleteHistory`는 내 기록 삭제, `leave`는 멤버십 탈퇴, `deleteRoom`은 전체 방 삭제다. 하나를 다른 기능의 대체로 연결하지 않는다.
|
|
72
|
+
검색/공유 미디어/알림은 `tools`가 실제 제품 경로로 연결한다. 검색 엔진이나 알림 서버가 이 구성에 포함됐다는 뜻이 아니다.
|
|
73
|
+
|
|
74
|
+
## 코드 골격
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// Web
|
|
78
|
+
<ChatRoomProfile key={roomId} value={draft} onValueChange={setDraft}
|
|
79
|
+
onSave={saveRoomProfile} onPickPhoto={pickAndUploadRoomPhoto} labels={labels} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// Native
|
|
84
|
+
<ChatRoomProfile key={roomId} value={draft} onValueChange={setDraft}
|
|
85
|
+
onSave={saveRoomProfile} onPickPhoto={pickAndUploadRoomPhoto} labels={labels} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
실행 가능한 Web 예제: `showcase/web/src/patterns/ChatRoom.preview.tsx`.
|
|
89
|
+
Native 예제: `showcase/native/src/ChatRoom.preview.tsx`.
|
|
90
|
+
기본·정보 편집·단톡방 개설·실패 후 입력 유지·멤버 권한·어두운 테마를 제공한다. 예제의 사진 선택·저장·멤버 변경은 fixture이며 운영 저장이 아니다.
|
|
91
|
+
|
|
92
|
+
## 참고한 흐름
|
|
93
|
+
|
|
94
|
+
2026-10-23 사용자 요청에 따라 [Signal 그룹 관리](https://support.signal.org/hc/en-us/articles/360050427692-Manage-a-group)의 사진·이름·소개·관리자 권한과 [Telegram 공유 미디어](https://telegram.org/blog/shared-media-scrolling-calendar-join-requests-and-more)의 방별 미디어 진입을 참고했다. 색·간격·버튼·모달은 HJM과 제품 테마가 소유한다. [Signal 기록 삭제](https://support.signal.org/hc/en-us/articles/360007320491-Delete-messages-alerts-or-chats)를 참고해 기록 삭제와 나가기를 분리했다.
|
|
95
|
+
|
|
96
|
+
## 실제 화면 비교와 반영 — 2026-10-23
|
|
97
|
+
|
|
98
|
+
문서의 기능 목록만 읽고 첫 시안을 만든 접근을 사용자가 지적했다. 아래 원본 이미지를 브라우저에서 실제로 열어 HJM 웹 390px·기존 iPhone 17 Pro 화면과 비교하고 구성을 다시 만들었다.
|
|
99
|
+
|
|
100
|
+
| 비교 화면 | HJM에서 확인한 차이 | 공통 구현에 흡수한 내용 |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| [Telegram 방 정보·편집 화면](https://img7.thuthuatphanmem.vn/uploads/2023/08/29/chon-members_020222519.jpg) | 초기 시안은 2줄 테두리 버튼, 본문 중앙 편집, 지나친 분산 배치 | 상단 편집, 1줄 동폭 아이콘 타일, 묶음형 참여자·설정 영역. 색은 HJM bg·surface 역할 유지 |
|
|
103
|
+
| [Signal 방 개설 화면](https://www.hardreset.info/media/resetinfo/2021/021/da0f9e34575545b8aca274f144ca3ce0/apps-signal.jpg) | 초기 시안은 사진·이름·전체 멤버를 한 화면에 넣고 하단 생성 | 검색 가능한 참여자 선택 → 사진·이름 설정, 상단 다음·개설·뒤로, 선택과 초안 유지 |
|
|
104
|
+
| [Telegram 답글 원본](https://telegram.org/file/400780400216/2/mfF9maKnA2g.67421/e744b36dd0e0da3840) | 인용 영역과 본문 구분이 약함 | 3px 시작선과 12% 인용 표면색, 누르면 원문 이동하는 기존 replyLink 보존 |
|
|
105
|
+
| Signal 그룹 관리 흐름 | 생성·초대·역할 변경이 화면별 조립에 맡겨짐 | ChatRoomCreate·Invite·Members와 서버 성공 대기, 취소·확인·실패 상태 제공 |
|
|
106
|
+
| 현재 대화 목록 예제 | 작성 버튼이 상태 문구만 변경 | 웹·Native의 실제 단톡방 개설 흐름으로 연결 |
|
|
107
|
+
|
|
108
|
+
사진·검색·미디어·알림·멤버 저장의 실제 서버는 제품이 연결한다. Storybook에서 fixture로 검증한 상태를 제품 운영 기능 완료로 보고하지 않는다. 현재 번뚝은 peer 기반 DM 및 프로젝트 대화, 유틸버스는 단일 도구 대화라 그룹 서버·권한·보존 정책은 별도 제품 구현 지점이다.
|
|
109
|
+
|
|
110
|
+
2026-10-23 사용자 보완: 다른 앱의 배경색·브랜드 팔레트는 복제하지 않는다. 참조 범위는 기능·배치·상호작용이며 화면 바탕은 HJM `bg`, 그룹·도구는 `surface`, 강조와 글자는 기존 의미 토큰과 제품 Provider를 따른다.
|
|
@@ -31,7 +31,7 @@ host: 남은 높이·safe area·키보드
|
|
|
31
31
|
|
|
32
32
|
| 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
|
|
33
33
|
| --- | --- | --- | --- |
|
|
34
|
-
| 작업 | SettingsScreen 공개 행동 슬롯 | Switch 행 자체로 토글;
|
|
34
|
+
| 작업 | SettingsScreen 공개 행동 슬롯 | Switch 행 자체로 토글; 언어·화면 테마는 ChoiceRow 행이 선택 시트를 연다 | 같은 표면에 경쟁하는 primary 하나만 |
|
|
35
35
|
| 복구 | Button·secondary | 오류 근처 | 재시도할 대상과 범위를 표시 |
|
|
36
36
|
|
|
37
37
|
## 상태
|
|
@@ -58,6 +58,7 @@ host: 남은 높이·safe area·키보드
|
|
|
58
58
|
| 지침 | 쓰는 곳 |
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| [SettingsScreen](../components/settings-screen.md) | 필수 props·슬롯·플랫폼 차이 |
|
|
61
|
+
| [ChoiceRow](../components/choice-row.md) | 언어·화면 테마 같은 한 값 선택 설정 |
|
|
61
62
|
| [Button](../components/button.md) | 동작·로딩·보조 행동 |
|
|
62
63
|
| [ScreenLayout](../components/screen-layout.md) | 화면 높이·본문 교체·스크롤 소유 |
|
|
63
64
|
|
|
@@ -67,7 +68,7 @@ host: 남은 높이·safe area·키보드
|
|
|
67
68
|
아래 preview의 메모리 상태를 서버 저장으로 취급하지 않는다. 예제는 읽기용이며 제품에서 showcase를 import하지 않는다.
|
|
68
69
|
|
|
69
70
|
<!-- usage-example: Web ../../../../../showcase/web/src/patterns/settings-page-preview.tsx -->
|
|
70
|
-
- [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen
|
|
71
|
+
- [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen·ChoiceRow·Switch 행·Sheet 조합과 로컬 설정 상태.
|
|
71
72
|
<!-- usage-example: Native ../../../../../showcase/native/src/settings-page-preview.tsx -->
|
|
72
73
|
- [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/settings-page-preview.tsx): 같은 흐름의 Native 공개 API와 이벤트 연결.
|
|
73
74
|
|
|
@@ -41,7 +41,7 @@ host: 남은 높이·safe area·키보드
|
|
|
41
41
|
| 기본 | 선택적 그림 → 권한 이유 → 상태별 행동 | 각 공개 콜백을 제품 상태에 연결 |
|
|
42
42
|
| 로딩 | 최초 조회는 본문 상태, 저장은 해당 행동 pending | 중복 제출 차단; 성공을 먼저 표시하지 않음 |
|
|
43
43
|
| 빈 | 해당 없음: 권한은 목록 0건이 아니라 OS 상태다 | 상태에 맞는 행동을 제공 |
|
|
44
|
-
| 사용 불가 | status=unavailable, 기능 미지원·대체 경로 안내 | skip
|
|
44
|
+
| 사용 불가 | status=unavailable, 기능 미지원·대체 경로 안내 | `alternative`·skip |
|
|
45
45
|
| 오류 | 렌더 시 OS 요청 금지; 설정 복귀 시 제품이 상태 재조회 | 실패 원인과 재시도 경로 제공 |
|
|
46
46
|
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ host: 남은 높이·safe area·키보드
|
|
|
50
50
|
| 상황 | 연결 | 유지·복구 |
|
|
51
51
|
| --- | --- | --- |
|
|
52
52
|
| prompt | request | 사용자 행동에서만 OS 권한 요청 |
|
|
53
|
-
| denied | settings | 설정 앱·브라우저 도움으로 이동; 복귀 시 실제 권한
|
|
53
|
+
| denied | settings, 선택 `recheck` | 설정 앱·브라우저 도움으로 이동; 복귀 시 실제 권한 재조회. 사용자가 직접 다시 확인할 길은 `recheck`(footer secondary) |
|
|
54
54
|
| granted | continueAction | 사용할 기능으로 이동 |
|
|
55
|
-
| unavailable |
|
|
55
|
+
| unavailable | `alternative`(대체 경로) 또는 skip | 권한 재요청을 반복하지 않는다. 대체 경로는 본문 버튼이 아니라 `alternative` |
|
|
56
56
|
| 조회·요청 실패 | notice + 같은 요청 재시도 | denied와 통신·호스트 오류를 같은 상태로 저장하지 않는다 |
|
|
57
57
|
|
|
58
58
|
## 사용하는 지침
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: `src/foundations.ts`(`shadow`·`opacity`·`stateLayer`·`overlay`·`backdrop`·`scrim`), `src/component-contracts.ts`(`floatingSurfaceContract`), `src/component-recipes.ts`(`dialogRecipe`·`sheetRecipe`·`toastRecipe`·`bottomCtaRecipe`), `packages/react/src/theme.ts`, `packages/react/src/styles.css`, `packages/react-native/src/primitives.tsx`
|
|
9
9
|
- 스토리북: `배포/토큰/표면과 움직임/그림자와 투명도`
|
|
10
10
|
|
|
@@ -50,6 +50,17 @@ Native 경로의 이름은 `@hjmds/design-contracts/foundations` import다.
|
|
|
50
50
|
|
|
51
51
|
## 쓰는 법
|
|
52
52
|
|
|
53
|
+
HJM 컴포넌트에는 그림자를 감싸는 View·className으로 주지 않고 `elevation` prop(`"none"` · `"raised"` · `"floating"` · `"overlay"`,
|
|
54
|
+
`ElevationStep`)을 쓴다(2026-10-23). `Surface`는 생략하면 tone이 정하고(`raised` tone → raised), `IconButton`은 기본 `none`이며
|
|
55
|
+
채운 tone(`primary`·`secondary`·`danger`)만 받는다. Native는 같은 단계를 iOS `shadow*`와 Android `elevation`
|
|
56
|
+
(프로필이 없으면 `nativeElevationFallback` 4·8·16)으로 번역한다. 번뚝 `RaisedControl` 받침과 웹 `shadow-lg` className이 이 자리를 대신하던 우회다.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Web — Native도 같은 prop
|
|
60
|
+
<IconButton label={t("burn")} tone="primary" size="large" shape="circle" elevation="floating" onClick={burn}>{icon}</IconButton>
|
|
61
|
+
<Surface tone="default" elevation="floating" padding="sm">{notice}</Surface>
|
|
62
|
+
```
|
|
63
|
+
|
|
53
64
|
```tsx
|
|
54
65
|
// Web
|
|
55
66
|
// 제품 고유 떠 있는 카드: .product-floating { box-shadow: var(--hjm-shadow-floating); }
|
|
@@ -80,7 +91,8 @@ const s = shadow.floating;
|
|
|
80
91
|
| 항목 | Web | Native |
|
|
81
92
|
| --- | --- | --- |
|
|
82
93
|
| 그림자 표현 | box-shadow 문자열(`--hjm-shadow-*`) | iOS `shadow*` 속성 + Android `elevation` |
|
|
83
|
-
| Surface `tone="raised"` | `--hjm-shadow-
|
|
94
|
+
| Surface `tone="raised"` | `--hjm-surface-raised-shadow`(프로필) 또는 `--hjm-shadow-raised` | `shadow.raised` + `elevation` 4 |
|
|
95
|
+
| `elevation` prop(Surface·IconButton) | `--hjm-shadow-<단계>`(Surface raised는 프로필 변수 우선) | 해당 `shadow.<단계>` + `elevation`(프로필 없으면 4·8·16) |
|
|
84
96
|
| HJM 목록 팝업 그림자 | `--hjm-shadow-floating`(Select·Menubar·DatePicker·Menu·Popover) | Modal 기반 메뉴는 해당 플랫폼 표면 계약 |
|
|
85
97
|
|
|
86
98
|
2026-10-06 후속 검수에서 정의만 있던 Web 그림자 변수를 실제 떠 있는 표면에 연결했다. CommandPalette·Tour 같은 큰 강조 표면은 `shadow.overlay`, 일반 popup·Surface는 `shadow.floating`을 쓴다. Native Surface의 별도 blur 6도 floating token으로 맞췄다. 포커스 링·선택 테두리·스위치 손잡이의 inset 표현은 높이 그림자가 아니므로 각 컴포넌트 상태 계약을 유지한다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hjmds/design-contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -739,6 +739,12 @@
|
|
|
739
739
|
"import": "./dist/collection-rail.js",
|
|
740
740
|
"default": "./dist/collection-rail.js"
|
|
741
741
|
},
|
|
742
|
+
"./character-count": {
|
|
743
|
+
"types": "./dist/character-count.d.ts",
|
|
744
|
+
"react-native": "./dist/character-count.js",
|
|
745
|
+
"import": "./dist/character-count.js",
|
|
746
|
+
"default": "./dist/character-count.js"
|
|
747
|
+
},
|
|
742
748
|
"./activity-chip": {
|
|
743
749
|
"types": "./dist/activity-chip.d.ts",
|
|
744
750
|
"react-native": "./dist/activity-chip.js",
|