@hjmds/design-contracts 1.17.1 → 1.18.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 (74) hide show
  1. package/dist/activity-chip.d.ts +127 -0
  2. package/dist/activity-chip.d.ts.map +1 -0
  3. package/dist/activity-chip.js +65 -0
  4. package/dist/activity-chip.js.map +1 -0
  5. package/dist/base-recipes.d.ts +1 -1
  6. package/dist/base-recipes.d.ts.map +1 -1
  7. package/dist/base-recipes.js +2 -1
  8. package/dist/base-recipes.js.map +1 -1
  9. package/dist/behaviors.d.ts +2 -0
  10. package/dist/behaviors.d.ts.map +1 -1
  11. package/dist/behaviors.js +1 -0
  12. package/dist/behaviors.js.map +1 -1
  13. package/dist/bottom-navigation.d.ts +12 -0
  14. package/dist/bottom-navigation.d.ts.map +1 -1
  15. package/dist/bottom-navigation.js +7 -0
  16. package/dist/bottom-navigation.js.map +1 -1
  17. package/dist/card.d.ts +4 -0
  18. package/dist/card.d.ts.map +1 -1
  19. package/dist/card.js +4 -1
  20. package/dist/card.js.map +1 -1
  21. package/dist/catalog.d.ts +39 -9
  22. package/dist/catalog.d.ts.map +1 -1
  23. package/dist/component-recipes.d.ts +33 -3
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +27 -5
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/content-transition.d.ts +1 -1
  28. package/dist/content-transition.d.ts.map +1 -1
  29. package/dist/content-transition.js +2 -2
  30. package/dist/content-transition.js.map +1 -1
  31. package/dist/icon-button-recipe.d.ts +1 -5
  32. package/dist/icon-button-recipe.d.ts.map +1 -1
  33. package/dist/icon-button-recipe.js +2 -1
  34. package/dist/icon-button-recipe.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/screen-patterns.d.ts +6 -0
  40. package/dist/screen-patterns.d.ts.map +1 -1
  41. package/dist/screen-patterns.js +5 -0
  42. package/dist/screen-patterns.js.map +1 -1
  43. package/dist/toast-liquid.d.ts +1 -1
  44. package/dist/toast-liquid.d.ts.map +1 -1
  45. package/dist/toast-liquid.js +3 -2
  46. package/dist/toast-liquid.js.map +1 -1
  47. package/dist/toast.d.ts +19 -1
  48. package/dist/toast.d.ts.map +1 -1
  49. package/dist/toast.js +16 -1
  50. package/dist/toast.js.map +1 -1
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/version.js.map +1 -1
  54. package/docs/activity-chip.md +34 -0
  55. package/docs/bottom-navigation.md +5 -1
  56. package/docs/generated/component-maturity.md +1 -1
  57. package/docs/generated/renderer-evidence.json +3 -3
  58. package/docs/generated/renderer-evidence.md +1 -1
  59. package/docs/generated/showcase-manifest.json +1 -1
  60. package/docs/toast.md +23 -0
  61. package/docs/usage/README.md +2 -0
  62. package/docs/usage/components/activity-chip.md +109 -0
  63. package/docs/usage/components/bottom-navigation.md +2 -1
  64. package/docs/usage/components/button.md +4 -1
  65. package/docs/usage/components/card.md +27 -6
  66. package/docs/usage/components/chat-message.md +4 -0
  67. package/docs/usage/components/content-transition.md +1 -0
  68. package/docs/usage/components/icon-button.md +3 -0
  69. package/docs/usage/components/settings-screen.md +7 -7
  70. package/docs/usage/components/switch.md +9 -1
  71. package/docs/usage/components/text-area.md +3 -0
  72. package/docs/usage/components/toast.md +26 -8
  73. package/docs/usage/compositions/activity-status.md +162 -0
  74. package/package.json +7 -1
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-08
7
+ - 검토일: 2026-10-10
8
8
  - 근거: [BottomNavigation](../../bottom-navigation.md), recipe `bottomNavigationRecipe`
9
9
  - 스토리북: `배포/컴포넌트/탐색/하단 탐색`
10
10
 
@@ -73,6 +73,7 @@ import { BottomNavigation } from "@hjmds/react-native/navigation";
73
73
  | `configuration.density` | `regular` · `compact` | `regular` | — |
74
74
  | `configuration.direction` | `ltr` · `rtl` | Provider 환경(없으면 `ltr`) | — |
75
75
  | `configuration.keyboardBehavior` | `hide` · `remain` | `hide` | 기본은 소프트 키보드가 열리면 막대를 숨긴다 |
76
+ | `configuration.labels` | `visible` · `hidden` | `visible` | `hidden`은 아이콘만 남기고 목적지 이름을 화면에서 뺀다. 이름은 항목의 접근성 이름으로 그대로 읽힌다. 아이콘을 칸 가운데에 둔다. 선택 표시는 아이콘 굵기·확대(와 제품 선택 표시)만 남으므로 의도한 아이콘 전용 막대에만 쓴다 |
76
77
  | `primaryAction` | `ReactNode`(Button/IconButton) | — | `configuration` 밖의 별도 prop. 생성 같은 비목적지 행동이며 `center-gap` 또는 `capsule`과 짝짓는다 |
77
78
  | 항목 `badge` | `{ count, max?, accessibilityLabel }` | `max` 99 | 숫자만. 점 배지는 없다. `count`가 0이면 그리지 않는다 |
78
79
  | `onActivate` | `(activation: { key: Key; reason: "navigate" \| "reselect" }) => void` | Web 선택 · Native 필수 | 이동 의도만 알린다. 선택은 바꾸지 않는다. `reselect`는 이미 선택된 항목을 다시 누른 경우 |
@@ -106,7 +106,7 @@ import { Button } from "@hjmds/react-native/actions";
106
106
  Native에서 `style`·`labelStyle`을 넘기면 실행 중 `TypeError`가 난다.
107
107
  - 색은 tone과 제품 테마 토큰으로 바꾼다. 버튼마다 브랜드 색을 하드코딩하지 않는다.
108
108
  - 진행 중 상태는 `loading`으로 표시하고 같은 자리에 별도 Spinner를 겹치지 않는다. children을 빈 문자열이나 다른 길이의 진행 문구로 바꾸지 않는다. 렌더러가 문구·아이콘을 숨긴 자리에 중앙 스피너만 표시하고 기존 크기·접근성 이름을 유지한다. 2026-10-06 독립 검증에서 이 사용자 요구가 사용 지침에는 빠져 있음을 확인해 명시했다.
109
- - "한 화면 `primary` 하나"의 예외는 다음과 같다. `selected`를 준 버튼은 tone과 관계없이 `buttonRecipe.states.selected`(배경 `bg`, 글자·테두리 `contentBrand`)로 칠해져 primary 채움이 아니므로 세지 않고(Web `.hjm-button[data-selected="true"]`, Native `internal/recipe-button.tsx`, 검색·작품 탐색 스토리의 필터 줄), Sheet·Dialog 안의 행동은 그 표면 안에서 primary 하나를 센다([Sheet](sheet.md) `footer`, 작품 탐색·랜딩 스토리의 시트).
109
+ - "한 화면 `primary` 하나"의 예외는 다음과 같다. `selected`를 준 버튼은 tone과 관계없이 `buttonRecipe.states.selected`(배경 `bg`, 글자 `contentBrand`, 선택 테두리 없음)로 칠해져 primary 채움이 아니므로 세지 않고(Web `.hjm-button[data-selected="true"]`, Native `internal/recipe-button.tsx`, 검색·작품 탐색 스토리의 필터 줄), Sheet·Dialog 안의 행동은 그 표면 안에서 primary 하나를 센다([Sheet](sheet.md) `footer`, 작품 탐색·랜딩 스토리의 시트).
110
110
 
111
111
  - 긴 소개 화면에서 위·아래 CTA가 **동일한 행동**으로 이어지고 서로 다른 스크롤 구간에 있으면 둘 다 primary를 허용한다. 큰 글자에서 첫 CTA가 멀어지는 문제를 보완하기 위한 예외이며, 서로 다른 가입·구매 행동을 동시에 강조하는 근거로 쓰지 않는다.
112
112
 
@@ -122,3 +122,6 @@ import { Button } from "@hjmds/react-native/actions";
122
122
 
123
123
 
124
124
  - 2026-10-06 독립 지침 재구현에서 footer 순서를 모든 본문 행동에 강제하는 것으로 읽혔다. 시간 선택처럼 주 행동 다음에 초기화가 오는 구성과 AlertDialog의 좁은 폭 순서는 각각 명시된 구성 계약을 따른다.
125
+
126
+ 2026-10-10 사용자 요청으로 선택 시 추가되는 테두리를 제거했다. `selected` 접근성 상태와
127
+ contentBrand 글자·아이콘 색은 유지하며 키보드 focus outline은 선택과 별도로 유지한다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-10
8
8
  - 근거: recipe `cardRecipe`(`src/card.ts`), 바탕 `surfaceRecipe`(`src/base-recipes.ts`). 별도 계약 문서는 없다
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/카드`
10
10
 
@@ -78,9 +78,31 @@ import { Button } from "@hjmds/react-native/actions";
78
78
  | `selected` | `boolean` | `false` | `true`면 tone이 `accent`로 바뀐다(`cardRecipe.selectedTone`) |
79
79
  | `headingLevel`(Web만) | `2` · `3` · `4` | `3` | 제목이 `h3`로 렌더되므로 문서 위계에 맞춰 고른다 |
80
80
  | `title` · `description` · `leading` · `media` · `actions` · `children` | `ReactNode` | — | 슬롯. 순서는 HJM이 고정한다 |
81
+ | `onActivate` | `() => void` | — | 카드 전체가 하나의 행동일 때 사용(미게시). Web `aria-label`, Native `accessibilityLabel` 필수 |
82
+ | `disabled` | `boolean` | `false` | 전체 카드 행동 차단·비활성 피드백(미게시) |
81
83
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. 두 플랫폼 모두 root Surface에 적용된다 |
82
84
 
83
- Card 자체에는 콜백이 없다. 누름 행동은 `actions`의 Button(Web `onClick: (event: MouseEvent<HTMLButtonElement>) => void`, Native `onPress`)이 갖는다.
85
+ ### 카드 전체 누름(미게시)
86
+
87
+ 2026-10-10 번뚝의 세 열 시작점 선택에서 별도 선택 버튼이 좁은 카드의 절반을 차지했다.
88
+ `onActivate`는 기존 Card의 구조를 유지하면서 전체 표면을 하나의 행동으로 만든다.
89
+ 제품이 Card 바깥에 Pressable을 다시 조립하는 대신 HJM이 버튼 역할, 선택·비활성 상태,
90
+ 44pt 최소 대상, 눌림·초점 피드백을 소유한다. 같은 카드를 다시 눌러도 콜백을 호출하므로
91
+ 제품이 수정된 초안 교체 확인을 처리할 수 있다. RadioGroup은 단일 옵션 값 변경용이며,
92
+ 이처럼 같은 항목을 다시 실행하는 카드 동작을 대신하지 않는다.
93
+
94
+ ```tsx
95
+ <Card aria-label={starter.title} onActivate={() => choose(starter)}
96
+ selected={selectedId === starter.id} disabled={pending} bordered={selectedId === starter.id} padding="sm">
97
+ <span>{starter.emoji} {starter.title}</span>
98
+ </Card>
99
+ // Native는 aria-label 대신 accessibilityLabel을 전달한다.
100
+ ```
101
+
102
+ `onActivate`와 `actions`를 함께 쓰지 않는다. children·media·leading에도 입력/링크/버튼을
103
+ 넣지 않는다. 내부 행동이 여러 개인 카드에는 기존 `actions` 슬롯을 사용한다.
104
+ 기존 passive Card는 콜백 없이 동일하게 유지된다. 이 API는 로컬 source/candidate 단계이며
105
+ npm 게시 및 소비 제품의 게시 버전 채택 완료를 뜻하지 않는다.
84
106
 
85
107
  2026-10-07 [확장 카드·그리드 대조](../../../../../docs/plans/aceternity-interaction-adoption-2026-10-07.md)에서
86
108
  카드와 상세 표시를 별도 새 카드 엔진으로 복제할 필요가 없는 경우를 확인했다. 상세는 `actions`의
@@ -112,13 +134,13 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
112
134
  - 색·radius·padding은 `tone`·`radius`·`padding` 축으로만 바꾼다. 카드마다 브랜드 색을 칠하지 않는다.
113
135
  - 배치는 두 플랫폼 모두 `layoutStyle`로 한다. Native `CardProps`에는 `style`이 없다. Web의 `className`·`style`로 recipe 값을 덮지 않는다.
114
136
  카드 목록 사이 간격은 바깥 [Stack](stack.md)·[Grid](grid.md)이 정한다.
115
- - Card 자체는 누름 행동이 없다(Native는 `onPress`가 없다). 카드 안 행동은 `actions`의 Button으로 둔다.
137
+ - 카드 전체 행동은 `onActivate`만 사용한다. 카드 안 행동이 있으면 전체 행동을 빼고 `actions`의 Button으로 둔다.
116
138
 
117
139
  ## 플랫폼 차이
118
140
 
119
141
  | 항목 | Web | Native |
120
142
  | --- | --- | --- |
121
- | root | `<article>` | `View`(Surface) |
143
+ | root | 기본 `<article>`, 전체 행동은 `<button type="button">` | 기본 `View`(Surface), 전체 행동은 `Pressable` |
122
144
  | 제목 | `h{headingLevel}` | `Text` + `accessibilityRole="header"`, 수준 지정 없음 |
123
145
  | 배치 | `layoutStyle` | `layoutStyle` |
124
146
  | 내용 clip | tone의 `clipsContent`(`raised`는 그림자 때문에 clip 안 함) | 내부 `View`가 항상 clip, 그림자는 바깥에 남는다. frame과 clip이 같은 Provider radius token을 사용 |
@@ -129,5 +151,4 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
129
151
  등록되면 그 테마의 `tokens.radius.lg`를 쓴다. 2026-10-07 카드 갤러리 비교에서 Native의 바깥
130
152
  Surface만 테마를 따르고 내부 media clip은 foundation 값을 쓰는 누락을 발견해 같은 token으로 연결했다.
131
153
 
132
- - `selected`는 tone만 바꾼다. Web은 `data-state="selected"` 속성뿐이고 Native는 접근성 state를
133
- 알리지 않는다. 선택 가능한 카드 목록이라면 선택 상태를 문구나 다른 컨트롤로도 전달한다.
154
+ - passive Card의 `selected`는 tone 표시다. 전체 행동 Card는 Web `aria-pressed`, Native `accessibilityState.selected`로도 선택을 알린다.
@@ -73,6 +73,8 @@ import { Text } from "@hjmds/react-native/primitives";
73
73
  | `direction` | `"incoming"` · `"outgoing"` | 필수 | 수신은 시작 쪽, 발신은 끝 쪽에 붙는다 |
74
74
  | `author` | `string` | 필수 | 빈 문자열이면 작성자 줄을 그리지 않는다(연속 메시지 그룹) |
75
75
  | `timestamp` | `string` | 필수 | 제품이 포맷한 시각. 빈 문자열이고 `deliveryLabel`·`actions`도 없으면 메타 줄을 그리지 않는다 |
76
+ | `timestampPresentation` | `"always"` · `"swipe"` | `"always"` | `swipe`는 수신은 오른쪽·발신은 왼쪽으로 끄는 동안 반대편 여백에 시각을 보여 주고 놓으면 숨긴다. 이 모드에서는 같은 제스처로 답장을 실행하지 않는다. 시각은 보조기술에서도 읽을 수 있다. |
77
+ | `bubbleTail` | `boolean` | `false` | 말풍선 본체와 이어진 꼬리를 수신 시작 쪽·발신 끝 쪽에 표시한다. 같은 표면·테두리를 쓰며 행 양쪽 `spacing.xs` 8 여백으로 잘림을 막는다. |
76
78
  | `deliveryLabel` | `string` | 없음 | 시각 뒤에 ` · `로 붙는 전송 상태 |
77
79
  | `avatar` | `ReactNode` | 없음 | 버블 옆 아바타 |
78
80
  | `reply` | `ReactNode` | 없음 | 인용 노드. `replyLink`가 없으면 버블 안 인용으로 그린다 |
@@ -130,3 +132,5 @@ import { Text } from "@hjmds/react-native/primitives";
130
132
  ### 고정 아이콘과 큰 글자
131
133
 
132
134
  2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
135
+
136
+ 2026-10-09 번뚝 DM 사용자 요청으로 시각 공개 동작을 공용 ChatMessage에 추가했다. 앱별 PanResponder/PointerEvent 복제를 피하고 세로 스크롤·내부 컨트롤 우선권을 유지한다.
@@ -67,6 +67,7 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
67
67
  | prop | 값 | 기본값 | 설명 |
68
68
  | --- | --- | --- | --- |
69
69
  | `preset` | `fade` · `rise` · `slide` · `scale` | provider `designProfile.interactions.contentTransition`, 프로필 없으면 `fade` | 명시한 prop이 프로필보다 우선. `rise`는 아래 12에서, `slide`는 가로 16(RTL이면 반대), `scale`은 0.96에서 시작 |
70
+ | `travelDirection` | `forward` · `backward` | `forward` | `slide`의 등장 방향. 이전 페이지는 `backward`, 다음 페이지는 `forward`; 언어 방향과 함께 공통 resolver가 계산한다. |
70
71
  | `motion` | `system` · `none` | `system` | `system`은 reduced motion을 따르고 `none`은 항상 즉시 교체 |
71
72
  | `stateKey` | `string` | — (필수) | 바뀔 때만 새 내용이 나타난다 |
72
73
  | `enterOnMount` | boolean | false | 미게시: 새 데이터 항목이 처음 추가될 때도 등장 전환. 이미 표시한 항목은 stable key를 유지한다 |
@@ -124,3 +124,6 @@ TopBar
124
124
  - children의 색은 버튼이 칠하지 않는다. HJM Icon은 자기 `tone` 색을 쓰므로, `primary`·`danger` 버튼 안에서는
125
125
  `tone="inverse"`를 준다. 다른 아이콘 라이브러리의 컴포넌트를 직접 넣으면(Native) 색을 제품이 넘겨야 한다.
126
126
  - Native `IconButton` `style`은 deprecated지만 다음 major 전까지 recipe 뒤에 합쳐져 지름·배경까지 바뀐다. 새 코드에서 쓰지 않는다.
127
+
128
+ 2026-10-10 사용자 요청: Button과 같은 선택 표현을 쓴다. 선택은 bg 바탕·contentBrand 아이콘으로
129
+ 표시하고 추가 선택 테두리는 없다. aria-pressed/Native selected와 키보드 focus outline은 유지한다.
@@ -3,7 +3,7 @@
3
3
  - 단계: 컴포넌트
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 1.17.1
7
7
  - 검토일: 2026-10-08
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
9
  - 스토리북: `배포/화면/설정/앱 설정`
@@ -11,7 +11,7 @@
11
11
  ## 언제 쓰나
12
12
 
13
13
  앱의 설정 화면 전체에 쓴다. 제목, 선택적 프로필 영역, id가 있는 섹션 목록을 받아
14
- [ScreenLayout](screen-layout.md) 안에 [Section](section.md)으로 쌓는다. 섹션 안의 행은
14
+ [ScreenLayout](screen-layout.md) 안에 그룹으로 쌓는다(Web은 의미 있는 Section, Native는 Stack과 header 역할). 섹션 안의 행은
15
15
  기존 ListRow·Switch·Select·Field를 그대로 넣는다.
16
16
 
17
17
  2026-09-23 소비 감사에서 제품마다 같은 설정 화면을 직접 조립하고 있었다. 제목·섹션 틀·구분선·
@@ -88,21 +88,21 @@ import { ListRow } from "@hjmds/react-native/data-display";
88
88
 
89
89
  | prop | 값 | 기본값 | 설명 |
90
90
  | --- | --- | --- | --- |
91
- | `sections` | `readonly { id: string; title: string; description?: string; children: ReactNode }[]` | 필수 | 각 항목을 Section 하나로 그린다. `id`는 안정적인 key |
91
+ | `sections` | `readonly { id: string; title: string; description?: string; children: ReactNode }[]` | 필수 | 각 항목을 설정 그룹 하나로 그린다. `id`는 안정적인 key |
92
92
  | `profile` | `ReactNode` | 없음 | 섹션 위 프로필 영역 |
93
93
  | 나머지 | `ScreenLayout`과 같음(`children`·`scroll` 제외) | — | `title` 필수, `state`·`stateAction`·`notice`·`footer`·`header`·`leading`·`actions`·`contentInset`. 화면 전체가 스크롤한다 |
94
94
 
95
- 두 플랫폼 모두 섹션 내용 위에 1px 구분선을 그리고 회색 카드 배경은 쓰지 않는다(2026-10-05 사용자 결정).
95
+ 두 플랫폼 모두 그룹 위의 별도 구분선과 카드 배경을 두지 않는다. 행 구분은 List·Switch의 계약을 따른다(2026-10-08 실제 설정 화면 재검토).
96
96
 
97
97
  ## 배치
98
98
 
99
99
  | 항목 | 값 | 근거 |
100
100
  | --- | --- | --- |
101
101
  | 크기 | ScreenLayout 폭(최대 720) 안에서 섹션을 세로로 쌓는다; 행 높이는 ListRow·Switch row 계약 | `ScreenLayout`, `Section` |
102
- | 간격 | 화면 padding `spacing.md` 16; `profile`·섹션 사이 `spacing.xl` 24; 섹션 제목–내용 `spacing.xs` 8, 제목–설명 `spacing.xxs` 4; 행 사이 간격은 없고 구분선 1 | Web·Native `SettingsScreen` `Stack gap="xl"`, `sectionRecipe` |
102
+ | 간격 | 화면 padding `spacing.md` 16; `profile`·섹션 사이 `spacing.md` 16; 그룹 제목–내용 `spacing.xs` 8; Native 설명도 같은 Stack 간격. 행 간격·구분은 행 계약 | Web·Native `SettingsScreen` `Stack gap="md"` |
103
103
  | 순서·정렬 | 헤더(제목) → `notice` → `profile` → `sections` 순서대로 → `footer` | 렌더 순서 |
104
104
  | 고정·스크롤 | 헤더·footer 고정, 본문 화면 스크롤(`scroll` 고정 `"screen"`); 저장 실패는 `notice` | `ScreenLayout` |
105
- | 좁은 폭·큰 글자 | 섹션 제목·행 문구는 줄바꿈되고 자르지 않는다; 제목 열 최소 폭 120 × 글자 배율 | `.hjm-section` `overflow-wrap: anywhere`, `headerMinWidth` |
105
+ | 좁은 폭·큰 글자 | 섹션 제목·행 문구는 줄바꿈되고 자르지 않는다 | Text와 각 행의 내용 기반 높이 |
106
106
 
107
107
  ## 꼭 지킬 것
108
108
 
@@ -120,7 +120,7 @@ import { ListRow } from "@hjmds/react-native/data-display";
120
120
 
121
121
  | 항목 | Web | Native |
122
122
  | --- | --- | --- |
123
- | 섹션 구분 | Section(`hjm-settings-section`) 내용 위 1px 구분선(CSS) | Section + 내용 위 1px 구분선(`View`) |
123
+ | 섹션 구분 | Section(`hjm-settings-section`), 별도 그룹 선 없음 | Stack + header 역할, 별도 그룹 선 없음 |
124
124
  | 조각 import | `Switch` `/selection`, `ListRow` `/display` | `Switch` `/inputs`, `ListRow` `/data-display` |
125
125
  | Switch 기본 `presentation` | `inline` | `row` |
126
126
  | 행 이동 이벤트 | ListRow `href`/`onClick` | ListRow `onPress` |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-08
8
8
  - 근거: [제품 채택 1.4 §설정 한 행](../../product-adoption-1.4.md), `src/component-recipes.ts`(`switchRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/입력/스위치`
10
10
 
@@ -118,3 +118,11 @@ presentation="row" 큰 글자(≥1.6)
118
118
  - Native 꺼짐 상태에는 테두리가 없으므로, 어두운 카드 위에서 track이 배경과 섞이는지 실제 기기 다크 모드로 확인한다.
119
119
  (Web은 이 hairline이 빠져 보이지 않던 일이 있었고 현재 stylesheet가 그린다.)
120
120
  - Web은 `type="button"`이 기본이라 폼 안에서도 submit을 일으키지 않는다.
121
+
122
+ **설정 행의 글자 역할**
123
+
124
+ 2026-10-08 Spint 설정 화면에서 Native의 고정 `bodyLarge` 항목 이름이 설명과
125
+ 섹션 제목보다 지나치게 강하게 보였다. `presentation="row"`의 이름은 두 renderer
126
+ 모두 `switchRecipe.rowLabelTextVariant`의 `body` 역할을 사용한다. 설명은 `caption`,
127
+ 최소 터치 영역과 두 줄 행 높이는 그대로 유지한다. inline의 기존 글자 처리는 유지한다.
128
+ 앱에서 Text로 이름을 다시 조립하거나 내부 글자 스타일을 덮지 않는다.
@@ -112,3 +112,6 @@ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력
112
112
 
113
113
  - 현재 Web `TextField`의 `onValueChange` 주석은 "Web TextArea와 같은 콜백"이라고 적지만 Web `TextAreaProps`에는
114
114
  `onValueChange`가 없다(`react/src/forms.tsx`). 공유 폼 코드는 Web TextArea만 `onChange`로 분기한다.
115
+
116
+
117
+ 2026-10-09 번뚝 시뮬레이터 확인: Native bounded 입력은 같은 너비의 비노출 텍스트로 줄 높이를 측정한다. iOS TextInput의 contentSize가 현재 viewport 높이로 제한되는 경우를 피하고, 마지막 줄바꿈도 크기에 포함한다. 화면에서 수동 픽셀 높이를 덧씌우지 않는다. 최대 표시 줄에 도달하기 전에는 커지고 이후 내부 스크롤을 사용한다.
@@ -4,9 +4,9 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-10
8
8
  - 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
9
- - 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`
9
+ - 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`, `실험/구성/피드백과 복구/백그라운드 작업 상태`
10
10
 
11
11
  ## 언제 쓰나
12
12
 
@@ -20,6 +20,7 @@
20
20
  | 화면 안에 계속 남아야 하는 안내·경고(점검 예정, 권한 없음, 폼 오류 요약) | [Notice](notice.md) |
21
21
  | 사용자가 응답해야 진행되는 확인·삭제 확인·필수 선택 | [AlertDialog](alert-dialog.md) |
22
22
  | 작업 결과를 화면 전체로 보여 줌 | [Result](result.md) |
23
+ | 헤더에 작업 상태를 상시 표시하고 눌러서 패널을 여닫음 | [ActivityChip](activity-chip.md) + 이 Toast 패널([구성](../compositions/activity-status.md)) |
23
24
 
24
25
  선택 기준: 사용자가 놓쳐도 되고 자리를 차지하면 안 되면 Toast, 그 화면을 보는 동안 계속 보여야 하거나
25
26
  읽지 않으면 다음 행동을 잘못할 수 있으면 Notice다. Toast에는 그 알림에서만 할 수 있는 행동을 두지 않는다.
@@ -78,7 +79,9 @@ function useProfileSavedToast() {
78
79
 
79
80
  | prop | 값 | 기본값 | 설명 |
80
81
  | --- | --- | --- | --- |
81
- | descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`announcement`·`presentation` 선택 | — | 모두 `string`(ReactNode 아님) |
82
+ | descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`announcement`·`presentation`·`media`·`progress` 선택 | — | 문구는 모두 `string`(ReactNode 아님). `media`만 예외 |
83
+ | `media` | `unknown`(Web·Native에서 `ReactNode`를 넘긴다) | — | 미게시(1.17.2 이후). 장식 시각물(마스코트·아바타). tone 배지 자리를 44(`toastRecipe.media.size`) 원으로 대신하고 보조기술에서 숨긴다 |
84
+ | `progress` | `{ steps: readonly string[]; current: number }` | — | 미게시(1.17.2 이후). 단계 2~5개, `current`는 0부터. 설명 아래 점+이름 줄로 그리고, 기본 낭독 끝에 현재 단계 이름을 붙인다. 범위 밖이면 throw |
82
85
  | `action` | `{ label, accessibilityLabel?, onAction: () => void, dismissOnAction? }` | — | 누르면 `onAction`, 기본으로 알림이 닫힌다 |
83
86
  | `tone` | `neutral` · `info` · `success` · `warning` · `danger` | `neutral` | — |
84
87
  | `priority` | `normal` · `high` | — | 낭독 순서, 색과 무관 |
@@ -101,10 +104,10 @@ function useProfileSavedToast() {
101
104
  프로필 없는 소비자의 기본값은 유지한다. 상태·초안·선택·Modal teardown은 이 축의 소유가 아니다.
102
105
  플랫폼 근사와 미검증 범위는 [프로필 계약](../../design-profile.md#오버레이선택-입력의-프로필-연결-보완)을 따른다.
103
106
 
104
- Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`와 `shadow.raised`를
107
+ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`(패널 이전 `radius.lg`)과 `shadow.raised`를
105
108
  읽는다. 일반 Toast의 `floating` 대신 `raised`를 유지하는 이유는 리퀴드 카드의 얕은 깊이를
106
109
  정한 기존 검토다. RN 내용 clip과 Skia 카드 모서리를 함께 바꾸고 그림자의 위·아래·좌·우
107
- 여유를 확보한다. 프로필이 없으면 기존 모서리 12와 foundation `raised`를 유지한다.
110
+ 여유를 확보한다. 프로필이 없으면 모서리 24(패널, 미게시 1.17.2 이후. 그전 12)와 foundation `raised`를 쓴다.
108
111
  원형 시작점·큐·행동은 유지하며 Web은 일반 Toast fallback이다. 기존
109
112
  `실험/구성/비교와 검증/테마 조합`의 Native ‘물방울 알림의 테마 비교’에서 같은 알림을
110
113
  유지한 채 다음 테마로 바꿀 수 있다. 기기 시각·음성·제스처 검증은 별도로 남는다.
@@ -114,9 +117,9 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
114
117
 
115
118
  | 항목 | 값 | 근거 |
116
119
  | --- | --- | --- |
117
- | 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 열은 [아이콘 배지 32][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
120
+ | 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 모서리 `radius.xl` 24(패널, 미게시 1.17.2 이후. 그전 `lg` 16). 열은 [아이콘 배지 32 또는 `media` 44][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill. `progress` 점 6(현재 단계 1.4배) | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
118
121
  | 간격 | 화면 가장자리에서 `spacing.md` 16 + 안전 영역(`safeAreaMode: "additive"`). 여러 장 사이 `spacing.sm` 12. 카드 안 열 간격 `spacing.sm` 12 | `toastRecipe.viewport`, `.hjm-toast-viewport` |
119
- | 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. `action`은 둘째 줄 끝 | `toastRecipe.placements`, `.hjm-toast__action` |
122
+ | 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. 문구 열 안 순서는 제목 → 설명 → `progress` → `action`. `action`은 문구 아래 시작 쪽(`toastRecipe.action.align` `start`, 미게시 1.17.2 이후. 그전 둘째 줄 끝) | `toastRecipe.placements`, `.hjm-toast__action` |
120
123
  | 고정·스크롤 | 화면 흐름 밖에 떠 있는 층이라 본문 레이아웃에 자리를 만들지 않는다. 하단 고정 바(BottomNavigation·BottomCTA)가 있으면 Web `bottomOffset`(px 또는 CSS 길이), Native `keyboardOffset`으로 그 위에 띄운다. Native 아래쪽 배치는 `avoidKeyboard`(기본 true)로 키보드 위에 선다 | `react/src/toast.tsx`, `react-native/src/feedback.tsx`(`ToastRegion`) |
121
124
  | 좁은 폭·큰 글자 | 폭이 줄고 문구가 줄바꿈되어 카드가 세로로 자란다. 잘라 내지 않는다 | `.hjm-toast__content`(`overflow-wrap: anywhere`) |
122
125
 
@@ -126,13 +129,28 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
126
129
  │ 본문(스크롤) │ │ 본문(스크롤) │
127
130
  │ │ │ ┌──────────────────────────┐ │
128
131
  │ ┌──────────────────────────┐ │ │ │(●) 저장했어요 [×] │ │
129
- │ │(●) 저장했어요 [×] │ │ │ │ [되돌리기] │ │ ← action(선택)
132
+ │ │(●) 저장했어요 [×] │ │ │ │ [되돌리기] │ │ ← action(선택), 문구 아래 시작 쪽
130
133
  │ └──────────────────────────┘ │ │ └──────────────────────────┘ │
131
134
  │ ↑ 16 + 안전 영역 │ │ ↑ bottomOffset/keyboardOffset
132
135
  └──────────────────────────────┘ │ [홈] [검색] [내 정보] │ ← 고정 바
133
136
  좌우 16, 최대 폭 420, 가운데 └──────────────────────────────┘
134
137
  ```
135
138
 
139
+ ### 패널 — media·progress (미게시, 1.17.2 이후)
140
+
141
+ 2026-10-10 사용자 요청으로 패널이 Toast의 모양이 됐다(기본·리퀴드 모두). 진행 중인 작업은 같은 `id`, `durationMs: null`로
142
+ 열어 두고 단계가 바뀔 때마다 같은 `id`로 다시 `publish`해 제자리에서 갱신한다. 끝나면 `progress`를 빼고 결과 문구와
143
+ `action`으로 바꾼다. 여닫는 트리거는 [ActivityChip](activity-chip.md)이다.
144
+
145
+ ```text
146
+ ┌──────────────────────────────────────┐
147
+ │ (🐥) 도우미 [×] │ ← media 44가 tone 배지 자리를 대신
148
+ │ 앱을 다듬고 있어요. │
149
+ │ ● 읽기 ◉ 다듬기 ○ 확인 │ ← progress: 완료·현재(굵게)·예정, 점 6
150
+ │ [결과 보기] │ ← action: 문구 아래 시작 쪽
151
+ └──────────────────────────────────────┘ 모서리 radius.xl 24
152
+ ```
153
+
136
154
  ## 꼭 지킬 것
137
155
 
138
156
  - Provider/Region은 앱에 하나만 두고 화면마다 새로 만들지 않는다. 진행 상태를 바꿀 때는 같은 `id`로 다시 `publish`한다.
@@ -0,0 +1,162 @@
1
+ # 백그라운드 작업 상태
2
+
3
+ - 단계: 구성
4
+ - 상태: 실험
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.17.2 이후)
7
+ - 검토일: 2026-10-10
8
+ - 근거: [ActivityChip 계약](../../activity-chip.md), [Toast 패널](../../toast.md#패널-toast--미게시1172-이후), 양 Showcase `activity-status-preview.tsx`, `showcase/shared/activity-status.ts`
9
+ - 스토리북: `실험/구성/피드백과 복구/백그라운드 작업 상태`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 사용자가 다른 화면을 보는 동안 계속되는 작업 하나(검수·생성·업로드)의 진행을 헤더의 작은 칩으로 알리고, 칩을 누르면
14
+ 같은 작업의 패널 Toast가 열려 단계와 결과를 보여 줄 때 쓴다. 2026-10-10 사용자 요청("구성에 칩 누르면 나오게 하자")으로
15
+ BurnTok 검수 상태 흐름을 공통 구성으로 만들었다. 작업 상태·문구·마스코트·결과 화면은 제품 소유다.
16
+
17
+ ## 구성 요소
18
+
19
+ | 컴포넌트 | 역할 | 지침 |
20
+ | --- | --- | --- |
21
+ | TopBar | 칩이 놓이는 헤더(`trailing`) | [TopBar](../components/top-bar.md) |
22
+ | ActivityChip | 작업 상태 표시 겸 패널 여닫기 | [ActivityChip](../components/activity-chip.md) |
23
+ | Toast(패널) | 같은 id로 갱신되는 진행·결과 패널(`media`·`progress`·`action`) | [Toast](../components/toast.md) |
24
+ | ToastProvider·ToastRegion | 앱 루트의 알림 층(위쪽 배치 권장) | [Toast](../components/toast.md) |
25
+
26
+ ## 배치
27
+
28
+ ```text
29
+ ┌ 안전 영역 ────────────────────────────────────────┐
30
+ │ [←] 내 작업 ( (🐥) 다듬는 중 ) │ ← TopBar, trailing에 ActivityChip(높이 44, 최대 폭 180)
31
+ ├───────────────────────────────────────────────────┤
32
+ │ ┌───────────────────────────────────────────────┐ │ ← 패널 Toast(placement="top"), 칩을 누르면 열림
33
+ │ │ (🐥) 도우미 [×] │ │
34
+ │ │ 앱을 다듬고 있어요. │ │
35
+ │ │ ● 읽기 ◉ 다듬기 ○ 확인 │ │ ← progress(진행 중)
36
+ │ └───────────────────────────────────────────────┘ │
37
+ │ 본문(스크롤) — 작업과 무관하게 계속 쓸 수 있다 │
38
+ └───────────────────────────────────────────────────┘
39
+ 끝나면: 칩 ( (🐥) 완료 ) emphasis, 패널은 progress 대신 [결과 보기] action
40
+ ```
41
+
42
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
43
+ | --- | --- | --- | --- |
44
+ | 바깥 틀 | 화면 + 앱 루트 ToastProvider/ToastRegion | 칩은 헤더, 패널은 화면 위 떠 있는 층 | Toast 가장자리 `spacing.md` 16 + 안전 영역 |
45
+ | 헤더 | TopBar + ActivityChip | `trailing` 첫 자리(제목이 없는 헤더면 빈 가운데 자리) | 칩 높이 44(`control.minTouchTarget`)·최대 폭 180 |
46
+ | 패널 | Toast(`media`·`progress`·`action`) | 위쪽 배치 `placement="top"` | 최대 폭 420, 모서리 `radius.xl` 24, media 44 |
47
+ | 본문 | 제품 화면 | 헤더 아래 | 패널이 떠 있어도 자리를 차지하지 않는다 |
48
+
49
+ ## 흐름과 상태
50
+
51
+ 1. 작업이 시작되면 헤더에 칩이 나타난다(`label`=진행 문구, `expanded=false`). 작업이 없으면 칩을 렌더하지 않는다.
52
+ 2. 칩을 누르면 `open=true`가 되고 패널 Toast를 `id` 고정, `durationMs: null`, `media`, `progress`로 `publish`한다.
53
+ 3. 단계가 바뀌면 같은 `id`로 다시 `publish`한다. 기본 `duplicatePolicy: "update"`라 쌓이지 않고 제자리에서 갱신된다.
54
+ 4. 칩을 다시 누르면 Web `close(id)` / Native `dismiss(id, "programmatic")`로 닫는다. 닫기 버튼·Escape·스와이프로 닫혀도
55
+ descriptor의 `onDismiss`가 불리므로 거기서 `open=false`로 되돌린다(칩의 `expanded`와 패널이 어긋나지 않게).
56
+ 5. 작업이 끝나면 칩은 결과 문구 + `emphasis="emphasis"`로 바뀌고, 열린 패널은 `progress` 대신 `tone: "success"`와
57
+ `action`(결과 보기)으로 갱신된다. 패널이 닫혀 있으면 칩은 잠시(예제 4초) 뒤 사라진다.
58
+
59
+ | 상태 | 모습 | 포커스·알림 |
60
+ | --- | --- | --- |
61
+ | 기본 | 작업 없음: 칩 없음, 헤더 자리 비어 있음 | — |
62
+ | 진행 중 | 칩 `다듬는 중`, 열면 패널에 단계 점과 이름 | 칩은 `expanded`를 말한다. 패널 낭독은 `제목. 설명. 현재 단계` |
63
+ | 완료 | 칩 `완료`(brand), 패널은 성공 문구 + 결과 보기 | 갱신된 문구를 다시 낭독한다. action은 문구 아래 시작 쪽 |
64
+ | 실패 | 칩 문구를 실패로 바꾸고 패널은 `tone: "danger"` + 다시 시도 action(제품이 연결) | 예제는 실패를 만들지 않는다. 서버 실패 복구는 제품 소유 |
65
+
66
+ ## 코드 골격
67
+
68
+ ```tsx
69
+ // Web
70
+ import { ActivityChip } from "@hjmds/react/activity-chip";
71
+ import { useToast } from "@hjmds/react/toast";
72
+ import { TopBar } from "@hjmds/react/top-bar";
73
+
74
+ function ReviewHeader({ task }: { task: ReviewTask | null }) {
75
+ const toast = useToast();
76
+ const [open, setOpen] = useState(false);
77
+ useEffect(() => {
78
+ if (!open || !task) return;
79
+ toast.publish({
80
+ id: "review-status",
81
+ title: t("review.title"),
82
+ description: t(task.done ? "review.done" : "review.running"),
83
+ tone: task.done ? "success" : "neutral",
84
+ durationMs: null,
85
+ media: <img src={mascotUrl} alt="" />,
86
+ ...(task.done
87
+ ? { action: { label: t("review.open"), onAction: openResult } }
88
+ : { progress: { steps: [t("review.read"), t("review.polish"), t("review.check")], current: task.step } }),
89
+ closeLabel: t("common.close"),
90
+ onDismiss: () => setOpen(false),
91
+ });
92
+ }, [open, task, toast]);
93
+ return (
94
+ <TopBar
95
+ title={t("screen.title")}
96
+ trailing={task ? (
97
+ <ActivityChip
98
+ label={t(task.done ? "review.chip.done" : "review.chip.running")}
99
+ emphasis={task.done ? "emphasis" : "default"}
100
+ expanded={open}
101
+ media={<img src={mascotUrl} alt="" />}
102
+ onClick={() => (open ? toast.close("review-status") : setOpen(true))}
103
+ />
104
+ ) : undefined}
105
+ />
106
+ );
107
+ }
108
+ ```
109
+
110
+ ```tsx
111
+ // Native
112
+ import { Image } from "react-native";
113
+ import { ActivityChip } from "@hjmds/react-native/activity-chip";
114
+ import { useToastRegion } from "@hjmds/react-native/feedback";
115
+ import { TopBar } from "@hjmds/react-native/top-bar";
116
+
117
+ // 앱 루트: <ToastRegion placement="top" maxVisible={1} layoutStyle={{ flex: 1 }}>…</ToastRegion>
118
+ function ReviewHeader({ task }: { task: ReviewTask | null }) {
119
+ const toast = useToastRegion();
120
+ const [open, setOpen] = useState(false);
121
+ useEffect(() => {
122
+ if (!open || !task) return;
123
+ toast.publish(reviewPanel(task, <Image source={mascot} style={{ width: 44, height: 44 }} />, () => setOpen(false)));
124
+ }, [open, task, toast]);
125
+ return (
126
+ <TopBar
127
+ title={t("screen.title")}
128
+ {...(task ? {
129
+ trailing: (
130
+ <ActivityChip
131
+ label={t(task.done ? "review.chip.done" : "review.chip.running")}
132
+ emphasis={task.done ? "emphasis" : "default"}
133
+ expanded={open}
134
+ media={<Image source={mascot} style={{ width: 28, height: 28 }} />}
135
+ onPress={() => (open ? toast.dismiss("review-status", "programmatic") : setOpen(true))}
136
+ />
137
+ ),
138
+ } : {})}
139
+ />
140
+ );
141
+ }
142
+ ```
143
+
144
+ `reviewPanel`은 Web 예의 descriptor와 같은 모양을 돌려주는 제품 함수다(예제는 `showcase/shared/activity-status.ts`의
145
+ `activityStatusDescriptor`).
146
+
147
+ ## 플랫폼 차이
148
+
149
+ | 항목 | Web | Native |
150
+ | --- | --- | --- |
151
+ | 알림 층 | `ToastProvider label placement="top"` | `ToastRegion placement="top" maxVisible={1}`, 안전 영역은 제품이 `safeAreaInsets`로 |
152
+ | 패널 닫기 | `useToast().close(id)` | `useToastRegion().dismiss(id, "programmatic")` |
153
+ | 리퀴드 표현 | 없음(같은 패널 카드) | `presentation: "liquid"` + `presentationAdapter`면 같은 패널 열을 물방울 연출로 연다 |
154
+ | 칩 누름 | `onClick` | `onPress` |
155
+
156
+ ## 함정
157
+
158
+ - 갱신할 때마다 새 `id`를 쓰면 패널이 쌓인다. 작업 하나에 `id` 하나를 고정한다.
159
+ - 패널을 닫는 경로가 여럿(칩·닫기·Escape·스와이프)이라 `open` 상태는 `onDismiss`에서만 내린다. 칩 핸들러에서 먼저
160
+ `setOpen(false)`하고 `close`를 부르면 닫히는 동안(closing) 다시 누른 `publish`가 `ignored`(`closing`)로 돌아올 수 있다.
161
+ - `media`는 장식이다. 마스코트에 의미가 있으면 그 뜻을 `title`·`description`에 쓴다.
162
+ - 현재 스토리는 작업 진행을 타이머로 흉내 낸다. 제품은 자기 작업 이벤트로 단계를 바꾼다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -720,6 +720,12 @@
720
720
  "react-native": "./dist/collection-rail.js",
721
721
  "import": "./dist/collection-rail.js",
722
722
  "default": "./dist/collection-rail.js"
723
+ },
724
+ "./activity-chip": {
725
+ "types": "./dist/activity-chip.d.ts",
726
+ "react-native": "./dist/activity-chip.js",
727
+ "import": "./dist/activity-chip.js",
728
+ "default": "./dist/activity-chip.js"
723
729
  }
724
730
  },
725
731
  "files": [