@hjmds/design-contracts 1.46.0 → 1.47.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.
Files changed (89) hide show
  1. package/dist/behaviors.d.ts +2 -1
  2. package/dist/behaviors.d.ts.map +1 -1
  3. package/dist/bottom-navigation.d.ts +4 -0
  4. package/dist/bottom-navigation.d.ts.map +1 -1
  5. package/dist/bottom-navigation.js +12 -0
  6. package/dist/bottom-navigation.js.map +1 -1
  7. package/dist/card.d.ts +2 -2
  8. package/dist/card.d.ts.map +1 -1
  9. package/dist/card.js +3 -2
  10. package/dist/card.js.map +1 -1
  11. package/dist/carousel.d.ts +1 -0
  12. package/dist/carousel.d.ts.map +1 -1
  13. package/dist/carousel.js +1 -0
  14. package/dist/carousel.js.map +1 -1
  15. package/dist/catalog.d.ts +130 -14
  16. package/dist/catalog.d.ts.map +1 -1
  17. package/dist/catalog.js +4 -3
  18. package/dist/catalog.js.map +1 -1
  19. package/dist/color-picker.d.ts +51 -1
  20. package/dist/color-picker.d.ts.map +1 -1
  21. package/dist/color-picker.js +56 -2
  22. package/dist/color-picker.js.map +1 -1
  23. package/dist/component-recipes.d.ts +23 -9
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +24 -18
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/conversation-row.d.ts +1 -1
  28. package/dist/conversation-row.d.ts.map +1 -1
  29. package/dist/conversation-row.js +2 -1
  30. package/dist/conversation-row.js.map +1 -1
  31. package/dist/date-range.d.ts +122 -3
  32. package/dist/date-range.d.ts.map +1 -1
  33. package/dist/date-range.js +139 -5
  34. package/dist/date-range.js.map +1 -1
  35. package/dist/icon.d.ts +2 -1
  36. package/dist/icon.d.ts.map +1 -1
  37. package/dist/icon.js +2 -0
  38. package/dist/icon.js.map +1 -1
  39. package/dist/recipes.d.ts +1 -0
  40. package/dist/recipes.d.ts.map +1 -1
  41. package/dist/recipes.js +1 -0
  42. package/dist/recipes.js.map +1 -1
  43. package/dist/screen-patterns.d.ts +33 -2
  44. package/dist/screen-patterns.d.ts.map +1 -1
  45. package/dist/screen-patterns.js +27 -2
  46. package/dist/screen-patterns.js.map +1 -1
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/dist/version.js.map +1 -1
  50. package/docs/bottom-navigation.md +16 -0
  51. package/docs/color-picker.md +24 -19
  52. package/docs/generated/component-maturity.md +3 -3
  53. package/docs/generated/renderer-evidence.json +59 -7
  54. package/docs/generated/renderer-evidence.md +3 -2
  55. package/docs/generated/showcase-manifest.json +18 -4
  56. package/docs/usage/README.md +5 -2
  57. package/docs/usage/SELECTION.md +109 -0
  58. package/docs/usage/components/avatar.md +31 -6
  59. package/docs/usage/components/bottom-navigation.md +16 -0
  60. package/docs/usage/components/button.md +11 -0
  61. package/docs/usage/components/card.md +6 -0
  62. package/docs/usage/components/chat-message.md +10 -2
  63. package/docs/usage/components/chat-room-members.md +4 -0
  64. package/docs/usage/components/chat-screen.md +10 -1
  65. package/docs/usage/components/color-picker.md +65 -28
  66. package/docs/usage/components/comment-thread-screen.md +16 -1
  67. package/docs/usage/components/conversation-list-screen.md +81 -0
  68. package/docs/usage/components/conversation-row.md +5 -1
  69. package/docs/usage/components/date-range-picker.md +15 -5
  70. package/docs/usage/components/event-attendance-card.md +4 -0
  71. package/docs/usage/components/follow-list-screen.md +109 -0
  72. package/docs/usage/components/follow-profile-card.md +4 -0
  73. package/docs/usage/components/list-row.md +6 -0
  74. package/docs/usage/components/menu.md +12 -0
  75. package/docs/usage/components/notice.md +8 -3
  76. package/docs/usage/components/notification-inbox-screen.md +10 -0
  77. package/docs/usage/components/notification-row.md +4 -0
  78. package/docs/usage/components/page-indicator.md +1 -0
  79. package/docs/usage/components/profile-summary.md +32 -3
  80. package/docs/usage/components/section.md +2 -2
  81. package/docs/usage/components/settings-screen.md +1 -1
  82. package/docs/usage/components/sheet.md +1 -0
  83. package/docs/usage/components/statistic.md +1 -1
  84. package/docs/usage/compositions/web-additions.md +5 -5
  85. package/docs/usage/screens/common-comments.md +1 -0
  86. package/docs/usage/screens/common-conversation-list.md +22 -1
  87. package/docs/usage/screens/common-follow-list.md +87 -0
  88. package/docs/usage/screens/common-settings.md +23 -0
  89. package/package.json +1 -1
@@ -45,12 +45,13 @@ import { ProfileSummary } from '@hjmds/react-native/screen-flows';
45
45
  | prop | 값 | 기본값 | 설명 |
46
46
  | --- | --- | --- | --- |
47
47
  | name | string | 필수 | 제목(이름) |
48
- | avatar | ReactNode | 필수 | 원형 72 프레임 안에 넣는 아바타 |
48
+ | avatar | ReactNode | 필수 | 원형 48 프레임 안에 넣는 아바타 |
49
49
  | headingLevel | 1~6 | 2 | 이름 제목 수준 |
50
50
  | badge | ReactNode | 없음 | 이름 옆 배지 |
51
51
  | nameActions | ReactNode | 없음 | 이름 행의 행동 |
52
+ | nameEdit | `{label,onAction,disabled?,pending?}` | 없음 | 이름 옆 편집(연필) 아이콘 버튼. 내 프로필에서 닉네임 바꾸기를 연다(2026-10-25, 아래 절) |
52
53
  | stats | `{id,value,label,onPress?,disabled?}[]` | `[]` | 통계 수치. `onPress`가 있으면 버튼 |
53
- | actionsLayout | `inline` · `stacked` | `inline` | 미게시. 가로 보조 행동은 기존 줄바꿈 행, 팔로우/메시지처럼 같은 폭의 주·보조 행동은 stacked로 세로 배치 |
54
+ | actionsLayout | `inline` · `stacked` · `equal` | `inline` | 가로 보조 행동은 inline 줄바꿈 행, 세로 전체 폭은 stacked, 같은 폭의 가로 행동은 equal(개발 소스) |
54
55
  | actions | ReactNode | 없음 | 수치 아래 행동 줄. [ProfileScreen](profile-screen.md) `editPlacement="summary"` 안에서는 그 화면의 `edit`이 이 줄 맨 앞에 `secondary` `small` 버튼(`profileSummaryRecipe.editAction`)으로 먼저 들어간다(1.31.0) |
55
56
  | children | ReactNode | 없음 | 하단 footer |
56
57
 
@@ -60,7 +61,7 @@ import { ProfileSummary } from '@hjmds/react-native/screen-flows';
60
61
 
61
62
  | 항목 | 값 | 근거 |
62
63
  | --- | --- | --- |
63
- | 크기 | 원형 아바타 프레임 72. ProfileScreen 수정 행동은 `Button` `secondary` `small` | `profileSummaryRecipe.avatarSize`·`editAction` |
64
+ | 크기 | 원형 아바타 프레임 48. ProfileScreen 수정 행동은 `Button` `secondary` `small` | `profileSummaryRecipe.avatarSize`·`editAction` |
64
65
  | 간격 | 수치 칸 사이 `spacing.md` 16, 값·라벨 사이 `spacing.xxs` 4, 행동 사이 `spacing.xs` 8. 영역 간격은 [CompositionLayout](composition-layout.md) | 양 renderer |
65
66
  | 순서·정렬 | 머리(아바타 → 이름·배지 → nameActions) → 수치 줄 → actions → children, 수치 칸은 균등 폭·가운데 | `CompositionLayout` + `flex: 1` 칸 |
66
67
  | 고정·스크롤 | 없음. ProfileScreen 본문 스크롤 안에 놓인다 | — |
@@ -87,3 +88,31 @@ Web의 텍스트/이모지 artwork는 원형72 프레임 안에서 glyph.xxl48
87
88
  actions={<><Button fullWidth onClick={follow}>{followLabel}</Button><Button fullWidth tone="secondary" onClick={message}>{messageLabel}</Button></>} />
88
89
  // Native는 같은 actionsLayout에 Button.onPress를 연결한다.
89
90
  ```
91
+
92
+ ### 같은 폭의 가로 행동 (2026-10-25)
93
+
94
+ 번뚝의 Instagram형 프로필 요청에 따라 `actionsLayout="equal"`은 버튼과 메뉴 trigger를 같은 폭의 한 행으로 배치한다. HJM Grid가 열·간격을 소유하며 fragment는 셀로 세지 않는다. 오류 안내는 actions 밖의 children에 공급한다. 팔로우·해제 메뉴·메시지 경로와 문구는 제품이 연결한다. 기존 inline/stacked 동작은 유지한다.
95
+
96
+ ### 내 프로필: 사진은 사진 변경, 이름 옆 연필은 닉네임 변경 (2026-10-25)
97
+
98
+ 사용자 지시로 모든 앱·웹의 내 프로필 카드에 같은 규칙을 쓴다.
99
+
100
+ - **사진을 누르면 프로필 사진 변경**이 열린다. `avatar`에 `Avatar action={{label: t("photo.change"), onPress}}`를 넣는다(이미지 고르기·자르기·업로드는 제품 소유).
101
+ - **이름 옆에 `nameEdit`(연필 아이콘 버튼)**를 두고 누르면 닉네임 변경이 열린다. 아이콘·크기(`profileSummaryRecipe.nameEdit`, small ghost 36 + hitSlop 4 = 44)·위치(`nameActions` 앞)는 HJM이 소유한다. 연필 그림은 제품 아이콘 세트의 연필을 `nameEdit.icon`으로 넣어 이름 줄의 다른 아이콘(설정 톱니 등)과 같은 선 모양으로 맞춘다. 넣지 않으면 HJM 기본 연필을 그린다.
102
+ - 같은 카드에 "프로필 편집"·"사진 바꾸기"·"닉네임 바꾸기" 같은 별도 버튼을 중복으로 두지 않는다. `ProfileScreen`의 `edit`은 이제 선택이며 손님의 로그인 같은 다른 행동에만 쓴다.
103
+ - 남의 프로필에는 `nameEdit`를 넣지 않고, 사진은 회원 아바타 기준(확대·신고)을 따른다.
104
+ - 제품이 ProfileSummary 대신 고유 머리를 그리는 경우에도 사진 = 사진 변경, 이름 = 닉네임 변경을 지킨다. 이름이 그림(예: Spint 내 정원의 나무 팻말)이면 연필을 따로 두지 않고 그 이름 그림 자체를 닉네임 변경 버튼으로 한다(2026-10-25 사용자 결정).
105
+
106
+ ### 프로필 카드 틀 (2026-10-25)
107
+
108
+ 같은 날 사용자 지적: Utilverse 프로필 카드(평평한 `default`, radius `lg`)가 BurnTok 카드(`raised` 그림자, 테두리, radius `xl`)와
109
+ 달라 보였다. ProfileSummary를 카드에 담는 화면은 `Surface tone="raised" bordered radius="xl" padding="md"`로 감싼다(Web·Native 같은 값).
110
+ 제품 컨셉 때문에 카드 대신 고유 머리를 그리는 화면(Spint 내 정원)은 예외로 그 사유를 코드 주석에 남긴다.
111
+
112
+ ### 손님 카드의 로그인 (2026-10-25)
113
+
114
+ 로그인하지 않은 사용자의 프로필 카드는 로그인이 카드의 주 행동이다. `actionsLayout="stacked"` + `actions`에 전체 폭 primary
115
+ `Button` 하나를 둔다. 이름 옆 작은 버튼(`nameActions`)이나 ProfileScreen `edit` 자리(작은 secondary)를 빌리지 않는다 —
116
+ Utilverse에서 작은 왼쪽 버튼이 카드 안에서 떠 보였다는 사용자 지적으로 정했다. 손님 카드에는 `nameEdit`와 사진 변경을 두지 않는다.
117
+
118
+ 2026-10-25 사용자 요청으로 회원 아바타 프레임은 `avatarRecipe.sizes.large`(48px 원형)이다. 댓글·채팅처럼 작은 목록의 크기는 바꾸지 않는다.
@@ -78,14 +78,14 @@ import { Section } from "@hjmds/react-native/primitives";
78
78
  | 항목 | 값 | 근거 |
79
79
  | --- | --- | --- |
80
80
  | 크기 | 자체 배경·테두리·여백이 없다. 폭은 부모를 따른다 | `.hjm-section` |
81
- | 글자 | 제목 `title` 18/26 500(제목 굵기), 설명 `caption` 11/16(두 플랫폼). 2026-10-06까지 Web은 둘 다 본문 14를 물려받았다(1.13.0 변경) | `sectionRecipe.title`·`description`, `.hjm-section__title`·`__description` |
81
+ | 글자 | 제목 `bodyLarge` 16/24 500(반복 구획이 본문을 압도하지 않도록 2026-10-25 번뚝 리뷰 반영, 개발 소스·미게시), 설명 `caption` 11/16(두 플랫폼). 2026-10-06까지 Web은 둘 다 본문 14를 물려받았다(1.13.0 변경) | `sectionRecipe.title`·`description`, `.hjm-section__title`·`__description` |
82
82
  | 간격 | 머리와 본문 사이 `spacing.xs` 8, 글자 묶음과 행동 사이 `spacing.xs` 8, 제목과 설명 사이 `spacing.xxs` 4. Section끼리의 간격은 Section이 주지 않는다. 감싸는 [Stack](stack.md)이나 `layoutStyle`로 `layout.sectionGap`(`spacing.xl` 24)을 둔다 | `sectionRecipe.gap`·`headerGap`·`copyGap`, `layout.sectionGap` |
83
83
  | 순서·정렬 | 머리 행동은 끝(LTR 오른쪽)에 하나, 글자 묶음과 세로 가운데 정렬. 보통 [Link](link.md) 또는 `tone="link"` [Button](button.md) | `.hjm-section__header`, Native `Section` |
84
84
  | 고정·스크롤 | 고정되지 않는다. 화면 본문 스크롤 안에 둔다 | — |
85
85
  | 좁은 폭·큰 글자 | 폭과 관계없이 한 행을 유지한다. 글자 크기 160% 이상(`largeTextThreshold`)에서만 머리 행이 세로로 쌓이고 행동이 글자 아래로 간다(두 플랫폼 같은 조건). 2026-10-17까지 Web은 폭 600 미만에서도 쌓여, 같은 휴대폰 폭에서 Native는 한 행인데 Web만 행동이 아래 줄로 늘어났다(화면 리뷰 S5#3) | Web `.hjm-root[data-large-text]`, Native `textScale >= 1.6` |
86
86
 
87
87
  ```text
88
- 최근 본 장소 [전체 보기] ← 머리: 제목 title 18, gap spacing.sm 12
88
+ 최근 본 장소 [전체 보기] ← 머리: 제목 bodyLarge 16, gap spacing.sm 12
89
89
  자주 가는 곳이에요 ← 설명: caption 11, gap spacing.xxs 4
90
90
  gap spacing.xs 8
91
91
  ┌──────────── 본문 ────────────┐
@@ -72,7 +72,7 @@
72
72
  Web 기본은 `inline`, Native 기본은 `row`이므로 Web·Native 같은 화면에서는 `presentation`을 명시한다.
73
73
  - ListRow의 trailing에 Switch만 둘 때는 `labelVisibility="hidden"`을 쓰고 그 ListRow에는 `onPress`를 두지 않는다.
74
74
  - 언어·화면 테마·기본 정렬처럼 한 값을 고르는 설정은 [ChoiceRow](choice-row.md)를 쓴다. 행 `description`에 확인된 값을 보이고 시트의 선택·저장 중·실패를 ChoiceRow가 소유한다. `ListRow`+`Sheet`+`RadioGroup`이나 `Select`로 다시 조립하지 않는다(2026-10-23 [선택 기준](choice-row.md#설정-값-선택의-기준)).
75
- - 섹션 `id`는 안정적인 키로 둔다(번역 문구를 id로 쓰지 않는다).
75
+ - 섹션 `id`는 안정적인 키로 둔다(번역 문구를 id로 쓰지 않는다). 그룹 id·이름·순서는 [설정 그룹 표준](../screens/common-settings.md#설정-그룹-표준-2026-10-25)을 따른다(2026-10-25).
76
76
  - 저장 방식·낙관적 갱신·실패 복구·탈퇴 확인은 제품과 [action-session](../../action-session.md)이 소유한다.
77
77
  저장 실패는 `state`가 아니라 `notice`로 알린다.
78
78
  - 배치: Web·Native 모두 `layoutStyle`(Web은 `className`도 있다). 섹션 색·여백을 덮지 않는다.
@@ -245,6 +245,7 @@ import { SheetPanel } from "@hjmds/react-native/sheet-panel";
245
245
 
246
246
  - 배치는 손잡이(최소 44) → 본문이며 위 모서리만 `sheetRecipe.content.radius`로 둥글게 한다. 하단은 화면에 붙어 지도색이 새지 않게 각진다. 좌우 바깥 여백은 부모의 대칭 `layoutStyle`로 연결한다.
247
247
  - `maximumHeight`는 부모의 지도/탭 영역을 제외한 가용 높이, `height`는 선택 상태 간 유지할 높이다. 본문만 스크롤하며 고정 높이에서 남은 영역을 채운다. 부모가 이미 safe area를 제외한 지도 높이를 주므로 safe area를 다시 더하지 않는다.
248
+ - `safeAreaBottom`(기본 0)은 시트 아래 가장자리를 덮는 다른 요소의 높이다(예: 위로 솟은 하단 탭 버튼). 표면은 그대로 아래 끝까지 닿아 탭바와 한 면으로 이어지고, 본문 아래 여백과 접힌 상태의 보이는 높이에 이 값을 더해 내용과 손잡이가 가려지지 않는다. 음수·무한대는 `RangeError`다. 2026-10-25 다에리 지도 시트가 솟은 메아리 탭과 겹치지 않으려고 탭바 위로 떠서 지도 띠가 보였던 문제에서 추가했다.
248
249
  - 접으면 손잡이만 남는다. 손잡이 끌기와 본문 맨 위의 아래 방향 끌기가 같은 snap 정책을 쓴다. 취소되면 시작 위치로 돌아오며 높이가 바뀌어도 접힘 상태를 유지한다. 내용 안에서 시작한 스크롤은 중간에 맨 위에 도달해도 시트를 끌지 않는다.
249
250
  - 손잡이를 눌러도 접기/펼치기가 가능하며 `accessibilityState.expanded`가 바뀐다. 접힌 본문은 보조기술에서 숨기고 뒤 지도는 숨기지 않는다. 포커스 격리·backdrop·Android back 가로채기는 없다.
250
251
  - 제품의 가로 넘김은 `contentGesture`로 제공한다. 수직 동작/멀티터치/짧은 탭을 가로 넘김이 빼앗지 않도록 제품 gesture의 방향 기준을 지정한다. `handleActions`/`onHandleAction`으로 이전/다음 같은 도메인 접근성 행동을 추가할 수 있다. `overlay`는 제품 그림·소인 슬롯이다.
@@ -116,7 +116,7 @@ Native 프레임의 모서리는 `statisticRecipe.presentations`의 역할을 Pr
116
116
  | 항목 | 값 | 근거 |
117
117
  | --- | --- | --- |
118
118
  | 크기 | `presentation="surface"`의 안쪽 여백: `comfortable` `spacing.md` 16 · `compact` `spacing.sm` 12(두 플랫폼). 테두리 `stroke.default` 1, radius `md` 12 | `statisticRecipe.density`·`presentations`, `styles.css` `.hjm-statistic[data-presentation="surface"]` |
119
- | 글자 | 라벨 `label` 12 medium(`compact` `caption` 11), 값 `heading` 24 semibold(`fontWeight.semibold`, `compact` `bodyLarge` 16. 2026-10-21 다에리 오늘의 기록 숫자가 크다는 사용자 피드백으로 compact 값을 한 단계 낮춤. 2026-10-17 큰 글자 단계 폐지로 `heading.level2` 굵기 참조를 끊음), prefix·suffix `body` 14 medium 본문색, hint `caption`, 추세 `caption` medium(두 플랫폼). 2026-10-06까지 Web은 라벨 본문 14 medium, 값은 밀도와 관계없이 18, prefix·suffix는 흐린색이었다(1.13.0 변경) | `statisticRecipe.density`·`label`·`value`·`affix`·`hint`·`trend`, `.hjm-statistic__*` |
119
+ | 글자 | 라벨 `label` 12 medium(`compact` `caption` 11), 값 `title` 18 semibold(2026-10-25 번뚝 리뷰: 콘텐츠보다 큰 요약 숫자를 줄임, 개발 소스·미게시)(`fontWeight.semibold`, `compact` `bodyLarge` 16. 2026-10-21 다에리 오늘의 기록 숫자가 크다는 사용자 피드백으로 compact 값을 한 단계 낮춤. 2026-10-17 큰 글자 단계 폐지로 `heading.level2` 굵기 참조를 끊음), prefix·suffix `body` 14 medium 본문색, hint `caption`, 추세 `caption` medium(두 플랫폼). 2026-10-06까지 Web은 라벨 본문 14 medium, 값은 밀도와 관계없이 18, prefix·suffix는 흐린색이었다(1.13.0 변경) | `statisticRecipe.density`·`label`·`value`·`affix`·`hint`·`trend`, `.hjm-statistic__*` |
120
120
  | 간격 | 그룹 간격 `statisticRecipe.group.gap` = `spacing.xs` 8. 지표 안 줄 간격(라벨·값·추세) `comfortable` `spacing.xs` 8 · `compact` `spacing.xxs` 4(두 플랫폼) | `styles.css` `.hjm-statistic`·`.hjm-statistic-group`, `statisticRecipe` |
121
121
  | 순서·정렬 | 화면 위쪽 요약 영역이나 카드 안에 둔다. 지표 여러 개는 [Grid](grid.md)를 직접 짜지 말고 `StatisticGroup`으로 묶는다 | — |
122
122
  | 고정·스크롤 | — | — |
@@ -27,7 +27,7 @@ Native 화면에서 같은 일이 필요하면 이 구성을 옮기지 않는다
27
27
 
28
28
  | 컴포넌트 | 역할 | 지침 |
29
29
  | --- | --- | --- |
30
- | `ColorPicker` | 네이티브 색 입력 + HEX 입력 + 불투명도(`alpha`) + 프리셋 견본. controlled | [ColorPicker](../components/color-picker.md) |
30
+ | `ColorPicker` | 채도·밝기 영역 + 색조 막대 + 불투명도(`alpha`) + HEX 입력 + 프리셋 견본. controlled | [ColorPicker](../components/color-picker.md) |
31
31
  | 선택값 문구 `Text` `role="status"` | 바뀐 HEX를 알림. 값은 `<bdi dir="ltr">`로 감싼다 | [Text](../components/text.md) |
32
32
  | `Watermark` | 본문 위 장식 SVG 타일. 포인터·선택을 가로채지 않는다 | [Watermark](../components/watermark.md) |
33
33
  | `Affix` | 가장 가까운 스크롤 조상 상단에 `position: sticky`로 붙는다 | [Affix](../components/affix.md) |
@@ -60,14 +60,14 @@ Native 화면에서 같은 일이 필요하면 이 구성을 옮기지 않는다
60
60
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
61
61
  | --- | --- | --- | --- |
62
62
  | 바깥 틀 | HJM ScreenLayout의 본문 | 세 스토리 모두 바깥 폭·여백을 정하지 않는다. 페이지 본문은 [Container](../components/container.md)가 정한다. Affix만 가장 가까운 스크롤 조상(문서 또는 제품의 `overflow: auto` 영역)에 붙는다. Web 전용이라 안전 영역·화면 키보드 처리는 없다 | Container gutter 폭 600 미만 `compact` 16 · 이상 `regular` 20 |
63
- | 색 입력 줄 | `ColorPicker` 색 견본 + HEX | fieldset 첫 줄, 좁으면 줄바꿈(`flex-wrap`) | 입력 최소 높이 `colorPickerRecipe.minTargetSize` 44, 색 견본 폭 3rem, 줄 간격 0.75rem |
64
- | 불투명도·미리보기·프리셋 | `ColorPicker` 내부 | 색 입력 아래 순서대로 | 각 위 여백 0.75rem, 프리셋 사이 0.5rem, 프리셋 최소 높이 44 |
63
+ | 색 편집 | `ColorPicker` 영역·막대 | fieldset 첫 부분, 폭을 채운다 | 영역 4:3, 막대 높이 24, 부품 사이 `spacing.md` |
64
+ | 미리보기·HEX·프리셋 | `ColorPicker` 내부 | 막대 아래 순서대로 | 미리보기 원 44, 견본 누름 영역 44(원 32) |
65
65
  | 선택값 | live 문구 | ColorPicker 바로 아래, 같은 Stack | `spacing.md` 16. 스토리는 `Container size="reading"`(최대 720) |
66
66
  | 워터마크 | `Watermark` | 감싼 본문 전체를 덮는 오버레이(`inset: 0`) | `tileWidth` 240 · `tileHeight` 160 · `rotate` -22 · `opacity` 0.12(기본값), 색 `--hjm-color-text-sub` |
67
67
  | 고정 행동 | `Affix` | 스크롤 조상 상단 + `offset` | `offset` 기본 0, 띄울 때는 spacing 토큰 값(스토리 `spacing.xs` 8). 스크롤 조상보다 높으면 고정하지 않고 흐름에 남는다 |
68
68
  | 저장 실패 | `Notice` danger | 저장 버튼 위(Watermark 본문 안, Affix 아래 본문 첫머리) | 버튼과 `spacing.md` 16 |
69
69
 
70
- - ColorPicker 내부 간격은 CSS가 rem 값으로 직접 둔다(`padding: 1rem`, `gap: 0.75rem`·`0.5rem`). 제품에서 덮지 않는다.
70
+ - ColorPicker 내부 간격은 HJM이 둔다(`padding: 1rem`, 부품 사이 `spacing.md`). 제품에서 덮지 않는다.
71
71
  - Affix는 스크롤 조상 안에서만 붙는다. 그 조상의 높이·overflow는 HJM 화면 골격이 정한다(스토리의 280px 박스는 데모 값).
72
72
 
73
73
  ## 흐름과 상태
@@ -104,7 +104,7 @@ import { Stack, Text } from "@hjmds/react/layout";
104
104
  <Stack gap="md">
105
105
  <ColorPicker label={t("theme.accent")} value={color} onValueChange={setColor} alpha
106
106
  presets={productPresets}
107
- labels={{ color: t("theme.color"), hex: t("theme.hex"), opacity: t("theme.opacity"), invalid: t("theme.invalidHex") }} />
107
+ labels={{ hex: t("theme.hex"), hue: t("theme.hue"), saturation: t("theme.saturation"), brightness: t("theme.brightness"), opacity: t("theme.opacity"), invalid: t("theme.invalidHex"), presets: t("theme.presets") }} />
108
108
  <Text role="status">{t("theme.selected")} <bdi dir="ltr">{color}</bdi></Text>
109
109
  </Stack>;
110
110
 
@@ -57,6 +57,7 @@ host: 남은 높이·safe area·키보드
57
57
  | --- | --- | --- |
58
58
  | 답글 접기·펼치기 | expandedIds와 원문 데이터 분리 | 서버 순서를 보존하고 접어도 답글 초안을 지우지 않는다 |
59
59
  | 목록 추가 조회 실패 | 기존 items + threadFooter 재시도 | 이미 읽던 댓글과 스크롤을 유지한다 |
60
+ | 답글 더 보기 | `moreRepliesLabel` + (서버 페이지면) `repliesPage` | 답글은 5개씩 열고 받은 답글을 먼저 보인 뒤 다음 페이지를 요청한다([답글 5개씩](../components/comment-thread-screen.md#답글-5개씩-2026-10-25-정책-미게시)) |
60
61
  | 첫 댓글 | empty + composer | 최초 로딩 실패와 실제 0건을 구분한다 |
61
62
  | 전송 실패 | 기존 ready 또는 empty + notice | 초안·답글 대상을 유지한다. 항목별 좋아요 실패는 해당 항목만 복구한다 |
62
63
 
@@ -23,7 +23,7 @@ host: 남은 높이·safe area
23
23
 
24
24
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
25
25
  | --- | --- | --- | --- |
26
- | 바깥 틀 | ScreenLayout(`scroll="content"`, `contentInset="body-edge"`) | route 본문 | 헤더·notice는 화면 recipe padding 16, 본문 좌우는 0(행이 여백 소유) |
26
+ | 바깥 틀 | ConversationListScreen(가상화 host는 `scroll="content"`) | route 본문 | 헤더·notice는 화면 recipe padding 16, 본문 좌우는 0(행이 여백 소유) |
27
27
  | 검색 | SearchField(라벨 "대화 검색", 검색 아이콘, placeholder "이름이나 메시지 검색") | `notice` | 화면 recipe itemGap 12. 결과가 없으면 `state.empty` |
28
28
  | 행 | 라우팅 host(Pressable·링크) + ConversationRow | 본문 목록 | 아바타 56, 행 padding 가로 16(`conversationRowRecipe.paddingInline`)·세로 10, 화면 끝에서 아바타까지 16 |
29
29
  | 행 메뉴 | ContextMenu | 행 host를 감싼다 | Native 카드 폭 최대 280, 행 48, 아이콘 24 |
@@ -107,3 +107,24 @@ import { ContextMenu } from "@hjmds/react/context-menu";
107
107
  - 헤더 새 대화 IconButton·`notice` 검색·Web 메뉴 아이콘: 2026-10-17 화면 검토(S5)에서 예제가 새 대화 행동 없이, `notice`에 데모 안내 문구를 두고, Web과 Native 문구·메뉴 모양이 갈라진 것을 확인했다. 예제 데이터·문구는 `showcase/shared/conversation-list.ts` 하나를 두 플랫폼이 읽는다.
108
108
  - 검색 아이콘·placeholder, 행동 결과 줄의 위치: 같은 날 재검증에서 notice로 옮긴 SearchField가 아이콘도 placeholder도 없는 빈 상자였고, 데모 행동 결과("미리보기 도구")가 기본 스토리 화면 위에 여백 없이 붙어 있었다. 아이콘은 Web `renderSearchIcon`·Native `renderLeading`(lucide `Search`)이고, 결과 줄은 다른 화면처럼 도구 스토리("행동 결과 확인")에서만 보인다.
109
109
  - 본문 `contentInset="body-edge"`: 2026-10-17 사용자 결정. 화면 검토 S5#9에서 본문 padding 16과 행 padding 16이 겹쳐 아바타가 약 32pt 안쪽에서 시작했다(인스타그램 DM 약 16). 기존 `none`은 앱 사용처가 "host가 모든 여백 소유"로 써서 의미를 바꿀 수 없었다.
110
+
111
+
112
+ ## 공통 화면 API — 개발 소스, 미게시 (2026-10-25)
113
+
114
+ Web은 `@hjmds/react/screens`, Native는 `@hjmds/react-native/screens`의
115
+ `ConversationListScreen`을 사용한다. 기존 ScreenLayout 상태·헤더·footer·스크롤 계약을
116
+ 재사용하며 `contentInset`은 노출하지 않는다. `search`는 검색 입력, `notice`는 조회 안내다.
117
+ 두 슬롯은 공통 notice 영역에서 배치되고, 본문 여백은 행이 한 번만 소유한다.
118
+ 제품은 ConversationRow에 데이터·라우팅·메뉴를 연결한다. 목록 바깥 Card·추가 좌우 padding·
119
+ List 구분선을 넣지 않는다. Native 가상화 host만 `scroll="content"`, 일반 자식은 기본 스크롤을 쓴다.
120
+
121
+ 번뚝에서 같은 행을 쓰고도 앱 wrapper로 배치가 달라진 것이 도입 이유다.
122
+ 이전에는 이 화면 조합이 Showcase에만 있었으므로 별도 공통 화면을 공개했고, 예제와 제품이
123
+ 같은 구현을 소비한다. 기존 ScreenLayout·ListDetailScreen은 일반 화면/상세 전환용으로 유지한다.
124
+
125
+ ```tsx
126
+ import { ConversationListScreen } from "@hjmds/react/screens";
127
+ <ConversationListScreen title={title} search={searchField} actions={composeAction} state={state}>
128
+ {conversationLinks}
129
+ </ConversationListScreen>
130
+ ```
@@ -0,0 +1,87 @@
1
+ # 팔로워와 팔로잉
2
+
3
+ - 단계: 화면
4
+ - 상태: 실험
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.46.1 이후)
7
+ - 검토일: 2026-10-25
8
+ - 근거: 사용자 Instagram 팔로워 화면 요청
9
+ - 스토리북: `실험/화면/소통/팔로워와 팔로잉`
10
+
11
+ ## 목적
12
+
13
+ 두 관계 목록을 검색하고 프로필로 이동하며 팔로우한다. [FollowListScreen](../components/follow-list-screen.md)이 전체 화면을 소유한다.
14
+
15
+ ## 영역 구조
16
+
17
+ ```text
18
+ 헤더 → 관계 탭 → 정렬·검색 도구 → 검색·안내 → 인물 목록
19
+ ```
20
+
21
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
22
+ | --- | --- | --- | --- |
23
+ | 바깥 틀 | FollowListScreen | route 본문 | ScreenLayout 기본 16 여백 |
24
+ | 탐색 | Tabs·SearchField | 헤더 아래 | spacing.sm 12 |
25
+ | 인물 | List·ListRow | 스크롤 본문 | 48px 원형 아바타, comfortable, separator none |
26
+
27
+ 뒤로가기·대상 이름 → 팔로워/팔로잉 수치 탭 → 검색·비차단 안내 → 평평한 인물 목록.
28
+ 48px 원형 아바타·사용자 이름·선택적인 표시 이름은 왼쪽, 팔로우하기·맞팔로우하기 또는 메시지 보내기는 오른쪽이다. 내 팔로워의 삭제는 X 아이콘, 팔로잉의 취소는 더보기 메뉴에 둔다.
29
+
30
+ 2026-10-25 사용자 참고 화면과 후속 지시를 반영했다. 일반 회원 아바타는 48px 원형으로 통일하며 댓글·채팅의 작은 크기는 유지한다. 이미 팔로우한 회원의 버튼에 탭과 같은 “팔로잉”을 반복하지 않는다.
31
+
32
+ ## 버튼과 행동 위치
33
+
34
+ | 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
35
+ | --- | --- | --- | --- |
36
+ | 뒤로가기 | ScreenLayout.exit | 헤더 왼쪽 | 하나 |
37
+ | 팔로우 | Button primary/secondary | 행 우측 | 제품 자격에 따라 0~1 |
38
+ | 추가 행동 | 제품 action | 팔로우 다음 | 선택적 |
39
+
40
+ 뒤로가기는 헤더 왼쪽, 프로필 이동은 행, 팔로우와 삭제 확인은 독립된 우측 행동이다.
41
+
42
+ ## 상태
43
+
44
+ | 상태 | 화면 모습 | 행동 |
45
+ | --- | --- | --- |
46
+ | 기본 | 인물 목록 | 탐색·팔로우 |
47
+ | 로딩 | 중앙 로딩 | 조회 대기 |
48
+ | 빈 | 중앙 empty 문구 | 검색 변경 |
49
+ | 오류 | 중앙 조회 오류 또는 행 요청 오류 | 재시도 |
50
+
51
+ 초기 로딩·오류·빈 목록은 공통 본문 상태다. 갱신 실패는 notice와 캐시 목록을 함께 유지한다.
52
+ 팔로우 진행·실패는 해당 행에서만 표시한다.
53
+
54
+ ## 사용하는 지침
55
+
56
+ | 지침 | 쓰는 곳 |
57
+ | --- | --- |
58
+ | [FollowListScreen](../components/follow-list-screen.md) | 전체 화면과 제품 슬롯 |
59
+
60
+ [FollowListScreen](../components/follow-list-screen.md)의 슬롯·배치·소유권을 따른다.
61
+
62
+ ## 코드 골격
63
+
64
+ Web/Native `src/components/FollowListScreen.stories.tsx`의 실제 공개 API 예제를 사용한다.
65
+
66
+ ```tsx
67
+ // Web
68
+ import { FollowListScreen } from "@hjmds/react/follow-list";
69
+ ```
70
+
71
+ ```tsx
72
+ // Native
73
+ import { FollowListScreen as NativeFollowListScreen } from "@hjmds/react-native/follow-list";
74
+ // 양 renderer의 실행 props는 위 Storybook 예제에서 제공한다.
75
+ ```
76
+
77
+ ## 큰 글자·다크·좁은 폭
78
+
79
+ | 조건 | 바뀌는 것 |
80
+ | --- | --- |
81
+ | 다크 | 제품 Provider의 토큰 색상 적용 |
82
+ | 좁은 폭 | 공통 행의 텍스트 축소·줄바꿈 |
83
+ | 최대 글자 | 루트 지침에 따라 범위 제외 |
84
+
85
+ 제품 Provider 테마와 공통 행 동작을 따른다. 최대 글자 조건은 루트 지침대로 범위 제외다.
86
+
87
+ 2026-10-25 후속 조정: 더보기는 세로 점 세 개(`moreVertical`)다. 정렬 기준은 우측에 놓고 검색창은 바로 아래 항상 보인다. 정렬·검색 간격은 spacing.xs(8)다. 번뚝의 최신순은 팔로우 관계 생성 시각 내림차순이며 이름순도 선택할 수 있다.
@@ -27,6 +27,29 @@ host: 남은 높이·safe area·키보드
27
27
  | 내용 | 공개 슬롯 | 제목 → 프로필 → 설정 섹션과 행 | 화면 recipe의 sectionGap·itemGap; 슬롯 안은 각 지침 토큰 |
28
28
  | 상태 | state 또는 해당 API 상태 | 본문 자리·비차단 notice | 입력 중 실패는 본문 높이와 초안을 유지 |
29
29
 
30
+ ## 설정 그룹 표준 (2026-10-25)
31
+
32
+ 2026-10-25 사용자 지시로 정했다. 같은 날 Spint·BurnTok·Utilverse 설정을 나란히 보니 그룹 이름과 순서가 앱마다 달랐다
33
+ ("앱 설정" 한 묶음, "언어" 묶음 안의 "언어" 행 하나, 계정이 맨 아래 등). 행 아이콘 유무도 서로 달랐다.
34
+ 모든 앱·웹의 설정 화면은 아래 그룹 id·이름·순서를 쓴다.
35
+ 해당 항목이 없는 그룹은 생략한다. 순서를 바꾸거나 이름을 제품마다 바꾸지 않는다.
36
+
37
+ | 순서 | 섹션 `id` | 이름(ko / en) | 넣는 것 |
38
+ | --- | --- | --- | --- |
39
+ | 1 | `account` | 계정 / Account | 내 프로필 입구·닉네임·프로필 사진·연결한 계정. 손님이면 로그인 행 하나 |
40
+ | 2 | `display` | 화면 / Display | 화면 모드 → 언어 → 그 밖의 표시 설정(홈 화면 테마, 글자 방향 등). 언어도 여기 둔다 |
41
+ | 3 | `sound` | 소리와 진동 / Sound & haptics | 효과음·진동. 없으면 생략 |
42
+ | 4 | `notifications` | 알림 / Notifications | 푸시 알림, 알림 종류 설정, 알림함 입구 |
43
+ | 5 | `privacy` | 개인정보 / Privacy | 사용 통계 보내기, 광고 개인정보 선택, 차단한 사용자 |
44
+ | 6 | `support` | 도움과 약관 / Help & legal | 문의·문제 알리기, 개인정보처리방침, 이용약관, 오픈소스 라이선스, 버전 |
45
+ | 7 | `account-actions` | 계정 관리 / Account actions | 로그아웃, 계정 삭제. 항상 맨 끝의 별도 묶음이다(파괴 행동 규칙) |
46
+
47
+ - **그룹 이름과 행 이름을 겹치지 않는다.** "언어" 그룹 안의 "언어" 행처럼 한 행만 담는 같은 이름 그룹을 만들지 않는다. 그래서 언어와 화면 모드는 `display` 하나에 둔다.
48
+ - **"앱 설정"처럼 내용을 말하지 않는 묶음 이름을 쓰지 않는다.** 위 표의 그룹으로 나눈다.
49
+ - **행 시작 아이콘:** 선택 행(ChoiceRow)과 이동 행(ListRow)은 같은 그룹 안에서 모두 시작 아이콘을 둔다. 아이콘 크기·색은 `listRowRecipe.trailing.glyph`와 `iconColor`를 쓴다. 켜고 끄는 행(Switch row)은 아이콘 없이 label·description만 둔다. 한 그룹 안에서 아이콘 있는 선택 행과 없는 선택 행을 섞지 않는다.
50
+ - **프로필 탭이 있는 앱:** 내 프로필 탭에서 설정을 여는 앱(Spint·BurnTok)도 같은 순서를 쓴다. `account` 그룹에는 프로필 화면에서 할 수 없는 항목(닉네임·연결한 계정·사진 지우기)만 남긴다. 사진 변경 자체는 내 사진을 누르는 것으로 한다([Avatar 지침](../components/avatar.md)).
51
+ - **문구는 제품이 소유한다.** 그룹 이름은 위 의미를 지키는 제품 i18n 문구이고, 영어 외 언어는 같은 뜻으로 번역한다. `id`는 위 영문 id를 그대로 쓴다.
52
+
30
53
  ## 버튼과 행동 위치
31
54
 
32
55
  | 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {