@hjmds/design-contracts 1.1.1 → 1.3.4
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/agreement.d.ts +183 -0
- package/dist/agreement.d.ts.map +1 -0
- package/dist/agreement.js +137 -0
- package/dist/agreement.js.map +1 -0
- package/dist/anchor.d.ts +62 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +43 -0
- package/dist/anchor.js.map +1 -0
- package/dist/asset.d.ts +94 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/asset.js +60 -0
- package/dist/asset.js.map +1 -0
- package/dist/auth-screen.d.ts +119 -0
- package/dist/auth-screen.d.ts.map +1 -0
- package/dist/auth-screen.js +87 -0
- package/dist/auth-screen.js.map +1 -0
- package/dist/base-recipes.d.ts +19 -0
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +17 -0
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +363 -3
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +37 -1
- package/dist/behaviors.js.map +1 -1
- package/dist/bottom-info.d.ts +58 -0
- package/dist/bottom-info.d.ts.map +1 -0
- package/dist/bottom-info.js +48 -0
- package/dist/bottom-info.js.map +1 -0
- package/dist/carousel.d.ts +3 -3
- package/dist/carousel.js +1 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +1315 -245
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +67 -25
- package/dist/catalog.js.map +1 -1
- package/dist/collapsible.d.ts +56 -0
- package/dist/collapsible.d.ts.map +1 -0
- package/dist/collapsible.js +37 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/component-definitions.d.ts +17 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +17 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +20 -3
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +24 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/context-menu.d.ts +53 -0
- package/dist/context-menu.d.ts.map +1 -0
- package/dist/context-menu.js +44 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +5 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -1
- package/dist/counter-badge-recipe.js +5 -0
- package/dist/counter-badge-recipe.js.map +1 -1
- package/dist/dataviz.d.ts +76 -0
- package/dist/dataviz.d.ts.map +1 -0
- package/dist/dataviz.js +58 -0
- package/dist/dataviz.js.map +1 -0
- package/dist/date-range.d.ts +56 -0
- package/dist/date-range.d.ts.map +1 -0
- package/dist/date-range.js +79 -0
- package/dist/date-range.js.map +1 -0
- package/dist/design-system-provider.d.ts +24 -0
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +21 -0
- package/dist/design-system-provider.js.map +1 -1
- package/dist/floating-action-button.d.ts +1 -1
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +3 -1
- package/dist/floating-action-button.js.map +1 -1
- package/dist/formatters.d.ts +37 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +69 -0
- package/dist/formatters.js.map +1 -0
- package/dist/heading.d.ts +82 -0
- package/dist/heading.d.ts.map +1 -0
- package/dist/heading.js +48 -0
- package/dist/heading.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/menubar.d.ts +133 -0
- package/dist/menubar.d.ts.map +1 -0
- package/dist/menubar.js +80 -0
- package/dist/menubar.js.map +1 -0
- package/dist/native-platform.d.ts +88 -0
- package/dist/native-platform.d.ts.map +1 -0
- package/dist/native-platform.js +70 -0
- package/dist/native-platform.js.map +1 -0
- package/dist/popover.d.ts +19 -4
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +3 -0
- package/dist/popover.js.map +1 -1
- package/dist/progress-recipe.d.ts +14 -0
- package/dist/progress-recipe.d.ts.map +1 -1
- package/dist/progress-recipe.js +14 -1
- package/dist/progress-recipe.js.map +1 -1
- package/dist/provider-button.d.ts +140 -0
- package/dist/provider-button.d.ts.map +1 -0
- package/dist/provider-button.js +83 -0
- package/dist/provider-button.js.map +1 -0
- package/dist/recipes.d.ts +16 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +15 -0
- package/dist/recipes.js.map +1 -1
- package/dist/sheet.d.ts +16 -0
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +33 -0
- package/dist/sheet.js.map +1 -1
- package/dist/sidebar.d.ts +168 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +98 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/skip-nav.d.ts +67 -0
- package/dist/skip-nav.d.ts.map +1 -0
- package/dist/skip-nav.js +47 -0
- package/dist/skip-nav.js.map +1 -0
- package/dist/tags-input.d.ts +157 -0
- package/dist/tags-input.d.ts.map +1 -0
- package/dist/tags-input.js +108 -0
- package/dist/tags-input.js.map +1 -0
- package/dist/text-formats.d.ts +85 -0
- package/dist/text-formats.d.ts.map +1 -0
- package/dist/text-formats.js +43 -0
- package/dist/text-formats.js.map +1 -0
- package/dist/toggle-group.d.ts +111 -0
- package/dist/toggle-group.d.ts.map +1 -0
- package/dist/toggle-group.js +78 -0
- package/dist/toggle-group.js.map +1 -0
- package/dist/top.d.ts +113 -0
- package/dist/top.d.ts.map +1 -0
- package/dist/top.js +72 -0
- package/dist/top.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/agreement.md +33 -0
- package/docs/anchor.md +59 -59
- package/docs/ant-design-coverage.md +5 -3
- package/docs/asset.md +26 -0
- package/docs/auth-screen.md +65 -0
- package/docs/bottom-info.md +15 -0
- package/docs/breadcrumb.md +15 -15
- package/docs/button-label.md +20 -0
- package/docs/calendar.md +82 -154
- package/docs/carousel.md +27 -2
- package/docs/cascader.md +17 -0
- package/docs/chart.md +33 -0
- package/docs/clipboard.md +13 -0
- package/docs/collapsible.md +19 -0
- package/docs/command-palette.md +19 -0
- package/docs/confirm-popover.md +11 -1
- package/docs/context-menu.md +23 -0
- package/docs/data-table.md +22 -0
- package/docs/date-range.md +31 -0
- package/docs/density.md +52 -0
- package/docs/expansion-roadmap.md +16 -3
- package/docs/floating-action-button.md +35 -3
- package/docs/formatters.md +18 -0
- package/docs/generated/component-maturity.md +40 -23
- package/docs/generated/renderer-evidence.json +3029 -652
- package/docs/generated/renderer-evidence.md +55 -6
- package/docs/generated/showcase-manifest.json +1156 -81
- package/docs/heading.md +25 -0
- package/docs/identity.md +5 -0
- package/docs/layout.md +1 -1
- package/docs/library-gap-analysis.md +29 -9
- package/docs/list-row.md +14 -0
- package/docs/mentions.md +18 -0
- package/docs/menubar.md +22 -0
- package/docs/native-platform.md +36 -0
- package/docs/overlay-stack.md +24 -0
- package/docs/pagination.md +16 -8
- package/docs/popover.md +42 -13
- package/docs/product-audit-2026-09-15.md +88 -0
- package/docs/progress.md +16 -0
- package/docs/provider-button.md +33 -0
- package/docs/rating.md +19 -0
- package/docs/react-native-completion.md +347 -0
- package/docs/screen-chrome.md +52 -0
- package/docs/side-panel.md +23 -2
- package/docs/sidebar.md +30 -0
- package/docs/skip-nav.md +20 -0
- package/docs/splitter.md +22 -2
- package/docs/stable-promotion.md +51 -0
- package/docs/tags-input.md +25 -0
- package/docs/text-formats.md +15 -0
- package/docs/theming.md +84 -0
- package/docs/time-picker.md +22 -1
- package/docs/toast.md +18 -0
- package/docs/toggle-group.md +21 -0
- package/docs/top.md +32 -0
- package/docs/tour.md +23 -2
- package/docs/transfer-list.md +20 -0
- package/docs/tree-select.md +18 -0
- package/docs/tree.md +21 -0
- package/package.json +126 -6
package/docs/heading.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Heading contract
|
|
2
|
+
|
|
3
|
+
**문제.** 랜딩 히어로, 결과 화면의 큰 숫자, 카드 제목처럼 **문서 제목 단계를 실제로
|
|
4
|
+
그려야 하는 자리**.
|
|
5
|
+
|
|
6
|
+
**이미 있던 것을 꺼냈을 뿐이다.** `foundations`의 `heading`에는 level1 40px부터
|
|
7
|
+
level5 18px까지 다섯 단계가 있었지만 어떤 renderer도 노출하지 않았다 — `Text`는
|
|
8
|
+
`TextVariant`(최대 24px)만 받는다. 그래서 큰 제목이 필요한 화면은 제품 CSS로 폰트
|
|
9
|
+
크기를 직접 썼다. 이 계약은 **새 크기를 만들지 않는다.**
|
|
10
|
+
|
|
11
|
+
**Top·Section과 겹치지 않는다.**
|
|
12
|
+
|
|
13
|
+
| | 아는 것 | 갖는 것 |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `Top` | 화면의 첫 자리 | 자기 여백·eyebrow·보조 문장·보조 행동 |
|
|
16
|
+
| `Section` | 본문 중간의 묶음 | 헤더 행·설명·본문 슬롯 |
|
|
17
|
+
| `Heading` | 아무것도 | 글자 하나 |
|
|
18
|
+
|
|
19
|
+
그래서 Heading은 주변 여백을 소유하지 않는다. 담는 블록이 정한다.
|
|
20
|
+
|
|
21
|
+
**두 축은 일부러 어긋날 수 있다.** `level`은 시각적 크기, `semanticLevel`은 문서
|
|
22
|
+
구조다. 크게 보이는 카드 제목이 구조상 `h4`인 경우가 실제로 있고, 그때 크기를 줄이거나
|
|
23
|
+
구조를 왜곡하는 대신 둘을 따로 적는다. 생략하면 `level`의 숫자를 따른다.
|
|
24
|
+
|
|
25
|
+
**Native.** 접근성 role은 `header` 하나뿐이라 문서 단계는 `aria-level`로 함께 싣는다.
|
package/docs/identity.md
CHANGED
|
@@ -110,6 +110,11 @@ HJM은 장식으로 브랜드를 증명하지 않습니다. 정보와 행동의
|
|
|
110
110
|
|
|
111
111
|
## 참고 원칙
|
|
112
112
|
|
|
113
|
+
2026-09-16 사용자 요청으로 **토스 UI를 최우선 시각 모티브**로 삼습니다. ListRow 중심
|
|
114
|
+
정보 위계, 절제된 surface, 짧고 분명한 주 행동, 본문과 이어지는 BottomCTA를 HJM의
|
|
115
|
+
토큰으로 구현합니다. React/RN 완성 범위와 실제 비교·검증은
|
|
116
|
+
[컴포넌트 완성 작업](react-native-completion.md)에 기록합니다. Flutter는 이 작업에서 제외합니다.
|
|
117
|
+
|
|
113
118
|
다른 시스템에서는 외형이나 코드를 복사하지 않고 다음을 학습합니다.
|
|
114
119
|
|
|
115
120
|
- [Ant Design](https://ant.design/docs/spec/values/): 설계 가치, 토큰 계층, 컴포넌트 범위
|
package/docs/layout.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
`relationship: "direct"`).
|
|
7
7
|
|
|
8
8
|
**먼저 뺀 것 — 이미 다른 컴포넌트가 소유한다.** 헤더 크롬은 이미
|
|
9
|
-
`TopBar`(
|
|
9
|
+
`TopBar`(adaptive, beta), 푸터 내비게이션은 이미 `BottomNavigation`(adaptive,
|
|
10
10
|
beta)이다. `Layout`이 그 콘텐츠나 상태를 다시 계약하면 두 곳이 같은 것을
|
|
11
11
|
소유하게 된다 — DataTable이 Pagination/LoadMore를 소유하지 않고 합성하기로 한
|
|
12
12
|
것과 같은 실수를 피한다. `Layout`은 **header/footer가 있다는 사실**만 알고
|
|
@@ -49,15 +49,35 @@ control의 `accessibilityLabel`/`accessibilityHint`가 canonical 번역이다.
|
|
|
49
49
|
|
|
50
50
|
## 이번에 채택하지 않음
|
|
51
51
|
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `
|
|
60
|
-
|
|
52
|
+
> 2026-09-18 갱신: 아래 목록의 전제가 커버리지 감사로 바뀐 항목이 여럿이다. 바뀐 줄은
|
|
53
|
+
> 그대로 두고 **무엇이 뒤집혔는지**와 그 근거를 함께 적는다 — 판정만 지우면 다음 사람이
|
|
54
|
+
> 같은 질문을 다시 한다.
|
|
55
|
+
|
|
56
|
+
- `Kbd`, `Code`, `Blockquote`: ~~제품·문서 콘텐츠 표현이며 HJM의 상호작용 계약이 없다.~~
|
|
57
|
+
**뒤집혔다.** 세 표현이 제품마다 다른 요소로 그려지던 것이 문제였다. 상호작용이 아니라
|
|
58
|
+
**의미 있는 요소를 고르는 판정**을 계약으로 두고 `TextFormat`으로 구현했다.
|
|
59
|
+
- `ScrollArea`: **유효하다.** Web custom scrollbar와 Native `ScrollView`는 같은 public
|
|
60
|
+
의미가 아니고, 기본 host scrolling을 감싸는 것만으로는 결함이 줄지 않는다. 2026-09-18
|
|
61
|
+
감사에서도 제품 두 곳 이상이 같은 문제를 겪은 사례가 없어 판정을 유지한다.
|
|
62
|
+
- `Toolbar`: **유효하다.** desktop keyboard model과 실제 제품 vertical slice가 먼저 필요하다.
|
|
63
|
+
- `Menubar`, `ContextMenu`: ~~desktop keyboard model과 vertical slice가 먼저 필요하다.~~
|
|
64
|
+
**뒤집혔다.** 두 컴포넌트의 값은 "Menu 여러 개로 대체되지 않는 키보드 단위"와 "트리거가
|
|
65
|
+
없어도 키보드로 열 수 있어야 한다"는 판정이고, 그것은 제품 slice를 기다릴 필요가 없었다.
|
|
66
|
+
각각 [menubar.md](./menubar.md) · [context-menu.md](./context-menu.md).
|
|
67
|
+
- `Heading`: ~~`Section`·`Text`가 이미 책임을 나눠 가진다.~~ **뒤집혔다.** `foundations`의
|
|
68
|
+
heading 스케일 다섯 단계를 **어떤 renderer도 노출하지 않고** 있었다. 새 스케일을 만든
|
|
69
|
+
것이 아니라 있던 것을 꺼냈다.
|
|
70
|
+
- `ProgressCircle`: ~~`Progress`의 presentation axis인지 먼저 검증해야 한다.~~ **검증했고,
|
|
71
|
+
맞았다.** 새 컴포넌트가 아니라 `Progress`의 `shape` 축으로 들어갔다.
|
|
72
|
+
- `Popover`, `DataTable`, `SidePanel`, `CommandPalette`: **닫혔다.** 넷 다 renderer가 들어와
|
|
73
|
+
planned → beta로 올라갔고, 예고한 대로 새 catalog 항목은 만들지 않았다.
|
|
74
|
+
- `ListHeader`(TDS): **채택하지 않는다.** 제목·설명·우측 행동으로 이루어진 목록 머리는
|
|
75
|
+
`Section`이 이미 갖는 구조다. 다른 점은 "목록 바로 위"라는 **위치**뿐인데, 위치는 계약이
|
|
76
|
+
아니라 배치다. 목록 전용 변형을 따로 두면 같은 제목이 화면 위치에 따라 다른 컴포넌트가
|
|
77
|
+
된다. `Section` + `List` 조합으로 충분하고, 그 조합이 부족하다는 실측이 나오면 그때
|
|
78
|
+
`Section`의 축으로 검토한다.
|
|
79
|
+
- `Chart`: **토큰만 채택한다.** 렌더러는 만들지 않고 계열 팔레트·축·격자·범례 토큰만
|
|
80
|
+
고정한다. 근거는 [chart.md](./chart.md).
|
|
61
81
|
|
|
62
82
|
## 후속 검토
|
|
63
83
|
|
package/docs/list-row.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ListRow contract
|
|
2
|
+
|
|
3
|
+
## loading rows (2026-09-18)
|
|
4
|
+
|
|
5
|
+
`loading`은 실제 행과 **같은 높이와 슬롯 기하**를 잡는 자리표시 행이다.
|
|
6
|
+
|
|
7
|
+
제품이 목록 옆에 따로 만든 skeleton(Diairy `LoadingSkeleton`, BurnTok `FeedCardSkeleton`)은
|
|
8
|
+
행의 실제 높이를 모른다. 그래서 내용이 도착하면 목록이 튄다. 자리표시가 같은 컴포넌트
|
|
9
|
+
안에 있어야 title/description의 **line box**를 그대로 예약할 수 있다.
|
|
10
|
+
|
|
11
|
+
- 넘기는 슬롯의 *존재*가 모양을 정한다. 문구 자체는 무시되므로 실제 행과 같은 슬롯을
|
|
12
|
+
주면 된다.
|
|
13
|
+
- 로딩 행은 상호작용하지 않는다 — 아직 활성화할 것이 없다.
|
|
14
|
+
- `role="status"` + `aria-busy`로 알리고, 문구(`loadingLabel`)는 제품이 현지화한다.
|
package/docs/mentions.md
CHANGED
|
@@ -80,3 +80,21 @@ popover가 이미 겪는 것과 같은 종류의 렌더러 문제다.
|
|
|
80
80
|
## 검증 화면
|
|
81
81
|
|
|
82
82
|
아직 없음. `planned → beta` 승격은 실제 제품 vertical slice 이후 리드가 진행한다.
|
|
83
|
+
|
|
84
|
+
## Web renderer (2026-09-18)
|
|
85
|
+
|
|
86
|
+
`@hjmds/react/mentions`의 `Mentions`가 이 모듈을 실행한다. catalog는 Web `beta`,
|
|
87
|
+
Native `planned`다.
|
|
88
|
+
|
|
89
|
+
- **새 목록 계약을 만들지 않았다.** 팝업은 Combobox의 listbox 어휘(`role="listbox"`/
|
|
90
|
+
`option`, `aria-activedescendant`, 방향키·Enter·Escape)를 그대로 쓰고, 이 모듈은
|
|
91
|
+
trigger 탐색과 치환 범위만 담당한다.
|
|
92
|
+
- **caret은 입력뿐 아니라 이동에서도 다시 읽는다.** 화살표·클릭으로 캐럿만 움직여도 활성
|
|
93
|
+
trigger가 달라지므로 `keyup`/`click`에서도 `findActiveMentionTrigger`를 다시 부른다.
|
|
94
|
+
- **포인터 확정은 `mousedown`에서 막고 처리한다.** blur가 먼저 일어나면 삽입이 의존하는
|
|
95
|
+
캐럿 위치가 이미 사라진다.
|
|
96
|
+
- **필터·로딩은 제품 소유다.** 후보 목록과 빈 문구를 제품이 넘기고, renderer는 활성 match를
|
|
97
|
+
`onMentionQueryChange`로 알린다.
|
|
98
|
+
- 로컬 검증: `test/mentions.browser.test.tsx` 5개(토큰 시작 trigger만 열림·공백이 닫음·
|
|
99
|
+
단어에 붙은 trigger 무시, Enter 확정의 치환 범위와 캐럿, 방향키 순환과 Escape가 본문을
|
|
100
|
+
건드리지 않음, 포인터 확정, 빈 문구)와 `Patterns/TransferList`의 Mentions 화면.
|
package/docs/menubar.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Menubar contract
|
|
2
|
+
|
|
3
|
+
**문제.** 데스크톱 앱의 가로 메뉴 막대 — 파일·편집·보기처럼 항상 같은 자리에 있는 메뉴들.
|
|
4
|
+
|
|
5
|
+
**Menu 여러 개를 나란히 놓는 것과 다르다.** 막대는 **하나의 키보드 단위**다. 메뉴가 열린
|
|
6
|
+
상태에서 ←/→를 누르면 옆 메뉴로 **넘어간다**(닫았다 여는 것이 아니다). 그리고 한 번에
|
|
7
|
+
하나만 열린다. 독립된 Menu 세 개로는 두 성질 모두 성립하지 않는다.
|
|
8
|
+
|
|
9
|
+
**Tabs와도 다르다.** Tabs는 화면을 바꾸고 선택 상태가 남는다. Menubar는 행동을 실행하고
|
|
10
|
+
아무것도 선택되지 않은 상태로 돌아간다. 그래서 `aria-selected`가 아니라 `menuitem`이다.
|
|
11
|
+
|
|
12
|
+
**막대 전체가 tab stop 하나다.** 메뉴가 여섯 개면 Tab을 여섯 번 눌러 지나야 하는 구조는
|
|
13
|
+
막대의 목적과 반대다. roving focus로 막대 안에서만 ←/→가 움직인다.
|
|
14
|
+
|
|
15
|
+
**비활성 메뉴는 건너뛰고 순환한다.** 끝에서 멈추면 사용자가 방향을 바꿔 되돌아와야 하는데,
|
|
16
|
+
항목이 대여섯 개뿐인 가로 막대에서는 순환이 더 짧다. `resolveMenubarNavigation`이 이
|
|
17
|
+
판단을 갖는다.
|
|
18
|
+
|
|
19
|
+
**하나가 열린 뒤에는 hover가 메뉴를 바꾼다.** 데스크톱 관습이다. 아무것도 열려 있지 않을
|
|
20
|
+
때 hover는 아무것도 열지 않는다 — 지나가다 메뉴가 펼쳐지는 것은 사고다.
|
|
21
|
+
|
|
22
|
+
**Web 전용.** 휴대폰에는 항상 떠 있는 메뉴 막대가 없고, 네이티브 앱 바는 OS의 것이다.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Native platform contract (keyboard · haptics)
|
|
2
|
+
|
|
3
|
+
React Native 화면이 매번 다시 풀던 두 가지를 계약으로 올렸다. 컴포넌트가 아니라 어휘와
|
|
4
|
+
판정이다.
|
|
5
|
+
|
|
6
|
+
## 키보드 회피
|
|
7
|
+
|
|
8
|
+
폼이 있는 모든 RN 화면이 `KeyboardAvoidingView`의 `behavior`를 각자 고르고, BottomCTA가
|
|
9
|
+
키보드에 가려지는 것을 각자 발견했다. **정답이 플랫폼별로 고정돼 있는데** 그 지식이
|
|
10
|
+
제품마다 흩어져 있었다.
|
|
11
|
+
|
|
12
|
+
- iOS는 키보드가 화면 위로 떠오르므로 `padding`, Android는 창이 줄어드는 `adjustResize`가
|
|
13
|
+
기본이라 `height`. `resolveKeyboardAvoidanceBehavior(platform)`이 그 표다.
|
|
14
|
+
- **safe area는 한 번만 센다.** 키보드가 떠 있으면 홈 인디케이터 여백은 키보드가 가리므로
|
|
15
|
+
다시 더하면 두 겹이 된다 — `resolveKeyboardInset`이 그 계산을 갖는다.
|
|
16
|
+
- `@hjmds/react-native/keyboard`의 `KeyboardAvoiding`이 이 판정을 적용한다.
|
|
17
|
+
|
|
18
|
+
## 햅틱
|
|
19
|
+
|
|
20
|
+
"성공하면 울린다"는 제품 결정이지만 **어떤 세기가 어떤 의미인가**는 디자인 시스템의
|
|
21
|
+
어휘다. 없으면 한 앱 안에서 저장은 무겁고 삭제는 가벼운 식으로 뒤섞인다.
|
|
22
|
+
|
|
23
|
+
| intent | 언제 |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `selection` | 선택이 바뀌었다 — 세그먼트, 토글, 슬라이더 눈금 |
|
|
26
|
+
| `success` | 되돌릴 수 있는 행동이 끝났다 — 저장, 담기 |
|
|
27
|
+
| `warning` | 사용자가 고쳐야 한다 — 검증 실패 |
|
|
28
|
+
| `error` | 되돌릴 수 없는 일 — 삭제 확정, 결제 실패 |
|
|
29
|
+
|
|
30
|
+
**울리지 않아야 할 때가 계약의 절반이다.** 사용자가 시작하지 않은 변화(서버 푸시로 목록이
|
|
31
|
+
바뀌는 것)에는 울리지 않는다 — 빼먹으면 주머니 속 기기가 이유 없이 떨린다.
|
|
32
|
+
**Reduce Motion은 진동을 끄지 않는다** — 화면 움직임 설정과 촉각 설정은 다르고, 모션을 끈
|
|
33
|
+
사용자에게는 진동이 유일한 확인 신호일 수 있다.
|
|
34
|
+
|
|
35
|
+
**네이티브 모듈은 들이지 않는다.** 실제 진동은 제품이 자기 햅틱 라이브러리로 실행하고,
|
|
36
|
+
HJM은 "울릴지"와 "무슨 뜻인지"만 정한다.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 명령형 오버레이 (useDialog · useSheet)
|
|
2
|
+
|
|
3
|
+
`useToast`가 이미 갖고 있던 모양의 나머지 절반이다. 그 옆이 비어 있어서 제품마다 "열림
|
|
4
|
+
상태 + 마운트 지점 + 닫힘 완료 신호"를 다시 배선했다 — BurnTok `AppModal`이 `onDismiss`를
|
|
5
|
+
직접 만든 이유가 그것이다.
|
|
6
|
+
|
|
7
|
+
**이 층이 하는 일은 소유권 이전뿐이다.** 열림 상태와 마운트 지점을 provider가 갖고,
|
|
8
|
+
호출부는 "열어 줘"와 "닫혔다"만 안다. dismiss 판정·초점·격리는 여전히 Dialog/Sheet
|
|
9
|
+
계약이 갖는다 — 여기서 다시 구현하지 않는다.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
const openDialog = useDialog();
|
|
13
|
+
const handle = openDialog({ title: "지울까요", closeLabel: "닫기", children: <p>…</p> });
|
|
14
|
+
await handle.closed; // portal이 사라지고 초점이 복구된 뒤
|
|
15
|
+
openSheet({ … }); // 그 다음에 다음 표면을 연다
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**한 번에 하나만 연다.** 겹쳐 여는 화면은 스택 규칙(어느 것이 위인가, 뒤의 것은 inert인가)이
|
|
19
|
+
필요한데 그 판정은 이미 모달 스택이 갖고 있다. 명령형 API가 우회해 두 벌을 만들면
|
|
20
|
+
"닫았는데 아래 것이 안 살아난다"가 생긴다. 후속 오버레이는 `closed` 뒤에 여는 것이
|
|
21
|
+
계약된 순서다.
|
|
22
|
+
|
|
23
|
+
**타이머가 사라진다.** `closed`는 Dialog/Sheet의 `onDismissComplete`에서 resolve되므로
|
|
24
|
+
제품이 0ms 타이머로 정리 시점을 추측할 필요가 없다.
|
package/docs/pagination.md
CHANGED
|
@@ -104,9 +104,7 @@ Steps와 같은 이유다: 순서를 나타내는 문장의 어순과 조사는
|
|
|
104
104
|
`paginationRecipe.item`은 현재 페이지에 `action.brand` 채움을 쓰지 않고
|
|
105
105
|
`border.focus`/`content.brand` 외곽선만 쓴다 — 페이지 번호는 위치 표시이지
|
|
106
106
|
버튼 커맨드가 아니다.
|
|
107
|
-
-
|
|
108
|
-
`chevronStart`/`chevronEnd`(RTL에서 자동 mirror)를 재사용한다. 생략 표시의
|
|
109
|
-
장식 마크도 기존 `more` 아이콘을 재사용한다.
|
|
107
|
+
- 이전/다음은 장식 문자 `‹`/`›`를 쓰고 RTL에서 미러링한다. 생략 표시는 장식 `…`다.
|
|
110
108
|
|
|
111
109
|
## 플랫폼 번역
|
|
112
110
|
|
|
@@ -114,7 +112,8 @@ Steps와 같은 이유다: 순서를 나타내는 문장의 어순과 조사는
|
|
|
114
112
|
현재 페이지에만 달고, 시각 숫자와 함께 제품이 조립한 `accessibleName`을
|
|
115
113
|
accessible name으로 쓴다. 생략 표시는 `aria-hidden`이며 tabbable하지 않다.
|
|
116
114
|
이전/다음 버튼은 `PaginationLabels`의 고정 현지화 문구를 쓰고, 경계에서는
|
|
117
|
-
`
|
|
115
|
+
`aria-disabled`와 opacity로 표시하고 activation을 차단한다. hard `disabled`로 바꾸면
|
|
116
|
+
마지막 페이지에 도착한 순간 누르던 버튼의 초점을 잃을 수 있어 tab stop은 유지한다.
|
|
118
117
|
- Native: `platform: web`이므로 이 계약은 Native 렌더러를 갖지 않는다 —
|
|
119
118
|
긴 목록의 Native 대응은 `LoadMore`다(위 경계 참고).
|
|
120
119
|
- Reduce Motion: 페이지 전환은 이동 애니메이션 없이 콘텐츠만 교체한다 —
|
|
@@ -132,8 +131,17 @@ Steps와 같은 이유다: 순서를 나타내는 문장의 어순과 조사는
|
|
|
132
131
|
| `disabled`(컨트롤 전체) | **배제** — 브리프가 요구한 필수 계약을 넘는 축이라 지금은 열지 않는다. 필요해지면 availability 축에서 `enabled`/`disabled`만 추가한다 |
|
|
133
132
|
| Native 대응 | **배제** — `LoadMore`가 이미 같은 문제의 Native 해法이다 |
|
|
134
133
|
|
|
135
|
-
## 검증
|
|
134
|
+
## 공개 경로와 검증
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
`import { Pagination } from "@hjmds/react/pagination"`로 가져옵니다. root와 기존 navigation
|
|
137
|
+
경로도 유지합니다. `label`, `descriptor`, `labels`, `composeAccessibleName`, `onPageChange`가
|
|
138
|
+
필수입니다. 상태·데이터 요청·결과 교체와 URL 동기화는 제품이 소유합니다.
|
|
139
|
+
|
|
140
|
+
2026-09-16 사용자의 명시적 확장 요청으로 라이브러리 beta를 제공하고 제품 채택은 별도로
|
|
141
|
+
추적합니다. `Patterns/WebNavigation`에서 125개 로컬 기록의 실제 페이지별 목록과 표시 범위가
|
|
142
|
+
바뀝니다. 서버 요청이나 제품 적용을 검증한 것은 아닙니다.
|
|
143
|
+
|
|
144
|
+
[MUI Pagination](https://mui.com/material-ui/react-pagination/)의 명시적 페이지 탐색과
|
|
145
|
+
outlined 위치 표시를 비교했습니다. HJM은 기존 content-brand outline과 평평한 목록을 유지합니다.
|
|
146
|
+
브라우저 테스트는 현재 페이지 의미, 마지막 페이지 focus 유지·중복 요청 차단, 빈 결과,
|
|
147
|
+
320px·2배 글자·4자리 페이지·RTL을 다룹니다. 실제 제품과 보조기기 검증은 남아 있습니다.
|
package/docs/popover.md
CHANGED
|
@@ -32,13 +32,13 @@ label/textValue, selection mode)을 따르는 항목 **목록**이고 그 role/k
|
|
|
32
32
|
않는, 목록이 아닌 콘텐츠를 위한 자리다. 제품이 실제로 액션 목록을 띄우려는
|
|
33
33
|
것이라면 그것은 Popover가 아니라 Menu다.
|
|
34
34
|
|
|
35
|
-
## ConfirmPopover는
|
|
35
|
+
## ConfirmPopover는 작동하는 조합으로 제공한다
|
|
36
36
|
|
|
37
37
|
catalog에 별도 `planned` 항목으로 있는 `ConfirmPopover`(antd `Popconfirm`,
|
|
38
38
|
`relationship: "adapted"`)는 이 Popover 위의 **조합**이다 — Popover의 anchored
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
비모달 surface에 되돌릴 수 있는 행동의 확인·취소를 얹은 조합이다. 파괴적 동작은
|
|
40
|
+
AlertDialog를 사용한다. `Patterns/Popover/ReversibleConfirmation`은 기록 보관·취소가
|
|
41
|
+
작동하는 예제이며 새 독립 renderer 수에 더하지 않는다.
|
|
42
42
|
|
|
43
43
|
## 일반화한 계약
|
|
44
44
|
|
|
@@ -84,7 +84,8 @@ flip/shift, RTL 논리 방향 변환은 제품 Web renderer의 비공개 `Anchor
|
|
|
84
84
|
과 의도적으로 다르다. Popover 콘텐츠는 Menu처럼 아래로 펼쳐지는 목록형
|
|
85
85
|
레이아웃을 담는 경우가 많아 Menu의 시각적 관성(아래로 열림)에 더 가깝다.
|
|
86
86
|
- `accessibilityLabel`은 선택 사항이다. 대부분의 Popover 콘텐츠는 자체 heading을
|
|
87
|
-
가지므로
|
|
87
|
+
가지므로 renderer가 `aria-labelledby`로 제목을 연결한다. heading 존재만으로 dialog가
|
|
88
|
+
자동으로 이름을 얻지는 않는다. heading이 없는 콘텐츠만
|
|
88
89
|
명시적으로 공급한다 — 기본값을 발명하지 않는다(Tooltip이 `content`를 필수로
|
|
89
90
|
요구하는 것과 반대로, Popover는 콘텐츠 자체를 타입으로 갖지 않으므로 대신
|
|
90
91
|
이 escape hatch만 둔다).
|
|
@@ -96,13 +97,14 @@ flip/shift, RTL 논리 방향 변환은 제품 Web renderer의 비공개 `Anchor
|
|
|
96
97
|
- Web: surface는 `role="dialog"`(비모달, `aria-modal` 없음), trigger는
|
|
97
98
|
`aria-haspopup="dialog"`와 `aria-expanded`를 합성한다. 열릴 때 초기 focus는
|
|
98
99
|
콘텐츠의 첫 focusable 요소로 이동하고(없으면 콘텐츠 root, `tabIndex={-1}`),
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
Escape·명시적 닫기 후 초점은 trigger로 복귀한다. outside pointer·Tab 이동은 새로 옮긴
|
|
101
|
+
초점을 유지한다. 기존 문서는 모든 종료 후 복귀를 요구했지만, 실제 Web 구현에서
|
|
102
|
+
다음 입력으로 가려는 행동을 끊는 문제가 있어 2026-09-16 수정했다.
|
|
101
103
|
- Native: 이 컴포넌트는 `platform: web`이다 — `docs/expansion-roadmap.md`
|
|
102
104
|
Batch 3에 `web`으로만 분류되어 있고, Native adaptive 대응(예: bottom sheet로
|
|
103
105
|
펼치는 대안)이 필요해지면 그때 별도 적응 계약을 연다.
|
|
104
106
|
- Reduce Motion: Tooltip과 같은 enter/exit preset(`motionPreset.enter/exit`)을
|
|
105
|
-
|
|
107
|
+
사용한다. 진입/종료는 이동 없는 opacity이며 reduced motion의 종료는 즉시 제거한다.
|
|
106
108
|
|
|
107
109
|
## 공개한 축 / 배제한 축
|
|
108
110
|
|
|
@@ -116,9 +118,36 @@ flip/shift, RTL 논리 방향 변환은 제품 Web renderer의 비공개 `Anchor
|
|
|
116
118
|
| portal/flip/shift 공개 API | **배제** — Tooltip의 `AnchoredOverlay` 경계를 그대로 상속 |
|
|
117
119
|
| content 데이터 모델 | **배제** — 런타임 의존성 금지 원칙상 React 콘텐츠 타입을 이 패키지가 가질 수 없다. 콘텐츠 자체는 항상 제품/렌더러 소유다 |
|
|
118
120
|
|
|
119
|
-
##
|
|
121
|
+
## 공개 API와 예제
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
`import { Popover } from "@hjmds/react/popover"`로 가져온다. 필수 props는 `trigger`,
|
|
124
|
+
`title`, `closeLabel`이다. trigger는 ref와 button props를 DOM까지 전달하는 단일 버튼이다.
|
|
125
|
+
children은 React 콘텐츠 또는 `({ close }) => ...` 함수이며 close는 `close-action`을 요청한다.
|
|
126
|
+
`open/defaultOpen/onOpenChange`, `descriptor`, `dismissPolicy`, `initialFocusRef`,
|
|
127
|
+
`portalContainer`, `description`을 제공한다. ref는 content div를 가리킨다.
|
|
128
|
+
|
|
129
|
+
공개 위치 설정은 기존 descriptor의 placement/align이다. 내부 `useAnchoredPopup`이 portal·
|
|
130
|
+
flip·shift를 담당하고, 옆 공간이 부족한 넓은 콘텐츠는 block 축으로 옮긴다. 크기는 recipe의
|
|
131
|
+
240–360px을 기준으로 viewport에 제한하고 긴 콘텐츠는 내부 스크롤한다. 내부 API는 공개하지 않는다.
|
|
132
|
+
|
|
133
|
+
초기 focus는 지정된 ref, 본문의 첫 tabbable control, content root 순서다. 닫기 버튼을
|
|
134
|
+
무조건 첫 초점으로 고르지 않는다. Tab 경계는 portal의 body 끝 위치 대신 트리거 다음
|
|
135
|
+
입력으로 이어진다. 현재 초점이 다른 제어 요소로 이동했다면 종료 효과가 다시 뺏지 않는다.
|
|
136
|
+
닫히는 표면은 즉시 inert/aria-hidden이 되고 exit 후 제거된다. nested Popover의 portal도 함께
|
|
137
|
+
닫히며, 내부 Menu/Select portal은 바깥 클릭·초점으로 오인하지 않는다.
|
|
138
|
+
|
|
139
|
+
Dialog 안의 Popover는 첫 Escape를 소유한다. 내부 Menu가 Escape를 처리했다면 부모는
|
|
140
|
+
그 이벤트를 다시 처리하지 않는다. 모달의 초점 목록은 hidden/inert/disabled 자손을 제외한다.
|
|
141
|
+
|
|
142
|
+
2026-09-16 사용자의 확장 요청으로 라이브러리 beta를 제공한다. 제품 채택은 별도다.
|
|
143
|
+
`Patterns/Popover/Filters`는 제목·즐겨찾기 조건 적용/취소/초기화와 실제 목록 교체를 제공한다.
|
|
144
|
+
`ReversibleConfirmation`은 보관 후 취소 버튼으로 초점을 옮기며 실제로 복원한다.
|
|
145
|
+
|
|
146
|
+
[Radix Popover](https://www.radix-ui.com/primitives/docs/components/popover)의 비모달 focus·
|
|
147
|
+
종료 구분과 [React Aria Popover](https://react-aria.adobe.com/Popover)의 viewport/portal 경계를
|
|
148
|
+
비교했다. [Ant Design Popconfirm](https://ant.design/components/popconfirm/)의 확인/취소 흐름은
|
|
149
|
+
되돌릴 수 있는 제품 행동의 조합에 적용한다. API 호환을 약속하지 않는다.
|
|
150
|
+
|
|
151
|
+
브라우저 검증은 초기 focus, Escape 복귀, 바깥 클릭 유지, Tab 양방향 이탈, controlled 거절 후
|
|
152
|
+
재시도, dismiss 정책, 중첩 modal/menu, 320px·2배 글자·RTL·충돌 배치, exit 격리를 다룬다.
|
|
153
|
+
제품 채택·실제 보조기기 검증은 남아 있다.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 실제 8개 앱에서 도출한 HJM 업데이트
|
|
2
|
+
|
|
3
|
+
기준: 2026-09-15 local checkout · HJM 1.1.1 · 상태: 우선순위 등록 및 첫 수정
|
|
4
|
+
|
|
5
|
+
## 판단 근거
|
|
6
|
+
|
|
7
|
+
app-portfolio `portfolio.json`에 등록된 BurnTok, Choose Window, Portfolio Site,
|
|
8
|
+
Taground, Unairplane, Yajalal, Spint, Diairy를 조사했다. 앱별 상세 근거는 각 제품의
|
|
9
|
+
`docs/design/PRODUCT_EVOLUTION_2026-09-15.md`, 전체 연결은 메타 저장소의 같은 이름 문서에 있다.
|
|
10
|
+
아래 `apps/`로 시작하는 경로는 메타 저장소 기준이며 조사 당시 working tree의 줄 번호다.
|
|
11
|
+
제품 이름 뒤의 짧은 경로는 해당 제품, Showcase 경로는 이 모듈의 `showcase/web` 기준이다.
|
|
12
|
+
|
|
13
|
+
94개 catalog 항목 중 stable 4, beta 61, planned 29다. 기존 renderer의 존재와 제품 채택·실기기
|
|
14
|
+
증거는 다르다. 이 조사로 maturity를 올리거나 planned 전체 구현을 약속하지 않는다.
|
|
15
|
+
Yajalal과 Choose Window의 현재 제품은 Flutter이므로 JS renderer 소비자로 계산하지 않는다.
|
|
16
|
+
|
|
17
|
+
## 업데이트 목록과 수용 기준
|
|
18
|
+
|
|
19
|
+
| ID / 우선순위 | 문제·소스 근거 | 추가/개선할 계약 | 완료 기준·첫 소비 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| DS-01 / P1 | BurnTok `apps/burntok/apps/web/src/app/globals.css:301`의 Toast 임시 우회 | compact에서 copy·close를 같은 행에, optional action은 별도 행에 유지 | 320/390/480px·장문·2배 글자·RTL browser. 로컬 수정됨; npm 소비 뒤 BurnTok 우회 제거 |
|
|
22
|
+
| DS-02 / P1 | Taground `apps/taground/apps/mobile/src/features/community/message-composer.tsx:63,168–195`의 설명 복사와 같은 폴더 `room-community-card.tsx:95–100` focus DOM 조회 | Field/TextArea support/error ID와 사용자 describedby 병합, Checkbox focus ref | 두 input 고유 ID·오류 변경·Web focus·native hint 보존. 실제 Taground 우회 제거 후 채택 완료 |
|
|
23
|
+
| DS-03 / P1 | BurnTok `apps/burntok/apps/web/src/components/ui/AppModal.tsx:59`, 양쪽 `sheet-successor.ts` | dismiss 요청과 실제 exit/focus/격리 정리 완료 분리 | StrictMode·unmount·Android back·후속 overlay 정확히 한 번. 제품 0ms 타이머 복사 금지 |
|
|
24
|
+
| DS-04 / P1 | Diairy `apps/diairy/apps/mobile/src/components/StateView.tsx:6`; Spint `apps/spint/apps/mobile/src/features/cell/CellScreen.tsx` 신고 오류(제품 문서 경로 기준) | Notice/EmptyState/Skeleton/Button 조합 지침 | 초기 조회·기존 데이터+실패·빈 결과·행동별 pending/error 분리. Query/네트워크는 제품 소유 |
|
|
25
|
+
| DS-05 / P1 | Diairy `apps/diairy/apps/mobile/src/components/ProgressRing.tsx:10`; Choose Window `lib/screens/home/widgets/route/sun_exposure_bar.dart:39` | 작업 진행·분포 비율·정지·추정의 의미와 접근성 recipe | min/max/now, 값 없는 진행, 큰 글자/중복 발표. Diairy ring 의미 수정은 제품 소유 |
|
|
26
|
+
| DS-06 / P1 | Unairplane `apps/unairplane/src/data/airportCatalog.ts:55`, `src/screens/AddFlightScreen.tsx:214` | 기존 Combobox의 로컬 검색/선택 recipe | stable IATA/표시 이름 분리, offline·직접 입력·스캔값 보존; 공항 데이터는 제품 소유 |
|
|
27
|
+
| DS-07 / P2 | Unairplane `src/components/RouteMap.tsx:341`; Yajalal 통계표·AI 결과(제품 문서 근거) | Statistic/DescriptionList/Timeline 긴 값·시각/출처 조합 | 값 강제 축소/잘림 없이 읽기, 추정과 측정 구분. 새 StatsCard 만들지 않음 |
|
|
28
|
+
| DS-08 / P2 | BurnTok 두 표면 `src/features/feed/components/DiscoveryShowcase.tsx:27`/`:34` | 기존 Carousel planned 계약의 Web/RN 이동·현재 위치 renderer | 0/1/N·폭 변경·항목 삭제·keyboard·AT. 카드 내용/추천/자동재생은 공통화하지 않음 |
|
|
29
|
+
| DS-09 / P2 | Choose Window 자체 theme/empty state, Yajalal 자체 ErrorWidget·통계표 | Flutter 의미·토큰 대응표와 fixture | 플랫폼별 tap/큰 글자/상태 의미. Flutter renderer 신설·다크모드 추가는 자동 결정하지 않음 |
|
|
30
|
+
| DS-10 / P1 | Showcase `src/showcase.css`의 renderer와 겹치는 24개 class | showcase scaffolding namespace 분리 | 실제 IconButton 모양·Tabs 흐름을 browser로 검증. 220px 고정 stage 제거, section 40→24px |
|
|
31
|
+
| DS-11 / P1 | Diairy `apps/diairy/apps/web/src/app/globals.css:992`의 Notice action 우회 | action의 축소 방지와 공간 부족 시 다음 행 배치 | 320px·1배/2배 글자에서 짧은 재시도 라벨을 온전히 읽기. 로컬 수정됨; 소비 후 제품 우회 제거 |
|
|
32
|
+
| DS-12 / P1 | Taground static Web의 기존 feed는 light, 새 modal은 dark. RNW hook 서버/첫 client 값 불일치 재현 | NativeProvider의 Web hydration snapshot 일치 | 실제 renderToString→hydrateRoot, 명시 theme/상속/value/OS 변경. 로컬 수정됨; 게시·제품 소비 후 재검증 필요 |
|
|
33
|
+
|
|
34
|
+
## 간격과 버튼: 공통 숫자보다 조합의 원인을 수정
|
|
35
|
+
|
|
36
|
+
2026-09-15 사용자가 넓은 컴포넌트 간격과 어색한 버튼을 지적해 실제 화면/코드를 추가 조사했다.
|
|
37
|
+
|
|
38
|
+
- Showcase의 `.hjm-icon-button`, `.hjm-tabs`, `.hjm-toast` 등 데모 CSS가 renderer 이후 로드되어
|
|
39
|
+
크기/모서리/flow를 덮었다. 데모만 `hjm-showcase-*`로 옮겼다. 컴포넌트 recipe 수치를 줄이는
|
|
40
|
+
대안은 소비 앱까지 바꾸면서도 cascade 충돌을 남기므로 채택하지 않았다.
|
|
41
|
+
- Spint 색상/이모지 선택은 텍스트 Button의 수평 padding을 불필요하게 사용했다.
|
|
42
|
+
제품은 기존 medium IconButton과 glyph slot으로 전환하고 이름·선택 상태·44pt target을 유지한다.
|
|
43
|
+
- Diairy 작성 화면은 Web gap/padding 24와 Native 16/20이 달랐다. Web을 gap/padding16으로
|
|
44
|
+
조정하고 ghost 보조 행동의 내용 폭과 primary 전체 폭을 구분했다. 390px 편집 폭은285→301px.
|
|
45
|
+
- Portfolio Site는 제품 CSS의 hero/section/card 최소높이가 원인이었다. 제품에서 밀도를 조정하고
|
|
46
|
+
CTA 아이콘 색을 버튼 글자 색에 맞췄다. 공용 spacing scale 자체는 그대로 둔다.
|
|
47
|
+
- Flutter 제품에는 HJM CSS가 실행되지 않는다. 실제 버튼/접근성 수정과 token 대응 제안을 분리한다.
|
|
48
|
+
|
|
49
|
+
## 첫 구현·검증 연결
|
|
50
|
+
|
|
51
|
+
- Web Toast와 Notice: `packages/react/src/styles.css`,
|
|
52
|
+
`packages/react/test/toast-layout.browser.test.tsx`(Toast7 + Notice2).
|
|
53
|
+
default 증거는 canonical SSR에서 유지하고 추가 browser 시나리오는 실행 registry의
|
|
54
|
+
dark/long-copy/large-text/rtl 환경으로 직접 실행한다.
|
|
55
|
+
- Showcase 격리: `packages/react/test/showcase-style-isolation.browser.test.tsx`.
|
|
56
|
+
SSR class 확인만으로 발견되지 않은 Tabs 가로 배치와 버튼 모양을 실제 browser로 검사한다.
|
|
57
|
+
- Review: `Patterns/Toast layout`, 기존 Button/IconButton/Tabs/Notice story.
|
|
58
|
+
- Toast는 1280px 창의 420px 카드에서도 액션 행을 유지한다. 창 breakpoint만으로 판단하면
|
|
59
|
+
실제 provider의 좁은 카드에서 큰 글자 버튼이 다시 압축되므로 grid를 카드 기본 구조로 둔다.
|
|
60
|
+
- NativeProvider: `packages/react/test/native-provider-hydration.browser.test.tsx`5개와
|
|
61
|
+
`packages/react-native/test/provider-theme.test.tsx`4개. 실제 hydration과 Native 우선순위를 검증한다.
|
|
62
|
+
- Changeset: Web compact layout과 Native hydration 수정에 각각 patch 기록을 추가했다.
|
|
63
|
+
공개 API·runtime dependency 변경은 없다.
|
|
64
|
+
- generated evidence는 `contracts:sync → build → evidence:sync`로 갱신한다.
|
|
65
|
+
제품 adoption·VoiceOver/TalkBack·게시 증거를 자동 생성하지 않는다.
|
|
66
|
+
|
|
67
|
+
## 외부 비교와 책임 경계
|
|
68
|
+
|
|
69
|
+
[Carbon empty states](https://carbondesignsystem.com/patterns/empty-states-pattern/)에서 원인과 다음 행동의
|
|
70
|
+
구분, [W3C status messages](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html)에서
|
|
71
|
+
focus 이동 없는 상태 발표, [Radix Progress](https://www.radix-ui.com/primitives/docs/components/progress)에서
|
|
72
|
+
determinate/indeterminate 의미를 확인했다(2026-09-15). 외형·코드·자산·런타임은 복사하지 않는다.
|
|
73
|
+
제품이 문구·시간 포맷·권한·비동기 작업을 소유하며 HJM에는 그 결과를 표현하는 계약만 둔다.
|
|
74
|
+
|
|
75
|
+
## DS-02 / DS-03 마무리 (2026-09-18)
|
|
76
|
+
|
|
77
|
+
- **DS-02(Field/TextArea 설명·Checkbox focus).** 오류가 뜨면 지원 문구가 사라지던 것을
|
|
78
|
+
고쳤다. 이제 설명과 오류가 **함께** 보이고 `aria-describedby`도 소비자의 자체 값 →
|
|
79
|
+
설명 → 오류 순으로 병합한다. Taground가 설명을 오류 문자열에 복사해 넣은 이유가 바로
|
|
80
|
+
그 가림이었다. Checkbox는 이미 `forwardRef`가 실제 input에 닿아 있어 DOM 조회 없이
|
|
81
|
+
focus할 수 있고, 회귀 테스트로 고정했다.
|
|
82
|
+
- **DS-03(Dialog 종료 분리).** Sheet에만 있던 "요청과 정리 완료의 분리"를 Dialog에도
|
|
83
|
+
넣었다. `onDismissComplete`는 portal이 사라지고 `useModalFocus`가 초점을 되돌린 뒤
|
|
84
|
+
한 번만 울린다. StrictMode probe와 실제 unmount는 epoch로 구분한다. 제품이 0ms 타이머로
|
|
85
|
+
정리 시점을 추측하던 우회를 제거할 수 있다.
|
|
86
|
+
- 로컬 검증: `packages/react/test/field-dialog-quality.browser.test.tsx` 4개.
|
|
87
|
+
제품 저장소의 우회 제거는 게시 후 각 제품에서 따로 한다.
|
|
88
|
+
|
package/docs/progress.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Progress contract
|
|
2
|
+
|
|
3
|
+
## circular shape (2026-09-18)
|
|
4
|
+
|
|
5
|
+
같은 값을 원으로 그리는 변형을 `shape: "linear" | "circular"`로 추가했다.
|
|
6
|
+
|
|
7
|
+
**왜 새 컴포넌트가 아닌가.** 의미가 완전히 같다 — min/max/now, 값 없는 진행,
|
|
8
|
+
발표 문구, tone이 전부 공유된다. 따로 만들면 "어느 쪽이 접근성 계약을 갖는가"가 둘로
|
|
9
|
+
갈리고, 한쪽만 고쳐지는 날이 온다. Diairy가 `ProgressRing`을 직접 만든 자리이고, 그때
|
|
10
|
+
필요했던 값이 지름과 획 두께 둘이라 `circular.sizes`·`circular.strokeWidth`만 더했다.
|
|
11
|
+
|
|
12
|
+
**Web**은 conic-gradient + mask로 그리고 `<progress>` 요소는 그대로 둔다 — 값과 발표는
|
|
13
|
+
변하지 않고 칠하는 방식만 다르다. 링은 `aria-hidden`이다.
|
|
14
|
+
|
|
15
|
+
**Native**는 conic gradient가 없어 회전한 반링으로 그린다. 그림을 위해 의존성을 들이지
|
|
16
|
+
않는다 — 값은 wrapper가 발표하므로 이 도형은 장식이다.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# AuthProviderButton contract
|
|
2
|
+
|
|
3
|
+
**문제.** "Google로 계속하기", "카카오로 계속하기" 같은 소셜 로그인 버튼. BurnTok이
|
|
4
|
+
`.hjm-button.bt-provider-google` 네 줄로 HJM 버튼을 덮어 쓰던 자리다.
|
|
5
|
+
|
|
6
|
+
**왜 Button의 tone이 아닌가.** HJM의 tone은 **의미**다 — primary/secondary/danger는
|
|
7
|
+
팔레트를 따라 테마와 함께 움직인다. 제공자 버튼의 색은 의미가 아니라 **남의 브랜드
|
|
8
|
+
자산**이고, 각 제공자가 배경·글자·테두리를 가이드라인으로 규정한 뒤 심사에서 그대로
|
|
9
|
+
쓰기를 요구한다. tone을 하나 더 만들면 HJM 팔레트가 그 색을 다시 칠할 수 있게 되고,
|
|
10
|
+
그 순간 계약이 거짓말이 된다. 그래서 별도 컴포넌트로 두고, 값의 출처가 HJM이 아니라는
|
|
11
|
+
사실을 타입과 주석으로 드러낸다.
|
|
12
|
+
|
|
13
|
+
**소유 경계.**
|
|
14
|
+
|
|
15
|
+
| | 소유자 | 내용 |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| 색·로고 모양 | 제공자 | `authProviderPalettes`가 가이드라인 값을 그대로 옮긴다 |
|
|
18
|
+
| 로고 자산 | 제품 | 재배포 조건이 제공자마다 달라 HJM이 번들하지 않는다. `logo` 슬롯으로 받는다 |
|
|
19
|
+
| 문구 | 제품 | 요구 표현이 제공자·언어마다 다르다. renderer가 만들지 않는다 |
|
|
20
|
+
| 높이·radius·타깃·포커스·배치 | HJM | 네 개가 세로로 쌓이는 화면에서 줄이 맞아야 한다 |
|
|
21
|
+
|
|
22
|
+
**테마의 역할은 선택뿐이다.** `resolveAuthProviderSurface(provider, theme)`는 제공자가
|
|
23
|
+
스스로 규정한 light/dark 변형 중 하나를 고를 뿐 색을 만들지 않는다. Google과 Apple은
|
|
24
|
+
어두운 변형을 따로 규정하고, Kakao·Naver는 브랜드색 자체가 정체성이라 다크에서도 같다.
|
|
25
|
+
|
|
26
|
+
**포커스 링은 바깥에 그린다.** `#FEE500`부터 `#000000`까지 어떤 배경 위에서도 보여야
|
|
27
|
+
하므로 `focusOutlineOffset`으로 fill 밖에 둔다.
|
|
28
|
+
|
|
29
|
+
**busy는 라벨을 지우지 않는다.** 스피너만 옆에 붙고 폭도 유지한다 — 버튼이 줄어들면
|
|
30
|
+
그 아래 제공자 버튼들이 밀린다.
|
|
31
|
+
|
|
32
|
+
**값의 출처.** 각 제공자의 공개 브랜드 가이드라인(2026-09 확인). 가이드라인이 바뀌면
|
|
33
|
+
`authProviderPalettes` 한 곳만 고친다.
|
package/docs/rating.md
CHANGED
|
@@ -56,3 +56,22 @@
|
|
|
56
56
|
3. 두 형태 중 하나가 기존 값 수학이나 접근성 계약으로 표현할 수 없는 요구(예: 정수도
|
|
57
57
|
반정수도 아닌 자유 분수 표시, 별 모양이 아닌 클릭 불가 장식과 클릭 가능 입력이
|
|
58
58
|
레이아웃까지 완전히 달라야 하는 경우)를 드러내면 이 판정을 다시 연다.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## 2026-09-16: React/RN 작동 조합
|
|
62
|
+
|
|
63
|
+
명시 요청에 따라 `Patterns/Rating`에 정수/0.5점 입력과 읽기 전용 평균 표시를 추가했다.
|
|
64
|
+
입력은 기존 Slider, 현재값과 평균 표시는 Statistic이며 별 모양 variant를 새로 만든 것은 아니다.
|
|
65
|
+
|
|
66
|
+
- [Web 예제](../../../showcase/web/src/patterns/Rating.stories.tsx): `@hjmds/react/slider`,
|
|
67
|
+
`@hjmds/react/display`, `@hjmds/react/actions`의 공개 API를 조합한다.
|
|
68
|
+
- [Native 예제](../../../showcase/native/src/Rating.stories.tsx): 대응하는 Native 공개 API와
|
|
69
|
+
올리기/내리기 접근성 라벨을 사용한다.
|
|
70
|
+
- 값은 1–5, `step`은 1 또는 0.5다. `getValueText`는 만점과 현재 점수를 함께 읽는다.
|
|
71
|
+
값 변경은 입력 상태이고, 저장은 별도 행동이다. 수정하면 이전 저장 완료 표시를 해제한다.
|
|
72
|
+
- 읽기 전용 4.3/5 예제는 Statistic만 제공하며 조작 가능한 Slider를 만들지 않는다.
|
|
73
|
+
- 브라우저에서 키보드로 3 → 3.5 변경, 값 표시·접근성 값·저장 결과를 확인했다.
|
|
74
|
+
Native는 타입·Showcase 검사 범위이며 실제 기기 검증은 별도다.
|
|
75
|
+
|
|
76
|
+
독립 Rating API를 추가하지 않았으므로 catalog의 composed 행과 renderer 수는 유지한다.
|
|
77
|
+
이 요청의 합성 예제 범위는 완료됐지만 별점 icon variant 채택을 증명하는 것은 아니다.
|