@hjmds/design-contracts 1.46.1 → 1.48.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/behaviors.d.ts +2 -1
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/bottom-navigation.d.ts +4 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +12 -0
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/card.d.ts +2 -2
- package/dist/card.d.ts.map +1 -1
- package/dist/card.js +3 -2
- package/dist/card.js.map +1 -1
- package/dist/carousel.d.ts +1 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +1 -0
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +151 -31
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +14 -13
- package/dist/catalog.js.map +1 -1
- package/dist/color-picker.d.ts +51 -1
- package/dist/color-picker.d.ts.map +1 -1
- package/dist/color-picker.js +56 -2
- package/dist/color-picker.js.map +1 -1
- package/dist/component-recipes.d.ts +28 -10
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +28 -20
- package/dist/component-recipes.js.map +1 -1
- package/dist/conversation-row.d.ts +1 -1
- package/dist/conversation-row.d.ts.map +1 -1
- package/dist/conversation-row.js +2 -1
- package/dist/conversation-row.js.map +1 -1
- package/dist/data-table.d.ts +12 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +2 -0
- package/dist/data-table.js.map +1 -1
- package/dist/date-range.d.ts +122 -3
- package/dist/date-range.d.ts.map +1 -1
- package/dist/date-range.js +139 -5
- package/dist/date-range.js.map +1 -1
- package/dist/icon.d.ts +2 -1
- package/dist/icon.d.ts.map +1 -1
- package/dist/icon.js +2 -0
- package/dist/icon.js.map +1 -1
- package/dist/recipes.d.ts +1 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -0
- package/dist/recipes.js.map +1 -1
- package/dist/screen-patterns.d.ts +33 -2
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +27 -2
- package/dist/screen-patterns.js.map +1 -1
- package/dist/share-sheet.d.ts +115 -0
- package/dist/share-sheet.d.ts.map +1 -0
- package/dist/share-sheet.js +62 -0
- package/dist/share-sheet.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/bottom-navigation.md +16 -0
- package/docs/color-picker.md +24 -19
- package/docs/dialog.md +2 -3
- package/docs/generated/component-maturity.md +12 -12
- package/docs/generated/renderer-evidence.json +71 -19
- package/docs/generated/renderer-evidence.md +15 -14
- package/docs/generated/showcase-manifest.json +35 -21
- package/docs/optional-adapters.md +21 -5
- package/docs/stable-core.md +37 -0
- package/docs/usage/README.md +14 -10
- package/docs/usage/SELECTION.md +109 -0
- package/docs/usage/components/avatar.md +31 -6
- package/docs/usage/components/badge.md +10 -1
- package/docs/usage/components/bottom-navigation.md +16 -0
- package/docs/usage/components/button.md +11 -0
- package/docs/usage/components/card.md +6 -0
- package/docs/usage/components/chat-message.md +9 -1
- package/docs/usage/components/chat-room-members.md +4 -0
- package/docs/usage/components/chat-screen.md +10 -1
- package/docs/usage/components/color-picker.md +65 -28
- package/docs/usage/components/comment-thread-screen.md +16 -1
- package/docs/usage/components/conversation-list-screen.md +81 -0
- package/docs/usage/components/conversation-row.md +5 -1
- package/docs/usage/components/data-table.md +2 -0
- package/docs/usage/components/date-range-picker.md +15 -5
- package/docs/usage/components/dialog.md +3 -3
- package/docs/usage/components/event-attendance-card.md +4 -0
- package/docs/usage/components/follow-list-screen.md +109 -0
- package/docs/usage/components/follow-profile-card.md +4 -0
- package/docs/usage/components/list-row.md +6 -0
- package/docs/usage/components/menu.md +12 -0
- package/docs/usage/components/notice.md +8 -3
- package/docs/usage/components/notification-inbox-screen.md +10 -0
- package/docs/usage/components/notification-row.md +4 -0
- package/docs/usage/components/page-indicator.md +1 -0
- package/docs/usage/components/profile-summary.md +32 -3
- package/docs/usage/components/section.md +2 -2
- package/docs/usage/components/settings-screen.md +1 -1
- package/docs/usage/components/share-sheet.md +139 -0
- package/docs/usage/components/shared-transition-element.md +1 -1
- package/docs/usage/components/sheet.md +1 -0
- package/docs/usage/components/statistic.md +1 -1
- package/docs/usage/compositions/interaction-adapters.md +2 -2
- package/docs/usage/compositions/optional-adapters.md +2 -2
- package/docs/usage/compositions/web-additions.md +5 -5
- package/docs/usage/screens/common-comments.md +1 -0
- package/docs/usage/screens/common-conversation-list.md +22 -1
- package/docs/usage/screens/common-follow-list.md +87 -0
- package/docs/usage/screens/common-settings.md +23 -0
- package/package.json +7 -1
|
@@ -135,7 +135,7 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
135
135
|
| 반응 메뉴 | Popover(Escape·외부 클릭·포커스 복귀), 우클릭·말풍선 자체의 Enter/Space로도 열림. 시각적으로 숨긴 닫기 버튼은 포커스를 받으면 보인다 | 전체 화면 `Modal`, 접근성 activate로도 열림. 닫기 대상은 표준 `activate` 동작에 답해 VoiceOver·TalkBack 모두 닫을 수 있다 |
|
|
136
136
|
| 낭독 | 메시지 행 `article`의 이름이 `author`, 말풍선 이름이 `picker.label`(반응이 있을 때) | 말풍선은 본문 텍스트로 읽히고 `picker.label`은 힌트. 작성자·시각은 별도 요소 |
|
|
137
137
|
| 키보드·보조기기 답장 | 메시지 포커스 후 Alt+←/→ | 접근성 custom action `reply` |
|
|
138
|
-
| `interactiveContent` | 말풍선을 `group`으로 두고, 안쪽 버튼·링크의 누름·Enter/Space는 반응 메뉴가 아니라 그 컨트롤이 받는다 | 반응이 있으면 별도
|
|
138
|
+
| `interactiveContent` | 말풍선을 `group`으로 두고, 안쪽 버튼·링크의 누름·Enter/Space는 반응 메뉴가 아니라 그 컨트롤이 받는다 | 반응이 있으면 별도 `⋮` IconButton이 메뉴를 열고 `reply` 동작도 가진다. 댓글과 같은 도형 세로 점(`glyph.sm`)을 쓴다(2026-10-25 사용자 통일 요청, 개발 소스). 반응이 없으면 `reply` 동작이 시각 caption(시각이 없으면 작성자 caption)에 붙는다 |
|
|
139
139
|
| `menuAction` | 즉시 실행 | 모달이 닫힌 뒤 실행(iOS 모달 경합 방지) |
|
|
140
140
|
|
|
141
141
|
## 함정
|
|
@@ -171,3 +171,11 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
171
171
|
2026-10-23: 말풍선 메뉴의 `reactions.menuActions`에 수정·삭제를 연결한다. 반응 선택 없이 메뉴만 필요하면 `options: []`를 쓴다. 수정은 같은 `MessageComposer`의 `editTarget`으로 전환하고 제품이 기존 새 메시지 초안·첨부를 보존한다. 저장 성공 전 원문을 덮지 않으며 삭제는 확인 대화상자 이후 서버 확인을 기다린다. 삭제 결과는 안내 말풍선으로 남기고 메뉴·첨부·반응을 제거한다.
|
|
172
172
|
|
|
173
173
|
`deliveryLabel`은 제품 서버가 계산한 수정 상태 또는 단체 대화의 안 읽은 인원 수를 표시한다. 단체 대화의 미확인 수는 `3`처럼 숫자만 전달하며 Web·Native 공통 `caption`(11, 줄 높이 16) 크기로 작게 표시한다. 인원 수가 0이면 읽음 숫자를 전달하지 않는다. 가입·퇴장, 메시지 열람 여부와 기록 저장은 제품 소유다. 조회 성공을 읽음으로 추정하거나 예시의 숫자를 제품에 복사하지 않는다. 웹·네이티브 대화 메시지 스토리에서 수정·삭제의 실패 복구와 단체 읽음 수 감소를 확인할 수 있다.
|
|
174
|
+
|
|
175
|
+
### 작성자 프로필 이동 (2026-10-25, 개발 소스)
|
|
176
|
+
|
|
177
|
+
`avatarAction={{ label: t("profile.open"), onPress: () => openMember(message.authorId) }}`로 사진·이니셜을
|
|
178
|
+
해당 회원의 프로필로 연결한다(Web·Native 동일). Avatar는 표시를 담당하고 ChatMessage가 이름 있는 버튼과
|
|
179
|
+
44px/pt 누르기 영역을 소유한다. 사진은 기존 32px/pt를 유지하고 이동 버튼이 있으면 아바타 열에 44px/pt를 확보한다. 묶음 뒤 메시지는 같은 열 폭을 비우고 중복 버튼을 만들지 않는다.
|
|
180
|
+
제품이 작성자 ID와 프로필 경로를 결정한다. 동작을 생략하면 기존 장식 아바타를 유지한다.
|
|
181
|
+
사진 확대·답장·반응과 프로필 이동을 섞지 않으며 프로필 누르기는 말풍선 제스처를 시작하지 않는다.
|
|
@@ -56,3 +56,7 @@ Native는 동일 prop 계약으로 `@hjmds/react-native/communication`에서 가
|
|
|
56
56
|
members·title·emptyLabel이 필수다. onSelectionChange가 있으면 선택 목록이다. `memberActions`는 멤버별 메뉴 이름(`label(member)`)·닫기 문구·허용된 행동만 전달하며, 메뉴는 가로 점 세 개 아이콘 버튼으로 열린다. `invite`를 주면 목록 위에 브랜드 색 "초대" 행이 생긴다(Telegram "구성원 추가"). 역할은 `member.role`로 이름 오른쪽에 브랜드 색 caption으로 표시하고, `detail`은 둘째 줄(접속·소개)에 둔다.
|
|
57
57
|
|
|
58
58
|
데이터·문구·권한은 제품이 소유한다. 저장·개설·초대·삭제는 실제 성공 뒤 Promise를 resolve하고 실패는 reject한다. 입력과 선택은 실패 시 유지한다. Storybook의 메모리 예제를 운영 저장으로 사용하지 않는다. 방 전환 시 `key={roomId}`로 작업 수명을 구분한다.
|
|
59
|
+
|
|
60
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
61
|
+
|
|
62
|
+
일반 참여자 목록은 `memberProfile={{label: member => openLabel(member.name), onPress: member => openMember(member.id)}}`로 사진과 행에서 해당 회원 화면을 연다. 명시적인 초대/선택 모드(onSelectionChange)는 선택 동작을 유지한다.
|
|
@@ -73,7 +73,7 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
|
|
|
73
73
|
| `composer`(필수) | `MessageComposer` 등 작성창 | `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
|
|
74
74
|
| `state` / `stateAction` | `loading`·`empty`·`error`·`restricted` + 지역화 `title` | ready 외에는 본문을 교체한다 |
|
|
75
75
|
| `notice` | 새로고침 실패 같은 비차단 안내 | 초안 유지가 필요하면 `state` 대신 이것 |
|
|
76
|
-
| `avatar` | 상대·방 사진 노드 | 미게시. 가운데 정렬 제목 앞에 놓는다. 단톡방은 인원 수를 `titleBadge`(흐린 글자)로 넘긴다. ScreenLayout `titleLeading`과 같은 자리라 ChatScreen은 `titleLeading`을 따로 받지 않는다. 장식이라 보조기술에서 숨는다(제목이 이름) |
|
|
76
|
+
| `avatar` | 상대·방 사진 노드 | 미게시. 가운데 정렬 제목 앞에 놓는다. 단톡방은 인원 수를 `titleBadge`(흐린 글자)로 넘긴다. ScreenLayout `titleLeading`과 같은 자리라 ChatScreen은 `titleLeading`을 따로 받지 않는다. 기본은 장식이라 보조기술에서 숨는다(제목이 이름). `avatarAction`을 주면 이름 있는 프로필 이동 버튼이 된다 |
|
|
77
77
|
| `jumpToLatest` | `{ label, icon, onPress() }` | 미게시. 독자가 최신 메시지에서 벗어났을 때만 넘긴다. 작성창 위 끝 쪽에 원형 `IconButton size="small"`(secondary)로 떠 있고, 스크롤 위치 판단은 제품이 한다 |
|
|
78
78
|
|
|
79
79
|
## 축과 기본값
|
|
@@ -115,3 +115,12 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
|
|
|
115
115
|
| 높이 | host가 남은 높이 제공 | `flex: 1` |
|
|
116
116
|
| 스크롤 연결 | 없음 | `scrollRef`, `scrollProps`(`refreshControl`, `keyboardDismissMode` 등) — `scroll="screen"`이거나 상태 교체 중일 때만 ScrollView가 있다 |
|
|
117
117
|
| 테스트 id | 없음 | `testID` |
|
|
118
|
+
|
|
119
|
+
### 상대 프로필 이동 (2026-10-25, 개발 소스)
|
|
120
|
+
|
|
121
|
+
헤더 사진을 회원 프로필로 연결할 때 `avatarAction={{ label: t("profile.open"), onPress: () => openMember(peer.id) }}`를 준다.
|
|
122
|
+
Web·Native 모두 같은 계약이며 제품이 회원 ID·이동 경로·번역 문구를 소유한다. 단체방의 헤더는 방을 나타내므로
|
|
123
|
+
회원 한 명의 프로필로 연결하지 않는다. 메시지 작성자는 `ChatMessage.avatarAction`으로 따로 연결한다.
|
|
124
|
+
동작이 없으면 기존 장식 표시를 유지한다. 동작이 있으면 44px/pt 버튼으로 노출하고 장식 사진만 보조기기에서 숨긴다.
|
|
125
|
+
기존에는 헤더 사진 전체가 숨겨져 프로필 이동 컨트롤을 넣어도 접근할 수 없었다.
|
|
126
|
+
일반 `ScreenLayout`의 같은 자리는 `titleLeadingAction`을 사용한다.
|
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
4
|
- 상태: 배포
|
|
5
|
-
- 지원: Web
|
|
5
|
+
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
8
|
-
- 근거: [ColorPicker —
|
|
7
|
+
- 검토일: 2026-10-25
|
|
8
|
+
- 근거: [ColorPicker — sRGB 색상 입력](../../color-picker.md), recipe `colorPickerRecipe`(`src/color-picker.ts`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/색상 선택기`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
13
|
-
사용자가 콘텐츠 색(라벨 색, 태그 색, 테마 편집기의 사용자 값 등)을 sRGB HEX로
|
|
14
|
-
|
|
13
|
+
사용자가 콘텐츠 색(라벨 색, 태그 색, 손그림 펜 색, 테마 편집기의 사용자 값 등)을 sRGB HEX로 고를 때 쓴다.
|
|
14
|
+
채도·밝기 영역, 색조 막대, 선택적 불투명도 막대, HEX 입력, 프리셋 견본을 한 묶음으로 제공한다. Web과 Native의 모양과 동작이 같다.
|
|
15
|
+
|
|
16
|
+
자주 쓰는 색 몇 개를 한 줄로 빠르게 고르는 곳(그리기 도구 막대 등)에는 동반 부품 `ColorSwatchPicker`를 두고,
|
|
17
|
+
"더 많은 색" 행동으로 `ColorPicker`를 [Sheet](sheet.md)에 연다.
|
|
15
18
|
|
|
16
19
|
## 쓰지 않을 때
|
|
17
20
|
|
|
18
21
|
| 상황 | 대신 쓸 것 |
|
|
19
22
|
| --- | --- |
|
|
20
|
-
| 정해진 몇 가지 색 중 하나만 고름 | [RadioGroup](radio-group.md), [SegmentedControl](segmented-control.md) |
|
|
23
|
+
| 정해진 몇 가지 색 중 하나만 고름 | `ColorSwatchPicker`(이 문서). 색이 아닌 선택지는 [RadioGroup](radio-group.md), [SegmentedControl](segmented-control.md) |
|
|
21
24
|
| 숫자 하나를 범위에서 고름 | [Slider](slider.md) |
|
|
22
25
|
| 제품 브랜드·테마 색을 화면마다 바꾸기 | 컴포넌트가 아니다. 제품 테마 토큰(`brandPalette`)으로 한다 |
|
|
23
26
|
|
|
@@ -25,10 +28,12 @@
|
|
|
25
28
|
|
|
26
29
|
| 이름 | 역할 | Web | Native |
|
|
27
30
|
| --- | --- | --- | --- |
|
|
28
|
-
| `ColorPicker` | 기본 | `/color-picker` |
|
|
29
|
-
| `
|
|
31
|
+
| `ColorPicker` | 기본 | `/color-picker` | `/color-picker` |
|
|
32
|
+
| `ColorSwatchPicker` | 동반(견본 하나 고르기) | `/color-picker` | `/color-picker` |
|
|
33
|
+
| `ColorPickerLabels`(타입) | 보조(문구 묶음) | `/color-picker` | `/color-picker` |
|
|
30
34
|
|
|
31
|
-
루트 barrel에는 없다.
|
|
35
|
+
루트 barrel에는 없다. Native는 `react-native-svg`(선택 peer)가 필요하다. Chart와 같다.
|
|
36
|
+
Native 구현과 2026-10-25 개편은 개발 후보이며 패키지 게시·기기 검증은 별도다.
|
|
32
37
|
|
|
33
38
|
## 최소 사용 예
|
|
34
39
|
|
|
@@ -39,8 +44,8 @@ import { ColorPicker } from "@hjmds/react/color-picker";
|
|
|
39
44
|
<ColorPicker
|
|
40
45
|
label={t("label.color")}
|
|
41
46
|
labels={{
|
|
42
|
-
color: t("color.
|
|
43
|
-
opacity: t("color.opacity"), invalid: t("color.invalid"),
|
|
47
|
+
hex: "HEX", hue: t("color.hue"), saturation: t("color.saturation"), brightness: t("color.brightness"),
|
|
48
|
+
opacity: t("color.opacity"), invalid: t("color.invalid"), presets: t("color.presets"),
|
|
44
49
|
}}
|
|
45
50
|
value={color}
|
|
46
51
|
onValueChange={setColor}
|
|
@@ -48,39 +53,71 @@ import { ColorPicker } from "@hjmds/react/color-picker";
|
|
|
48
53
|
/>
|
|
49
54
|
```
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
```tsx
|
|
57
|
+
// Native: 도구 막대의 빠른 색 줄 + 시트 안의 전체 선택기
|
|
58
|
+
import { ColorPicker, ColorSwatchPicker } from "@hjmds/react-native/color-picker";
|
|
59
|
+
|
|
60
|
+
<ColorSwatchPicker label={t("pen.color")} labelVisibility="hidden" layout="scroll"
|
|
61
|
+
colors={quickColors} value={quickColors.includes(color) ? color : null} onValueChange={setColor} />
|
|
62
|
+
<Sheet open={open} onOpenChange={setOpen} title={t("color.more")} closeLabel={t("action.close")}>
|
|
63
|
+
<ColorPicker label={t("pen.color")} labels={labels} value={color} onValueChange={setColor} />
|
|
64
|
+
</Sheet>
|
|
65
|
+
```
|
|
52
66
|
|
|
53
67
|
## 축과 기본값
|
|
54
68
|
|
|
55
69
|
| prop | 값 | 기본값 | 설명 |
|
|
56
70
|
| --- | --- | --- | --- |
|
|
57
|
-
| `value` + `onValueChange` | 소문자 `#rrggbb`(`alpha`면 `#rrggbbaa`) + `(value: string) => void` | 필수(controlled 전용) | 정규화한 값이 바뀔 때만 부른다 |
|
|
58
|
-
| `label` | `string` | 필수 |
|
|
59
|
-
| `labels` | `{
|
|
60
|
-
| `alpha` | `boolean` | `false` | 켜면 0–100% 불투명도
|
|
61
|
-
| `disabled` | `boolean` | `false` |
|
|
62
|
-
| `presets` | `readonly string[]`(HEX) | `[]` | 정규화 후 중복을 없앤다.
|
|
63
|
-
| `
|
|
64
|
-
|
|
|
71
|
+
| `value` + `onValueChange` | 소문자 `#rrggbb`(`alpha`면 `#rrggbbaa`) + `(value: string) => void` | 필수(controlled 전용) | 정규화한 값이 바뀔 때만 부른다. 영역·막대를 끄는 동안 연속으로 부른다 |
|
|
72
|
+
| `label` | `string` | 필수 | 묶음 이름(Web legend) |
|
|
73
|
+
| `labels` | `{ hex; hue; saturation; brightness; opacity; invalid; presets? }` | 필수 | 영역의 두 축·색조·불투명도 막대의 접근성 이름, HEX 입력, 오류, 프리셋 묶음 이름. `presets`는 `presets`가 있을 때 필수 |
|
|
74
|
+
| `alpha` | `boolean` | `false` | 켜면 0–100% 불투명도 막대와 % 표시가 생긴다. 3·6자리 HEX를 치면 현재 불투명도를 유지한다 |
|
|
75
|
+
| `disabled` | `boolean` | `false` | 영역·막대·HEX·프리셋을 모두 막는다 |
|
|
76
|
+
| `presets` | `readonly string[]`(HEX) | `[]` | 정규화 후 중복을 없앤다. `ColorSwatchPicker`로 그린다 |
|
|
77
|
+
| `getPresetLabel` | `(color) => string` | HEX 코드 | 견본의 접근성 이름. 제품이 지역화한 색 이름이 있으면 넘긴다 |
|
|
78
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 묶음 배치 |
|
|
79
|
+
| HEX 입력 | — | — | Enter·blur(Native는 submit·blur)에 확정, Web Escape는 마지막 `value`로 되돌린다. 잘못된 입력은 `labels.invalid`를 보이고 외부 값은 바꾸지 않는다 |
|
|
80
|
+
|
|
81
|
+
`ColorSwatchPicker`
|
|
82
|
+
|
|
83
|
+
| prop | 값 | 기본값 | 설명 |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `label` | `string` | 필수 | 묶음 이름 |
|
|
86
|
+
| `colors` | `readonly string[]`(HEX) | 필수 | 정규화 후 중복 제거 |
|
|
87
|
+
| `value` + `onValueChange` | HEX 또는 `null` | 필수 | 현재 색이 견본에 없으면 `null`(아무 견본도 선택 표시 안 함). 이미 선택된 견본을 누르면 부르지 않는다 |
|
|
88
|
+
| `layout` | `"wrap"` \| `"scroll"` | `"wrap"` | `scroll`은 가로 한 줄 스크롤(도구 막대) |
|
|
89
|
+
| `labelVisibility` | `"visible"` \| `"hidden"` | `"visible"` | `hidden`이면 보조기술에만 묶음 이름을 준다 |
|
|
90
|
+
| `alpha`, `disabled`, `getColorLabel`, `layoutStyle` | — | — | `ColorPicker`와 같은 뜻 |
|
|
65
91
|
|
|
66
92
|
## 배치
|
|
67
93
|
|
|
68
94
|
| 항목 | 값 | 근거 |
|
|
69
95
|
| --- | --- | --- |
|
|
70
|
-
| 크기 | 부모 폭을
|
|
71
|
-
| 간격 | 안쪽
|
|
72
|
-
| 순서·정렬 | 위→아래 [
|
|
73
|
-
|
|
|
74
|
-
|
|
|
96
|
+
| 크기 | 부모 폭을 채운다. 영역 4:3(`area.aspectRatio`), 영역 손잡이 24(흰 테두리 3), 막대 높이 24·손잡이 28, 미리보기 원 44, 견본 누름 영역 44·원 32·선택 고리 2(간격 2) | `colorPickerRecipe` |
|
|
97
|
+
| 간격 | 묶음 안쪽 16, 부품 사이 `spacing.md`, 견본 사이 2(Web 0.25rem·Native `spacing.xxs`) | `.hjm-color-picker*`, Native `color-picker.tsx` |
|
|
98
|
+
| 순서·정렬 | 위→아래 [label] → [채도·밝기 영역] → [색조 막대] → [불투명도 막대(`alpha`)] → [미리보기 원][HEX 입력][불투명도 %] → [오류] → [프리셋 견본] | `react/src/color-picker.tsx`, `react-native/src/color-picker.tsx` |
|
|
99
|
+
| 방향 | 영역과 막대는 RTL에서도 왼쪽→오른쪽이다(색 공간 좌표이지 읽기 순서가 아니다). HEX 입력도 LTR로 고정한다 | 같은 파일 주석 |
|
|
100
|
+
| 고정·스크롤 | 영역은 터치를 잡으면 상위 스크롤에 넘기지 않는다(Web `touch-action: none`, Native 응답자 종료 거절). 막대는 Slider처럼 가로 의도가 확인될 때까지 세로 스크롤에 양보한다 | 같은 파일, [Slider](slider.md) |
|
|
101
|
+
| 좁은 폭·큰 글자 | 영역·막대는 폭에 맞춰 줄고, HEX 입력이 남은 폭을 채운다. `wrap` 견본은 줄바꿈된다 | — |
|
|
75
102
|
|
|
76
103
|
## 꼭 지킬 것
|
|
77
104
|
|
|
78
|
-
- `label`과 `labels`의
|
|
105
|
+
- `label`과 `labels`의 문구는 모두 비어 있지 않아야 한다(`presets`가 있으면 `labels.presets`도). 하나라도 비면 렌더 중 `TypeError`가 난다.
|
|
79
106
|
- `value`와 `presets`는 유효한 HEX여야 한다. `alpha={false}`인데 불투명하지 않은 8자리 값을 주면 투명도를 버리지 않고
|
|
80
107
|
`TypeError`를 던진다. 저장된 값을 넘기기 전에 제품 어댑터에서 형식을 맞춘다.
|
|
81
108
|
- 고른 색을 본문·배경에 쓸 때의 대비 검사는 제품이 한다. 견본 색은 콘텐츠 데이터이며 HJM 토큰이 아니다.
|
|
82
|
-
-
|
|
109
|
+
- 견본 줄을 제품이 버튼으로 다시 만들지 않는다. `ColorSwatchPicker`를 쓴다.
|
|
110
|
+
- 배치는 `layoutStyle`로 한다. `className`·`style` prop은 없다.
|
|
83
111
|
|
|
84
112
|
## 함정
|
|
85
113
|
|
|
86
|
-
-
|
|
114
|
+
- 검정·회색은 색조가 없고 검정은 채도도 없다. 컴포넌트는 직전 색조·채도를 기억해 손잡이가 빨강(0°)으로 튀지 않게 한다.
|
|
115
|
+
외부에서 다른 색을 넣으면 그 색에서 다시 계산한다.
|
|
116
|
+
- `onValueChange`는 끄는 동안 여러 번 불린다. 저장·네트워크 전송은 제품이 시트를 닫을 때 등으로 모은다.
|
|
117
|
+
|
|
118
|
+
## 개편 근거 (2026-10-25)
|
|
119
|
+
|
|
120
|
+
Diairy 손그림 프로필에서 색 선택이 부족하다는 요청으로 Native를 추가했고, 같은 날 "다른 곳 사례를 참고해 개편" 요청으로 HSV 편집기로 바꿨다.
|
|
121
|
+
첫 Native 후보의 RGB 0–255 막대 세 개는 "조금 더 연한 분홍" 같은 생각을 세 막대 퍼즐로 만들었고, Web의 브라우저 색 팝업은 OS마다 모양이 달랐다.
|
|
122
|
+
Figma·Procreate Classic·iOS 색 선택기(Spectrum)처럼 채도·밝기 영역과 색조 막대를 기본으로 하고, React Aria `ColorSwatchPicker`처럼 견본 고르기를 별도 부품으로 분리했다.
|
|
123
|
+
Procreate처럼 자주 쓰는 색을 바깥 한 줄에 두는 구성은 견본 부품으로 제품이 짠다(최근 색 저장은 제품 데이터다).
|
|
@@ -86,7 +86,9 @@ import { MessageComposer } from "@hjmds/react-native/screens";
|
|
|
86
86
|
| `onLike(id)`·`onReply(id)` | 기본 좋아요 버튼·답글 버튼의 콜백 |
|
|
87
87
|
| `replyLabel`·`repliesLabel(count, expanded)` | 지역화 문구. 복수형·펼침 상태는 제품 i18n이 만든다. 펼침 버튼 이름이 이 문구이므로 `expanded`에 따라 "답글 3개 보기"/"답글 숨기기"처럼 상태가 드러나게 만든다 |
|
|
88
88
|
| `composer` | 작성창(보통 `MessageComposer`의 `replyTo`·`sendIcon`). `ready`·`empty`일 때만 보인다 |
|
|
89
|
-
| `threadFooter` | 더 보기·커서 페이지네이션 |
|
|
89
|
+
| `threadFooter` | 최상위 댓글 목록의 더 보기·커서 페이지네이션 |
|
|
90
|
+
| `moreRepliesLabel(remaining)`(미게시) | 펼친 답글 묶음의 "더 보기" 문구(예: "답글 7개 더 보기"). 주면 아래 답글 5개 정책이 켜지고, 생략하면 받은 답글을 모두 보이는 이전 동작이다. 포트폴리오 제품은 모두 준다 |
|
|
91
|
+
| `revealedReplyId`(미게시) | 방금 보낸 답글·알림 대상처럼 열린 페이지 밖이어도 보여야 하는 답글 id. 묶음 펼침은 여전히 `expandedIds`가 정한다 |
|
|
90
92
|
| `commentMenu`(미게시) | 댓글별 더보기 메뉴. `label(item)` 버튼·메뉴의 지역화 접근성 이름(예: "seoyeon 댓글 더 보기"), `items(item)` 그 댓글에 허용된 항목(`{ id, label, tone?: "danger", disabled? }`, Menu·ContextMenu와 같은 항목 어휘), `onAction(item, id)` 메뉴가 닫힌 뒤 호출. Native는 메뉴 시트의 닫기 문구 `dismissLabel`도 받는다. 항목이 비면 그 행에는 버튼을 그리지 않는다. 같은 prop을 `CommentThreadRow`도 받는다 |
|
|
91
93
|
|
|
92
94
|
`CommentThreadItem`: `id`, `parentId`(`null`이면 최상위), `author`, `body`(노드), `timeLabel`, `likeCountLabel`,
|
|
@@ -98,6 +100,15 @@ import { MessageComposer } from "@hjmds/react-native/screens";
|
|
|
98
100
|
받은 답글이 없어도 전체 개수·복구 안내·더 보기 슬롯으로 답글 영역을 열 수 있다. 접으면 세 영역이 함께 숨는다.
|
|
99
101
|
조회·커서·재시도·문구·데이터 보존은 제품이 맡고 오류/페이지 행동은 HJM Notice·Button/LoadMore로 공급한다.
|
|
100
102
|
Spint에서 페이지 오류와 더 보기가 원글 body에 들어가 답글을 접어도 남던 2026-10-18 이관이 확장 근거다.
|
|
103
|
+
|
|
104
|
+
### 답글 5개씩 (2026-10-25 정책, 미게시)
|
|
105
|
+
|
|
106
|
+
펼친 답글 묶음은 `commentThreadRecipe.replyPageSize`(5)개만 먼저 보이고, 묶음 끝의 흐린 글자 버튼
|
|
107
|
+
(`moreRepliesLabel(남은 수)`)이 5개씩 더 연다. 받은 답글이 남아 있으면 HJM이 바로 보이고, 받은 답글을 다 보였는데
|
|
108
|
+
`repliesCount`가 더 크면 최상위 항목의 `repliesPage.onLoadMore()`를 불러 제품이 다음 서버 페이지(5개)를 받게 한다.
|
|
109
|
+
요청 중에는 `repliesPage.pending`으로 버튼을 막는다. 오류는 `repliesNotice`, 그 밖의 묶음 끝 내용은 `repliesFooter`에 둔다.
|
|
110
|
+
열린 개수는 HJM 화면 상태이고, 데이터·커서·정렬·펼침은 제품이 맡는다. 판단 규칙은 `resolveCommentReplyPage`다.
|
|
111
|
+
계기: Spint가 답글 더 보기를 가운데 브랜드색 링크로 직접 만들고 번뚝은 답글을 접지 않아, 같은 기능이 앱마다 달랐다.
|
|
101
112
|
실행 원본: [Web 답글 복구](../../../../../showcase/web/src/components/comment-thread-replies-preview.tsx),
|
|
102
113
|
[Native 답글 복구](../../../../../showcase/native/src/components/comment-thread-replies-preview.tsx).
|
|
103
114
|
|
|
@@ -179,3 +190,7 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
|
|
|
179
190
|
2026-10-20 메뉴의 대상 연결을 분명히 하자는 결정으로 `commentMenu.presentation`은 Web·Native 모두
|
|
180
191
|
`anchored`가 기본이다. `grouped`/`bottom`으로 바꿀 때는 제품 상호작용 이유와 지역화한 `dismissLabel`을 함께 제공한다.
|
|
181
192
|
행마다 제품 Menu를 새로 조립하지 않는다. [Menu의 세 표현](menu.md#세-가지-표시-방식--개발-소스-미게시)을 따른다.
|
|
193
|
+
|
|
194
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
195
|
+
|
|
196
|
+
`CommentThreadItem.avatarAction={label,onPress}`로 작성자 사진·이니셜을 회원 프로필에 연결한다. 아바타 버튼은 댓글 hold/답장/좋아요 영역 밖에 위치한다.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# ConversationListScreen
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 미게시(1.46.1 이후)
|
|
7
|
+
- 검토일: 2026-10-25
|
|
8
|
+
- 근거: 번뚝 목록의 중복 여백·구분선 재발, 양 renderer `src/screens.tsx`
|
|
9
|
+
- 스토리북: `배포/화면/소통/대화 목록`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
DM·단체방 대화 목록의 공통 화면이다. 행은 ConversationRow, 데이터·이동·행 메뉴는 제품이 연결한다.
|
|
14
|
+
ScreenLayout은 일반 화면 골격이며 대화 목록에서는 여백을 다시 조합하지 않고 이 API를 사용한다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 일반 검색 | SearchScreen |
|
|
21
|
+
| 알림 | NotificationInboxScreen |
|
|
22
|
+
| 목록과 상세 전환 | ListDetailScreen |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `ConversationListScreen` | 대화 목록 화면 | `@hjmds/react/screens` | `@hjmds/react-native/screens` |
|
|
29
|
+
|
|
30
|
+
같은 경로에서 ConversationListScreenProps를 공개한다.
|
|
31
|
+
|
|
32
|
+
## 최소 사용 예
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// Web
|
|
36
|
+
import { ConversationListScreen } from '@hjmds/react/screens';
|
|
37
|
+
<ConversationListScreen title={title} search={searchField} actions={composeAction} state={state}>
|
|
38
|
+
{conversationLinks}
|
|
39
|
+
</ConversationListScreen>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// Native
|
|
44
|
+
import { ConversationListScreen } from '@hjmds/react-native/screens';
|
|
45
|
+
<ConversationListScreen title={title} search={searchField} actions={composeAction} state={state} scroll="content">
|
|
46
|
+
{virtualizedConversationList}
|
|
47
|
+
</ConversationListScreen>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 축과 기본값
|
|
51
|
+
|
|
52
|
+
| prop | 값 | 기본값 | 설명 |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| search | ReactNode | 없음 | 검색 입력 콘텐츠 |
|
|
55
|
+
| notice | ReactNode | 없음 | 비차단 조회 오류·안내 |
|
|
56
|
+
| children | ReactNode | 없음 | ConversationRow 링크/메뉴 또는 가상화 host |
|
|
57
|
+
| actions | ReactNode | 없음 | 새 대화 등 헤더 도구 |
|
|
58
|
+
| state, stateAction | ScreenLayout 계약 | ready, 없음 | 로딩·빈 상태·오류·권한 상태와 행동 |
|
|
59
|
+
| scroll | screen · content | screen | 가상화 host만 content |
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
나머지 prop은 [ScreenLayout](screen-layout.md)을 따른다. contentInset은 받지 않고 body-edge를 고정한다.
|
|
63
|
+
|
|
64
|
+
## 배치
|
|
65
|
+
|
|
66
|
+
| 항목 | 값 | 근거 |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| 크기 | 본문 폭·헤더 높이는 ScreenLayout, 행 크기는 ConversationRow | 두 공개 구현 |
|
|
69
|
+
| 간격 | 헤더·검색 좌우 16, 본문 좌우 0, 행 좌우 16, 검색–안내 12 | screenPatternRecipe.padding, conversationRowRecipe.paddingInline, Stack sm |
|
|
70
|
+
| 순서·정렬 | 헤더 → 검색/안내 → 본문 → footer | ConversationListScreen → ScreenLayout |
|
|
71
|
+
| 고정·스크롤 | 기본 본문 스크롤, 가상화 host만 scroll=content | ScreenLayout |
|
|
72
|
+
| 좁은 폭·큰 글자 | 행과 상태의 기존 반응형 동작 유지 | ConversationRow·ScreenLayout, 최대 글자 조건 제외 |
|
|
73
|
+
|
|
74
|
+
검색/안내가 모두 없으면 notice 영역을 만들지 않는다. 카드·구분선을 추가하지 않는다.
|
|
75
|
+
|
|
76
|
+
## 꼭 지킬 것
|
|
77
|
+
|
|
78
|
+
본문을 카드나 추가 좌우 여백으로 감싸지 않는다. 제품은 데이터·라우팅·메뉴를 연결한다.
|
|
79
|
+
배포 표기는 기존 Storybook 화면의 분류이며 새 API의 npm 게시 완료를 뜻하지 않는다.
|
|
80
|
+
실제 행을 사용한 브라우저의 시작점 확인과 Native host 검사를 기기 검증으로 확대하지 않는다.
|
|
81
|
+
전체 예시는 [대화 목록 화면](../screens/common-conversation-list.md)을 따른다.
|
|
@@ -63,7 +63,7 @@ import { ConversationRow } from "@hjmds/react-native/conversation-row";
|
|
|
63
63
|
|
|
64
64
|
| 항목 | 값 | 근거 |
|
|
65
65
|
| --- | --- | --- |
|
|
66
|
-
| 크기 | 아바타
|
|
66
|
+
| 크기 | 아바타 48, 읽지 않음 점 10 | conversationRowRecipe |
|
|
67
67
|
| 간격 | 가로 sm, 세로 10 | 같은 recipe |
|
|
68
68
|
| 순서·정렬 | 아바타 → 제목/미리보기·시각 → 읽지 않음 점 | 양 renderer |
|
|
69
69
|
| 고정·스크롤 | 바깥 목록 host 소유 | 별도 scroller 없음 |
|
|
@@ -99,3 +99,7 @@ host는 상대·미리보기·시각·읽지 않은 수가 포함된 현지화
|
|
|
99
99
|
|
|
100
100
|
- 공개 구성으로 분리: 2026-10-15 전사 적용 중 제품마다 복제한 DM 행의 배치가 달라졌다.
|
|
101
101
|
- 행 메뉴의 위험 항목 위치: 2026-10-16 인스타그램 DM 실측.
|
|
102
|
+
|
|
103
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
104
|
+
|
|
105
|
+
`avatarAction={label,onPress}`로 상대 프로필을 열고 `renderBody(body)`에 제품의 실제 Link/Pressable을 연결해 대화로 이동한다. 이 조합의 바깥에 행 전체 Link를 다시 감싸지 않는다.
|
|
@@ -212,3 +212,5 @@ ReactNode에서 자식 텍스트나 이미지 prop을 추측하지 않는다. We
|
|
|
212
212
|
Native는 고정 열의 행 target 하나만 이름을 갖고, 고정 열이 없는 표도 같은 행 이름을 갖는다.
|
|
213
213
|
`onRowPress`를 쓰는 분할 행의 스크롤 영역은 터치·포인터로 같은 행을 열지만 별도 접근성·키보드 대상은 아니다.
|
|
214
214
|
개발 브라우저에서도 이 구분을 유지하며, 고정 행 이름에는 스크롤 열의 값까지 포함한다.
|
|
215
|
+
|
|
216
|
+
2026-10-26 사용자 요청: LineScore 현재 이닝은 카테고리 선택과 같은 `selection.background`/`selection.content`를 쓴다. MatrixTable의 내 팀 강조는 기존 브랜드 역할을 유지한다. 개발 소스, 미게시.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-25
|
|
8
8
|
- 근거: [DateRange](../../date-range.md), 격자는 [Calendar](../../calendar.md), 규칙 함수 `src/date-range.ts`
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/기간 선택`
|
|
10
10
|
|
|
@@ -76,17 +76,27 @@ import { DateRangePicker } from "@hjmds/react-native/date-range";
|
|
|
76
76
|
| `descriptor.onFocusedMonthChange` | `(month: string, reason: "previous" \| "next" \| "jump") => void` | — | 없으면 이동 버튼이 비활성 |
|
|
77
77
|
| `composeAccessibleName` | `(info: { date, isToday, isSelected, disabled, content? }) => string` | — (필수) | 구간 접미사는 컴포넌트가 붙인다 |
|
|
78
78
|
| `rangeLabels` | `{ start: string, end: string, between: string }` | — (필수) | 셀 이름 뒤에 붙는 구간 위치 접미사 |
|
|
79
|
+
| `maxRange` | `{ years?, months?, days? } \| null` | `{ years: 1 }` | 끝을 포함한 최대 길이. 달력 단위라 윤년을 낀 1월 1일~12월 31일도 1년이다. 고르는 중 범위 밖 날은 비활성, `null`이면 제한 없음. 넘는 `value`를 넘기면 `RangeError` |
|
|
79
80
|
| `previousMonth` · `nextMonth` | `{ month: string, label: string }` | — | 없으면 이동 버튼이 없다 |
|
|
80
|
-
| `renderCellContent` | `(date: string) => ReactNode` | — | 날짜 아래 보조
|
|
81
|
+
| `renderCellContent` | `(date: string) => ReactNode` | — | 날짜 아래 보조 표시. 넘길 때만 행에 보조 줄이 생긴다 |
|
|
82
|
+
| `summary` | `{ startLabel, endLabel, placeholder, formatDate(date) }` | — | 격자 위 시작일·종료일 칸. 지금 고르는 쪽(`resolveDateRangeActiveEdge`: 비었으면 시작, 고르는 중이면 끝, 완성되면 없음)을 테두리로 표시한다. 문구는 i18n, 날짜 형식은 제품 소유 |
|
|
83
|
+
| `presets` | `{ id, label, length: { days } \| { months } }[]` | — | 요약 아래 단일 선택 Chip 줄(최근 7일·1개월 등). 모두 `grid.todayDate`로 끝나고(`resolveDateRangePreset`), 값이 그 구간과 같을 때만 선택으로 보인다. 누르면 끝 달로 `onFocusedMonthChange(month, "jump")`를 요청한다 |
|
|
81
84
|
| Web `layoutStyle` | 배치 전용 style | — | 루트 배치 |
|
|
82
85
|
|
|
83
86
|
## 배치
|
|
84
87
|
|
|
88
|
+
2026-10-25 개편(Spint 기간 시트 사용자 지적): 구간이 날짜 아래 점으로만 보여 이어져 보이지 않았고, 점 줄 때문에
|
|
89
|
+
모든 행이 라벨 한 줄만큼 높아졌다. 오늘 테두리와 단일 선택 원이 섞여 시작·끝도 구분되지 않았다. Material 3·Airbnb
|
|
90
|
+
기간 선택을 모티브로 두 끝은 같은 채운 원, 사이는 날짜 원 높이의 띠 하나로 잇고, 띠는 주(행)가 시작·끝나는 곳에서
|
|
91
|
+
둥글게 닫는다. 기간 설정 안내 문장과 결과 문장은 `summary`의 시작일·종료일 칸이 대신한다(지금 무엇을 고르는지 강조).
|
|
92
|
+
월 이동은 `calendarRecipe.header.navIcon` 화살표 아이콘이다(이전 텍스트 ‹ ›는 약 8pt로 보였다).
|
|
93
|
+
|
|
85
94
|
| 항목 | 값 | 근거 |
|
|
86
95
|
| --- | --- | --- |
|
|
87
96
|
| 크기 | 날짜 셀은 `medium` `control.minTouchTarget` 44, `large` `glyph.xxl` 44로 같고 7열이다. Web 격자는 최소 폭 7×44 = 308을 잡는다. 월 이동 버튼 44 | `src/calendar.ts`(`calendarRecipe`), `packages/react/src/styles.css` `.hjm-calendar*` |
|
|
88
|
-
| 간격 | 월 머리 안 간격·세로 간격 `spacing.xs` 8.
|
|
89
|
-
|
|
|
97
|
+
| 간격 | 월 머리 안 간격·세로 간격 `spacing.xs` 8. `summary`·`presets`·격자 사이 `spacing.md` 16, 칩 사이 `spacing.xs` 8, 두 칸 사이 `spacing.xs` 8, 칸 안쪽 8×12, 칸 라벨·값 사이 4 | `dateRangeRecipe` |
|
|
98
|
+
| 색 | 띠 `primary` 15% tint(두 끝과 같은 브랜드색. 처음 쓴 `surfaceSelected`는 기본 팔레트에서 #f0f1f3 회색이라 구간이 거의 보이지 않았다), 두 끝 Calendar 선택 원(`action.brand`). 칸 바탕 `surface.sunken`, 다음에 고를 칸 테두리 2 `action.brand`, 빈 값 `content.tertiary` | `dateRangeRecipe` |
|
|
99
|
+
| 순서·정렬 | 오버레이 없이 본문(폼·필터 영역·[Section](section.md)) 안에 격자를 펼쳐 둔다. 위에서 `summary`(시작일 · 종료일) → `presets` 칩 줄 → 월 머리(이전 · 월 이름 · 다음) → 요일 → 날짜 격자. 제출 행동은 격자 **아래**에 둔다. 시트에서는 `footer`에 `BottomCTA` [보조(전체 날짜 등)][주(적용)] 한 줄로 두고 시트 높이는 `auto`로 맞춘다. `summary`를 쓰면 별도 안내·결과 문장을 두지 않는다. 제출 버튼은 `isCompleteDateRange(value)`가 참일 때만 활성이다 | 같은 파일 |
|
|
90
100
|
| 고정·스크롤 | 본문과 함께 스크롤한다(오버레이 없음) | — |
|
|
91
101
|
| 좁은 폭·큰 글자 | Web 격자 최소 폭 308보다 좁은 컨테이너에 두지 않는다 | `packages/react/src/styles.css` `.hjm-calendar__grid` |
|
|
92
102
|
|
|
@@ -102,7 +112,7 @@ import { DateRangePicker } from "@hjmds/react-native/date-range";
|
|
|
102
112
|
|
|
103
113
|
| 항목 | Web | Native |
|
|
104
114
|
| --- | --- | --- |
|
|
105
|
-
| 구간 표시 |
|
|
115
|
+
| 구간 표시 | 두 끝 채운 원 + 띠(`data-selected`·`data-range-band`), 마우스 hover로 끝 미리보기 | 두 끝 채운 원 + 띠, 셀 이름 |
|
|
106
116
|
| 배치 | `layoutStyle`(그 밖에 `className`) | 없음(감싸는 View로 배치) |
|
|
107
117
|
|
|
108
118
|
## 함정
|
|
@@ -118,7 +118,7 @@ await handle.closed; // portal 제거·초점 복구 뒤. 다음 오버레이는
|
|
|
118
118
|
| `size` | `small` · `medium` · `large` | `medium` | Native recipe 최대 폭 320 · 420 · 640. Web 최대 폭 28rem · 36rem · 48rem(아래 배치 표) |
|
|
119
119
|
| `dismissible` | `true` · `false` | `true` | `false`면 닫기 버튼과 바깥·Escape/back 닫기가 사라진다 |
|
|
120
120
|
| `busy` | `true` · `false` | `false` | 비동기 작업 중에는 `open`을 유지하고 `busy`로 반복 행동과 닫기를 막는다 |
|
|
121
|
-
| `motionOrigin` | `{ x, y, width, height }`(`TransitionRect`) | 없음 | 열기 직전 측정한 출발 영역. Web viewport/Native window의 물리 좌표를 쓴다. 1.14.0 양 renderer의 게시 타입에 포함. 선택적
|
|
121
|
+
| `motionOrigin` | `{ x, y, width, height }`(`TransitionRect`) | 없음 | 열기 직전 측정한 출발 영역. Web viewport/Native window의 물리 좌표를 쓴다. 1.14.0 양 renderer의 게시 타입에 포함. 선택적 stable 표현 계약이며 모든 기기·성능 검증 완료와 구분한다 |
|
|
122
122
|
| `closeLabel` | 현지화 문자열 | — | 양쪽 필수. 닫기 버튼의 접근성 이름 |
|
|
123
123
|
| `open`·`defaultOpen` | `boolean` | `false` | 제어형은 `open`+`onOpenChange`. Web 비제어형은 `trigger`가 필수다 |
|
|
124
124
|
| `onOpenChange` | `(open: boolean, detail: { reason }) => void` | — | Web `reason`: `"trigger"`·`"close-action"`·`"escape"`·`"outside"`. Native: `"close-action"`·`"back"`·`"outside"`. `busy`이거나 `dismissible={false}`면 닫기 요청을 보내지 않는다 |
|
|
@@ -260,7 +260,7 @@ import { ImmersiveOverlay } from "@hjmds/react-native/overlays";
|
|
|
260
260
|
- Native는 제어형과 비제어형을 렌더 중에 바꾸면 예외가 난다.
|
|
261
261
|
- Native 기본 렌더러 예제는 실제 저장 서버가 없는 동기 완료 예시다. 제품에서는 저장 Promise를 반환하며, 예제의 영문 고정 문구는 제품 i18n 키로 치환한다. 닫기는 action이 요청하는 `close-action`이 맡는다.
|
|
262
262
|
|
|
263
|
-
### 선택적인 트리거 형태 전환 (게시 API
|
|
263
|
+
### 선택적인 트리거 형태 전환 (게시 API·stable 표현 계약)
|
|
264
264
|
|
|
265
265
|
2026-10-07 [확장 카드·그리드 대조](../../../../../docs/plans/aceternity-interaction-adoption-2026-10-07.md) 중
|
|
266
266
|
실제 npm 1.14.0 양 renderer tarball의 `dist/overlays.d.ts`에서 이 옵션을 확인했다. 기존 미게시 표기를
|
|
@@ -278,7 +278,7 @@ Web은 닫기 전환이 끝나야 portal을 제거하고 onDismissComplete와
|
|
|
278
278
|
Native는 실제 Modal 콘텐츠를 측정하며 콜백이 오지 않으면 기존 enter 시간 내 일반 표시로
|
|
279
279
|
복귀한다. 뒤늦은 측정으로 닫힌 세션을 되살리지 않는다. 측정이 없거나 모션 감소이면
|
|
280
280
|
공간 전환 없이 기존 표현을 쓴다. Native에서 진입 후 크기가 달라지면 이전 측정으로
|
|
281
|
-
닫지 않고 일반 fade로 복귀한다. 다음 open에서 다시 측정한다. 이 옵션의
|
|
281
|
+
닫지 않고 일반 fade로 복귀한다. 다음 open에서 다시 측정한다. 이 옵션의 사용 예는 `버튼에서 이어지는 편집` 구성이다.
|
|
282
282
|
|
|
283
283
|
초안은 제품 상태에 두고 닫을 때 삭제하지 않는다. 내용·화면 회전·키보드로 목적지 크기가
|
|
284
284
|
달라지는 흐름은 실제 기기 검증 후 채택한다. 원점이 다른 좌표계를 혼합하지 않는다.
|
|
@@ -102,3 +102,7 @@ import { EventAttendanceCard } from "@hjmds/react-native/social-compositions";
|
|
|
102
102
|
| --- | --- | --- |
|
|
103
103
|
| 진행 중 이름 | `aria-label={labels.pending}` | `loadingLabel={labels.pending}` |
|
|
104
104
|
| 기본 아바타 | `Avatar aria-hidden` | `Avatar decorative` |
|
|
105
|
+
|
|
106
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
107
|
+
|
|
108
|
+
`participants[].avatarAction={label,onPress}`로 각 참여자 사진을 해당 회원 프로필에 연결한다. 참석 응답/캘린더 행동과 분리한다.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# FollowListScreen
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.47.0
|
|
7
|
+
- 검토일: 2026-10-25
|
|
8
|
+
- 근거: 사용자 Instagram 팔로워 화면 요청, 양 renderer src/follow-list.tsx
|
|
9
|
+
- 스토리북: `배포/화면/소통/팔로워와 팔로잉`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
팔로워/팔로잉을 전환하고 사람을 검색하며 각 행에서 팔로우할 때 사용한다.
|
|
14
|
+
2026-10-25 사용자 요청에 따라 [Instagram 참고 화면](https://www.soy502.com/articulo/asi-lucira-nueva-barra-busqueda-instagram)의 두 탭·검색·아바타·이름·행 우측 행동 구성을 참고했다. 최신 버전 전체를 복제한다는 의미는 아니다.
|
|
15
|
+
## 쓰지 않을 때
|
|
16
|
+
|
|
17
|
+
| 상황 | 대신 쓸 것 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| 한 프로필의 팔로워 수와 행동 | FollowProfileCard |
|
|
20
|
+
| 대화 미리보기 목록 | ConversationListScreen |
|
|
21
|
+
|
|
22
|
+
FollowProfileCard는 한 프로필의 수치와 행동을 보여 주므로 다수 사람 탐색 화면을 대신하지 않는다.
|
|
23
|
+
ConversationListScreen은 대화 미리보기용이다. 이 구성은 ScreenLayout, Tabs, TabPanel, SearchField,
|
|
24
|
+
List/ListRow, Avatar, Button과 기존 action session을 합성하며 새 선택/요청 상태 엔진을 만들지 않는다.
|
|
25
|
+
|
|
26
|
+
## 공개 이름과 import
|
|
27
|
+
|
|
28
|
+
| 이름 | 역할 | Web | Native |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| FollowListScreen | 관계 목록 화면 | @hjmds/react/follow-list | @hjmds/react-native/follow-list |
|
|
31
|
+
|
|
32
|
+
`@hjmds/react/follow-list`, `@hjmds/react-native/follow-list`에서
|
|
33
|
+
`FollowListScreen`, `FollowListScreenProps`, `FollowListPerson`을 가져온다.
|
|
34
|
+
|
|
35
|
+
- `title`, `exit`: 대상 사용자 이름과 뒤로가기. Web은 onClick, Native는 onPress.
|
|
36
|
+
- `tab`, `onTabChange`: followers 또는 following의 controlled 선택.
|
|
37
|
+
- `labels`: followers/following은 서버 수치를 포함한 지역화된 완성 문구다. tabs/search/clearSearch/searching/empty/follow/followed/pending/failed도 제품이 제공한다.
|
|
38
|
+
- `people`: 현재 탭·검색에 해당하는 id/username/name/following, 선택적 avatar/disabled.
|
|
39
|
+
- `toolbar`, `searchVisible`: 정렬·검색 도구 슬롯과 검색 필드 표시 여부(기본 true).
|
|
40
|
+
- `people[].message`: label/accessibilityLabel/onPress로 관계 버튼 대신 메시지 행동을 연결한다. `people[].action`은 삭제 아이콘·관계 메뉴 슬롯이다.
|
|
41
|
+
- `query`, `onQueryChange`: 검색 입력. 서버 검색·필터링·페이지네이션은 제품이 소유한다.
|
|
42
|
+
- `onOpenProfile(id)`: 행의 프로필 탐색. 행 오른쪽 버튼과 독립된 누르기 대상이다.
|
|
43
|
+
- `onFollowChange(id, following)`: 실제 요청과 서버 확정 people 갱신 후 resolve; 실패는 reject.
|
|
44
|
+
- `state`, `stateAction`, `footer`: 공통 조회 상태·재시도·추가 로드 연결. 빈 ready 목록은 labels.empty를 중앙 표시한다.
|
|
45
|
+
|
|
46
|
+
## 최소 사용 예
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// Web: currentPeople, labels, callbacks는 제품이 공급한다.
|
|
50
|
+
import { FollowListScreen } from "@hjmds/react/follow-list";
|
|
51
|
+
<FollowListScreen title={title} tab={tab} onTabChange={setTab} labels={labels}
|
|
52
|
+
people={currentPeople} query={query} onQueryChange={setQuery}
|
|
53
|
+
onOpenProfile={openProfile} onFollowChange={changeFollow} />;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// Native: 같은 controlled 계약을 사용한다.
|
|
58
|
+
import { FollowListScreen } from "@hjmds/react-native/follow-list";
|
|
59
|
+
<FollowListScreen title={title} tab={tab} onTabChange={setTab} labels={labels}
|
|
60
|
+
people={currentPeople} query={query} onQueryChange={setQuery}
|
|
61
|
+
onOpenProfile={openProfile} onFollowChange={changeFollow} />;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
양 Showcase의 `src/components/FollowListScreen.stories.tsx`는 공개 import로 실행하는 controlled 예제다.
|
|
65
|
+
제품의 현재 탭 목록을 `people`, 조회 상태를 `state`로 공급한다.
|
|
66
|
+
|
|
67
|
+
## 배치
|
|
68
|
+
|
|
69
|
+
| 항목 | 값 | 근거 |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| 크기 | Avatar large 48, ListRow comfortable | avatarRecipe/listRowRecipe |
|
|
72
|
+
| 간격 | 탭·검색 spacing.sm 12, 행 행동 spacing.xs 8 | Stack props |
|
|
73
|
+
| 순서·정렬 | 헤더 → 탭 → 정렬·검색 도구 → 선택적 검색 → 목록, 행동은 우측 | FollowListScreen |
|
|
74
|
+
| 고정·스크롤 | 헤더·검색 유지, 목록은 screen 또는 제품 가상화 host | ScreenLayout.scroll |
|
|
75
|
+
| 좁은 폭·큰 글자 | 좁은 폭은 공통 행의 축소·줄바꿈 사용, 최대 글자 조건은 범위 제외 | 루트 지침 |
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
← 사용자 이름
|
|
79
|
+
팔로워 1,284 팔로잉 328
|
|
80
|
+
━━━━━━━━━━━
|
|
81
|
+
[ 검색 ]
|
|
82
|
+
(아바타) username [팔로우]
|
|
83
|
+
표시 이름
|
|
84
|
+
(아바타) username [메시지 보내기]
|
|
85
|
+
표시 이름
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
ScreenLayout 기본 본문 여백 안에서 List appearance=plain, separator=none을 사용한다.
|
|
89
|
+
탭은 fitted/standard/underline, 검색과 탭 간격은 spacing.sm(12)이다.
|
|
90
|
+
행은 ListRow comfortable/circle, leadingSize=large와 Avatar large(48), 우측 Button small을 쓴다.
|
|
91
|
+
이미 팔로우한 회원에게는 `person.message`로 secondary 메시지 버튼을 제공한다. 미팔로우는 제품 테마의 primary다. 팔로우 취소는 제품 action의 더보기 메뉴로 분리한다. 독립 카드와 행 구분선은 없다.
|
|
92
|
+
검색 입력으로 진입 즉시 키보드가 열리지 않도록 initialFocus=none을 기본으로 한다.
|
|
93
|
+
|
|
94
|
+
## 꼭 지킬 것
|
|
95
|
+
|
|
96
|
+
요청 중 해당 버튼은 로딩과 중복 입력 방지를 제공하며 확인된 관계 문구를 유지한다.
|
|
97
|
+
실패하면 같은 행 아래 오류를 알리고 기존 관계를 유지한다. 인물 id가 바뀌거나 행이 사라지면
|
|
98
|
+
기존 action session이 해제된다. 탭/검색을 바꿀 때 query 초기화·응답 순서·캐시 정책은 제품 소유다.
|
|
99
|
+
실행 예제는 양 Showcase의 `src/components/FollowListScreen.stories.tsx`다.
|
|
100
|
+
Web/Native 구현과 테스트는 npm 게시·소비 앱 채택·기기 확인과 별도다.
|
|
101
|
+
|
|
102
|
+
## 제품 연결
|
|
103
|
+
|
|
104
|
+
`people[].showFollow=false`는 본인·관계 미확인 시 행동을 숨긴다. `followLabel`과
|
|
105
|
+
`followAccessibilityLabel`은 맞팔로우 등 제품 문구, `action`은 삭제 확인 버튼 등 추가 행동이다.
|
|
106
|
+
HJM이 두 행동을 `Stack axis=inline gap=xs(8)`로 배치한다.
|
|
107
|
+
`notice`는 검색 아래 비차단 조회 오류에 쓰며, Web `href/renderLink`는 링크 탐색을 보존한다.
|
|
108
|
+
Native `renderPeople(people, renderPerson)`은 FlatList host를 연결한다. `scroll=content`를 함께 쓰고
|
|
109
|
+
renderPerson을 호출해 공통 행과 요청 상태를 유지한다. 이름 보조줄 `name`은 없으면 생략한다.
|
|
@@ -98,3 +98,7 @@ import { FollowProfileCard } from "@hjmds/react-native/social-compositions";
|
|
|
98
98
|
| 진행 중 이름 | 버튼 `aria-label`=pending | Button `loadingLabel`=pending |
|
|
99
99
|
| 기본 아바타 | `Avatar aria-hidden` | `Avatar decorative` |
|
|
100
100
|
| 수치 전환 의존성 | `statistic-motion`의 `@number-flow/react` peer 필요 | 기존 ContentTransition |
|
|
101
|
+
|
|
102
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
103
|
+
|
|
104
|
+
`avatarAction={label,onPress}`로 카드의 회원 사진을 해당 회원 프로필 화면에 연결한다. 팔로우 버튼은 별도 행동이다.
|
|
@@ -161,3 +161,9 @@ Web도 Native와 같이 `trailingAction`을 지원한다. 이 슬롯은 행의 l
|
|
|
161
161
|
- trailing 세로 가운데: 2026-10-17 정렬 실측(Web 402). trailing span이 block이라 안의 inline svg가 글자 기준선에 놓여 행 가운데보다 2~2.5px 위였다(설정 334/331.5, 컬렉션 184/181.5). Native는 행 가운데(388/388)였다. trailing을 flex 가운데 상자로 바꾼 뒤 Web도 334/334, 184/184로 측정됐다.
|
|
162
162
|
- 글자를 여백 선에, 표면은 바깥으로: 2026-10-17 사용자 결정. 행이 누름 배경용 좌우 여백 8을 콘텐츠 안에 두어 기본 화면에서 행 글자가 16 + 8 = 24에서 시작했고, 같은 화면의 섹션 제목·검색 결과 머리("결과 N개")·설정 묶음 제목은 16에서 시작했다(Web 402 실측: 검색 결과 행 24 / 머리 16, 설정 leading 24 / 묶음 제목 16, 알림 leading 24 / "오늘" 16. Native 3x 화면 잉크: 결과 행 제목 25 / 개수 16.7 — Native는 투명 선택 테두리 1까지 더해졌다). 표면을 바깥으로 8 넓히는 방식으로 바꾼 뒤 Web 402·1280, LTR·RTL 모두 행 글자·leading 시작과 trailing 끝이 16(1280은 화면 열 기준 296)으로, Native는 결과 행 제목 잉크 16, 설정 leading 아이콘 잉크 36 → 27, 알림 42.3 → 33(프레임 16 + 아이콘 안쪽 여백)으로 측정됐다. 구분선 시작도 글자 위치(52)로 옮겼다.
|
|
163
163
|
- 구분선이 행의 글자 열을 따름: 2026-10-17 사용자 보고(leading 없는 행에서 구분선이 중간에서 잘림). 위 52는 leading 있는 행의 글자 위치일 뿐이라 leading 없는 행(글자 16)에서는 구분선이 68에서 시작했다. 행이 leading 여부를 List에 알리도록 바꿔 구분선 시작이 각 행의 글자 열과 같아졌다([List 근거](list.md#근거)).
|
|
164
|
+
|
|
165
|
+
### 회원 프로필 이동 (2026-10-25)
|
|
166
|
+
|
|
167
|
+
`leadingAction={label,onPress}`를 주면 leading 아바타를 본문 행과 별도 컨트롤로 렌더링한다. `onClick`/`onPress`/`href` 및 `trailingAction`과 중첩되지 않는다. NotificationItem도 같은 prop을 받는다.
|
|
168
|
+
|
|
169
|
+
회원 아바타가 놓인 `leadingShape="circle"` 행은 `leadingSize="large"`(48) 프레임을 기본으로 쓴다(2026-10-25 사용자 크기 통일). 촘촘한 기존 목록은 `leadingSize="default"`로 40 프레임을 유지한다. 아이콘 등 다른 leading의 기본 프레임은 유지한다.
|
|
@@ -52,6 +52,18 @@ commentMenu={{ presentation: "anchored", label: commentMenuLabel,
|
|
|
52
52
|
dismissLabel: t("common.close"), items: allowedItems, onAction: handleCommentAction }}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### 관리 메뉴의 짧은 헤더 — 개발 소스, 미게시 (2026-10-25)
|
|
56
|
+
|
|
57
|
+
번뚝의 긴 분기 이력·반복 설명이 하단 메뉴를 과도하게 키운 사용자 관측에 따라,
|
|
58
|
+
양 renderer에 `title`, `description`, `dismissPlacement="header" | "footer" | "none"`를 제공한다.
|
|
59
|
+
2026-10-25 사용자의 관리 메뉴 상단 제거 요청으로 `none`을 추가했다. 제목·보조 설명을 생략하고 `none`을 지정하면 목록만 표시하며 바깥 누름·Escape/back으로 닫는다. 접근성 이름과 `dismissLabel`은 유지한다.
|
|
60
|
+
기본은 기존 `footer`다. `header`는 grouped/bottom에서 제목 옆에 지역화한
|
|
61
|
+
`dismissLabel`의 ✕를 두고 하단 닫기 버튼을 생략한다. 바깥 누름·Escape/back과
|
|
62
|
+
닫힌 후 실행 계약은 유지한다. `anchored`에는 헤더를 표시하지 않는다.
|
|
63
|
+
보조 설명은 대상 이름·현재 버전 등 짧은 맥락에 쓰며 한 줄 넘침은 말줄임한다.
|
|
64
|
+
분기 이력은 기록 화면, 현재 설정값은 trailing, 실행 결과가 불명확한 행동만 description으로 전달한다.
|
|
65
|
+
행·아이콘·본문·안전 영역과 스크롤은 기존 Menu가 소유한다.
|
|
66
|
+
|
|
55
67
|
`bottom`도 안정적인 id의 행동/선택 목록이면 Menu다. 입력 본문이 생기면 Dialog·Sheet,
|
|
56
68
|
확인 문구와 버튼만 필요하면 AlertDialog를 사용한다. Menu가 닫힌 후 여는 원칙은 세 표현 모두 동일하다.
|
|
57
69
|
실행 예제는 양 Showcase의 Menu `Grouped`·`Anchored`·`Bottom`이다.
|