@hjmds/design-contracts 0.8.2
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/README.md +208 -0
- package/dist/alert-dialog.d.ts +102 -0
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +136 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/base-recipes.d.ts +184 -0
- package/dist/base-recipes.d.ts.map +1 -0
- package/dist/base-recipes.js +129 -0
- package/dist/base-recipes.js.map +1 -0
- package/dist/behaviors.d.ts +1254 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/behaviors.js +972 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/bottom-navigation-defaults.d.ts +13 -0
- package/dist/bottom-navigation-defaults.d.ts.map +1 -0
- package/dist/bottom-navigation-defaults.js +10 -0
- package/dist/bottom-navigation-defaults.js.map +1 -0
- package/dist/bottom-navigation.d.ts +114 -0
- package/dist/bottom-navigation.d.ts.map +1 -0
- package/dist/bottom-navigation.js +223 -0
- package/dist/bottom-navigation.js.map +1 -0
- package/dist/breadcrumb.d.ts +97 -0
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +99 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/calendar.d.ts +285 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +297 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +39 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +50 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +180 -0
- package/dist/carousel.d.ts.map +1 -0
- package/dist/carousel.js +172 -0
- package/dist/carousel.js.map +1 -0
- package/dist/catalog.d.ts +7158 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +220 -0
- package/dist/catalog.js.map +1 -0
- package/dist/collection.d.ts +123 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +211 -0
- package/dist/collection.js.map +1 -0
- package/dist/color-references.d.ts +46 -0
- package/dist/color-references.d.ts.map +1 -0
- package/dist/color-references.js +39 -0
- package/dist/color-references.js.map +1 -0
- package/dist/colors.d.ts +56 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +90 -0
- package/dist/colors.js.map +1 -0
- package/dist/command-palette.d.ts +240 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +97 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/component-contracts.d.ts +137 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-contracts.js +53 -0
- package/dist/component-contracts.js.map +1 -0
- package/dist/component-definitions.d.ts +135 -0
- package/dist/component-definitions.d.ts.map +1 -0
- package/dist/component-definitions.js +142 -0
- package/dist/component-definitions.js.map +1 -0
- package/dist/component-recipes.d.ts +3583 -0
- package/dist/component-recipes.d.ts.map +1 -0
- package/dist/component-recipes.js +1503 -0
- package/dist/component-recipes.js.map +1 -0
- package/dist/component-references.d.ts +416 -0
- package/dist/component-references.d.ts.map +1 -0
- package/dist/component-references.js +140 -0
- package/dist/component-references.js.map +1 -0
- package/dist/content-state.d.ts +104 -0
- package/dist/content-state.d.ts.map +1 -0
- package/dist/content-state.js +116 -0
- package/dist/content-state.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +80 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -0
- package/dist/counter-badge-recipe.js +44 -0
- package/dist/counter-badge-recipe.js.map +1 -0
- package/dist/counter-badge.d.ts +12 -0
- package/dist/counter-badge.d.ts.map +1 -0
- package/dist/counter-badge.js +21 -0
- package/dist/counter-badge.js.map +1 -0
- package/dist/data-table.d.ts +203 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +182 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +268 -0
- package/dist/date-picker.d.ts.map +1 -0
- package/dist/date-picker.js +168 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/description-list.d.ts +68 -0
- package/dist/description-list.d.ts.map +1 -0
- package/dist/description-list.js +85 -0
- package/dist/description-list.js.map +1 -0
- package/dist/design-system-provider.d.ts +80 -0
- package/dist/design-system-provider.d.ts.map +1 -0
- package/dist/design-system-provider.js +147 -0
- package/dist/design-system-provider.js.map +1 -0
- package/dist/evidence.d.ts +49 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +133 -0
- package/dist/evidence.js.map +1 -0
- package/dist/file-picker.d.ts +183 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +224 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/floating-action-button.d.ts +143 -0
- package/dist/floating-action-button.d.ts.map +1 -0
- package/dist/floating-action-button.js +149 -0
- package/dist/floating-action-button.js.map +1 -0
- package/dist/form.d.ts +143 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +206 -0
- package/dist/form.js.map +1 -0
- package/dist/foundations.d.ts +300 -0
- package/dist/foundations.d.ts.map +1 -0
- package/dist/foundations.js +238 -0
- package/dist/foundations.js.map +1 -0
- package/dist/grid.d.ts +75 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +133 -0
- package/dist/grid.js.map +1 -0
- package/dist/icon-button-recipe.d.ts +99 -0
- package/dist/icon-button-recipe.d.ts.map +1 -0
- package/dist/icon-button-recipe.js +53 -0
- package/dist/icon-button-recipe.js.map +1 -0
- package/dist/icon.d.ts +41 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +147 -0
- package/dist/icon.js.map +1 -0
- package/dist/image.d.ts +82 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +100 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +118 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +118 -0
- package/dist/layout.js.map +1 -0
- package/dist/link.d.ts +57 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +142 -0
- package/dist/link.js.map +1 -0
- package/dist/load-more.d.ts +64 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +124 -0
- package/dist/load-more.js.map +1 -0
- package/dist/mentions.d.ts +67 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +107 -0
- package/dist/mentions.js.map +1 -0
- package/dist/number-field.d.ts +208 -0
- package/dist/number-field.d.ts.map +1 -0
- package/dist/number-field.js +247 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +152 -0
- package/dist/otp-field.d.ts.map +1 -0
- package/dist/otp-field.js +117 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/pagination.d.ts +181 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +226 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-field.d.ts +187 -0
- package/dist/password-field.d.ts.map +1 -0
- package/dist/password-field.js +120 -0
- package/dist/password-field.js.map +1 -0
- package/dist/popover.d.ts +152 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +137 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress-recipe.d.ts +43 -0
- package/dist/progress-recipe.d.ts.map +1 -0
- package/dist/progress-recipe.js +16 -0
- package/dist/progress-recipe.js.map +1 -0
- package/dist/recipes.d.ts +31 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +46 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responsive.d.ts +27 -0
- package/dist/responsive.d.ts.map +1 -0
- package/dist/responsive.js +66 -0
- package/dist/responsive.js.map +1 -0
- package/dist/result.d.ts +111 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/selection-helpers.d.ts +16 -0
- package/dist/selection-helpers.d.ts.map +1 -0
- package/dist/selection-helpers.js +52 -0
- package/dist/selection-helpers.js.map +1 -0
- package/dist/semantic-colors.d.ts +275 -0
- package/dist/semantic-colors.d.ts.map +1 -0
- package/dist/semantic-colors.js +84 -0
- package/dist/semantic-colors.js.map +1 -0
- package/dist/sheet.d.ts +51 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +69 -0
- package/dist/sheet.js.map +1 -0
- package/dist/showcase.d.ts +153 -0
- package/dist/showcase.d.ts.map +1 -0
- package/dist/showcase.js +210 -0
- package/dist/showcase.js.map +1 -0
- package/dist/side-panel.d.ts +199 -0
- package/dist/side-panel.d.ts.map +1 -0
- package/dist/side-panel.js +111 -0
- package/dist/side-panel.js.map +1 -0
- package/dist/slider.d.ts +138 -0
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +150 -0
- package/dist/slider.js.map +1 -0
- package/dist/splitter.d.ts +113 -0
- package/dist/splitter.d.ts.map +1 -0
- package/dist/splitter.js +99 -0
- package/dist/splitter.js.map +1 -0
- package/dist/statistic.d.ts +41 -0
- package/dist/statistic.d.ts.map +1 -0
- package/dist/statistic.js +76 -0
- package/dist/statistic.js.map +1 -0
- package/dist/steps.d.ts +196 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +160 -0
- package/dist/steps.js.map +1 -0
- package/dist/tag.d.ts +128 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +93 -0
- package/dist/tag.js.map +1 -0
- package/dist/timeline.d.ts +147 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +127 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toast.d.ts +164 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +529 -0
- package/dist/toast.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tooltip.d.ts +44 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +88 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tour.d.ts +218 -0
- package/dist/tour.d.ts.map +1 -0
- package/dist/tour.js +211 -0
- package/dist/tour.js.map +1 -0
- package/dist/transfer-list.d.ts +207 -0
- package/dist/transfer-list.d.ts.map +1 -0
- package/dist/transfer-list.js +193 -0
- package/dist/transfer-list.js.map +1 -0
- package/dist/tree-select.d.ts +78 -0
- package/dist/tree-select.d.ts.map +1 -0
- package/dist/tree-select.js +132 -0
- package/dist/tree-select.js.map +1 -0
- package/dist/tree.d.ts +206 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +223 -0
- package/dist/tree.js.map +1 -0
- package/dist/upload-item.d.ts +173 -0
- package/dist/upload-item.d.ts.map +1 -0
- package/dist/upload-item.js +156 -0
- package/dist/upload-item.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/affix.md +63 -0
- package/docs/anchor.md +59 -0
- package/docs/ant-design-coverage.md +119 -0
- package/docs/app-provider.md +50 -0
- package/docs/app-rn-adoption.md +227 -0
- package/docs/architecture.md +320 -0
- package/docs/authoring-brief.md +87 -0
- package/docs/border-beam.md +66 -0
- package/docs/bottom-navigation.md +127 -0
- package/docs/breadcrumb.md +82 -0
- package/docs/calendar.md +154 -0
- package/docs/carousel.md +130 -0
- package/docs/cascader.md +93 -0
- package/docs/catalog-decision-status.md +306 -0
- package/docs/color-picker.md +72 -0
- package/docs/command-palette.md +116 -0
- package/docs/confirm-popover.md +93 -0
- package/docs/consistency-audit.md +383 -0
- package/docs/consumer-release-gate.md +90 -0
- package/docs/content-state.md +156 -0
- package/docs/context-panel.md +88 -0
- package/docs/cross-platform-core-normalization.md +118 -0
- package/docs/data-table.md +79 -0
- package/docs/date-picker.md +84 -0
- package/docs/description-list.md +75 -0
- package/docs/design-system-provider.md +139 -0
- package/docs/dropdown.md +78 -0
- package/docs/expansion-roadmap.md +285 -0
- package/docs/file-picker.md +59 -0
- package/docs/floating-action-button.md +111 -0
- package/docs/form.md +134 -0
- package/docs/generated/component-maturity.md +103 -0
- package/docs/generated/renderer-evidence.json +5597 -0
- package/docs/generated/renderer-evidence.md +132 -0
- package/docs/generated/showcase-manifest.json +3732 -0
- package/docs/icon.md +22 -0
- package/docs/identity.md +121 -0
- package/docs/image.md +84 -0
- package/docs/implementation-0.5.md +85 -0
- package/docs/layout-primitives.md +107 -0
- package/docs/layout.md +83 -0
- package/docs/library-reference-decisions.md +123 -0
- package/docs/link.md +67 -0
- package/docs/load-more.md +31 -0
- package/docs/mentions.md +82 -0
- package/docs/migration-0.2.md +108 -0
- package/docs/migration-0.3.md +72 -0
- package/docs/migration-0.5.md +82 -0
- package/docs/migration-0.6.md +197 -0
- package/docs/notification.md +54 -0
- package/docs/number-field.md +82 -0
- package/docs/otp-field.md +103 -0
- package/docs/pagination.md +139 -0
- package/docs/password-field.md +98 -0
- package/docs/popover.md +124 -0
- package/docs/promotion-candidates.md +169 -0
- package/docs/qr-code.md +69 -0
- package/docs/rating.md +58 -0
- package/docs/responsive-grid.md +90 -0
- package/docs/result.md +96 -0
- package/docs/showcase.md +97 -0
- package/docs/side-panel.md +71 -0
- package/docs/slider.md +79 -0
- package/docs/splitter.md +55 -0
- package/docs/statistic.md +16 -0
- package/docs/steps.md +115 -0
- package/docs/tag.md +57 -0
- package/docs/time-picker.md +88 -0
- package/docs/timeline.md +146 -0
- package/docs/toast.md +127 -0
- package/docs/tooltip.md +58 -0
- package/docs/tour.md +136 -0
- package/docs/transfer-list.md +94 -0
- package/docs/tree-select.md +101 -0
- package/docs/tree.md +123 -0
- package/docs/upload-item.md +68 -0
- package/docs/utility.md +53 -0
- package/docs/virtual-list.md +70 -0
- package/docs/watermark.md +58 -0
- package/package.json +402 -0
package/docs/link.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Link contract
|
|
2
|
+
|
|
3
|
+
`Link`는 callback을 실행하는 Button이 아니라 사용자가 복사하거나 새 탭에서 열 수 있는
|
|
4
|
+
**목적지**입니다. HJM은 label, 목적지, semantic icon만 공유하고 Web과 Native가 각 플랫폼의
|
|
5
|
+
실제 navigation primitive로 번역합니다. BurnTok의 Web/RN 대화 destination 행에서 실제
|
|
6
|
+
navigation·접근성 계약을 검증했으므로 catalog status는 `beta`입니다.
|
|
7
|
+
|
|
8
|
+
## 목적지
|
|
9
|
+
|
|
10
|
+
- `internal`: `/profile`, `?tab=stats`, `#details`처럼 앱 router가 해석하는 목적지
|
|
11
|
+
- `external`: `https`, `http`, `mailto`, `tel` absolute URL
|
|
12
|
+
|
|
13
|
+
`href`는 항상 필수입니다. `onClick`/`onPress`로 navigation을 대신하거나 optional href를
|
|
14
|
+
Button처럼 사용하는 API는 허용하지 않습니다. 인증 확인, retry, back, replace 같은 command는
|
|
15
|
+
Button과 제품 navigation workflow가 소유합니다.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const profileLink = {
|
|
19
|
+
label: "프로필 보기",
|
|
20
|
+
destination: { kind: "internal", href: "/u/jimin" },
|
|
21
|
+
trailingIcon: { name: "chevronEnd" },
|
|
22
|
+
} satisfies LinkDescriptor;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
unavailable destination은 disabled Link로 만들지 않고 plain Text로 표시합니다. `visited`는 Web의
|
|
26
|
+
`:visited` pseudo-state이지 공통 application state가 아니며, download는 Web anchor attribute와
|
|
27
|
+
Native 파일 권한·저장·공유가 다른 별도 workflow이므로 공통 Link에 포함하지 않습니다.
|
|
28
|
+
|
|
29
|
+
## Adaptive renderer
|
|
30
|
+
|
|
31
|
+
### Web
|
|
32
|
+
|
|
33
|
+
- Next Link 또는 실제 `<a href>`를 사용합니다.
|
|
34
|
+
- Tab/Enter, modifier click, context menu, 주소 복사, 새 탭 열기를 browser에 남깁니다.
|
|
35
|
+
- SPA navigation을 사용하더라도 button으로 바꾸지 않습니다.
|
|
36
|
+
- inline variant는 색이 없어도 링크임을 알 수 있도록 항상 underline을 유지합니다.
|
|
37
|
+
|
|
38
|
+
### React Native
|
|
39
|
+
|
|
40
|
+
- internal destination은 Expo Router Link 같은 실제 route primitive와 link role을 사용합니다.
|
|
41
|
+
- external destination은 `Linking` 계열 adapter로 열고 실패를 제품에 전달합니다.
|
|
42
|
+
- Pressable callback만으로 내부·외부 목적지를 하나의 button처럼 평준화하지 않습니다.
|
|
43
|
+
|
|
44
|
+
## Icon과 접근성
|
|
45
|
+
|
|
46
|
+
label 또는 명시적 `accessibilityLabel`이 링크 이름을 소유합니다. 음성 제어에서 보이는 문구로
|
|
47
|
+
대상을 찾을 수 있도록 별도 접근성 이름도 visible label을 포함해야 합니다. leading/trailing
|
|
48
|
+
icon은 HJM semantic name만 고릅니다. decorative 처리와 size/tone/weight는 `linkRecipe`가,
|
|
49
|
+
`chevronStart`와 `chevronEnd` 같은 논리 방향은 Icon registry가 소유합니다. caller가 raw size,
|
|
50
|
+
color, stroke, fixed direction을 넘겨 이 문법을 바꾸지 못합니다. arbitrary ReactNode와 중첩
|
|
51
|
+
button/link도 열지 않습니다.
|
|
52
|
+
|
|
53
|
+
core descriptor와 destination은 허용된 key만 받으며 renderer의 `className`, `target`, `replace`
|
|
54
|
+
같은 플랫폼 prop을 섞지 않습니다. resolver는 검증된 label, canonical `{ kind, href }`, semantic
|
|
55
|
+
icon identity만 새 객체로 반환해 입력 객체의 숨은 확장 필드를 플랫폼 사이에 전달하지 않습니다.
|
|
56
|
+
|
|
57
|
+
standalone Link는 최소 44-unit target과 visible focus를 지킵니다. 문장 안 inline Link는 줄 높이를
|
|
58
|
+
강제로 키우지 않되 underline과 native focus semantics를 유지합니다.
|
|
59
|
+
|
|
60
|
+
## 첫 제품 검증
|
|
61
|
+
|
|
62
|
+
첫 paired slice는 BurnTok 메시지 목록의 대화 destination입니다. 고정된 conversation-row
|
|
63
|
+
adapter가 기존 avatar·preview·time·unread 위계를 소유하고, root만 Web의 실제 Next anchor와
|
|
64
|
+
Native의 Expo Router `Link asChild`로 분기했습니다. Web modifier/context navigation,
|
|
65
|
+
Native link role·activation, 한 번만 읽히는 접근성 이름을 검증했으며 callback·임의 children·
|
|
66
|
+
style escape는 열지 않았습니다. rich card 전체를 base Link의 arbitrary children으로 열지 않고
|
|
67
|
+
이후 linked-row/card adapter에서 같은 destination contract를 조합합니다.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# LoadMore contract
|
|
2
|
+
|
|
3
|
+
`LoadMore`는 목록 데이터나 cursor를 소유하지 않습니다. 이미 렌더된 항목을 유지한 채 다음
|
|
4
|
+
페이지 요청의 footer 상태와 중복 요청 방지만 공통화합니다.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
ready(requestKey) ─ request ─→ loading(requestKey)
|
|
8
|
+
↑ ├─ success + next key → ready
|
|
9
|
+
├──── retry ← error ←─────┤
|
|
10
|
+
└──────────── complete ←──┘
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `requestKey`는 cursor나 offset을 제품 adapter가 stable string으로 만든 값입니다.
|
|
14
|
+
- `labels`는 load more/loading/retry/complete 네 상태의 현지화된 visible copy입니다. renderer가
|
|
15
|
+
자체 문구나 영어 fallback을 만들지 않습니다.
|
|
16
|
+
- `createLoadMoreController`는 한 controller에서 요청 하나만 허용합니다. 같은 sentinel의 반복
|
|
17
|
+
노출이나 RN `onEndReached` 중복 호출이 query를 두 번 실행하지 못합니다.
|
|
18
|
+
- `automatic` mode는 viewport sentinel을 사용할 수 있지만, keyboard와 screen reader 사용자를
|
|
19
|
+
위한 manual fallback button을 함께 렌더링합니다. `manual` mode는 viewport 요청을 무시합니다.
|
|
20
|
+
- `ready`는 manual/viewport, `error`는 retry reason만 허용합니다. `loading`과 `complete`는 모든
|
|
21
|
+
요청을 차단합니다.
|
|
22
|
+
- `onLoadMore`는 실제 query가 끝날 때 settle되는 Promise를 반드시 반환합니다. detached 요청이나
|
|
23
|
+
`void fetchNextPage()`는 gate를 조기에 풀어 같은 cursor가 중복 실행될 수 있어 거부합니다.
|
|
24
|
+
query promise가 성공하거나 실패하면 gate가 풀립니다. 오류 copy와 retry 상태는 제품 query가
|
|
25
|
+
소유하며, 기존 collection item을 숨기지 않습니다.
|
|
26
|
+
- loading copy는 status로, 오류는 alert로 한 번만 발표합니다. retry/manual target은 44-unit
|
|
27
|
+
이상이고 focus indicator를 유지합니다.
|
|
28
|
+
|
|
29
|
+
Web `IntersectionObserver`와 RN `onEndReached`는 감지 방식만 다르며 같은 controller와 state를
|
|
30
|
+
사용합니다. 페이지 번호가 필요한 탐색은 `Pagination`, 사용자 의도 없이 계속 이어지는 긴
|
|
31
|
+
목록은 `LoadMore`로 분리합니다.
|
package/docs/mentions.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Mentions contract
|
|
2
|
+
|
|
3
|
+
## 문제
|
|
4
|
+
|
|
5
|
+
텍스트 입력 중 트리거 문자(`@`, `#`)를 만나면 후보 목록을 띄우고, 하나를 고르면 트리거부터
|
|
6
|
+
현재 커서까지를 선택한 후보로 바꾸며 뒤에 공백 하나를 남긴다. Ant Design `Mentions`와
|
|
7
|
+
`direct` crosswalk를 따른다.
|
|
8
|
+
|
|
9
|
+
## 이게 새 컴포넌트인가
|
|
10
|
+
|
|
11
|
+
먼저 판정할 것: Mentions는 `TextArea` + `Menu`(또는 Combobox의 collection) 조합으로
|
|
12
|
+
완결되는가, 새 계약이 필요한가.
|
|
13
|
+
|
|
14
|
+
**후보 목록 자체는 완결된다.** 후보를 필터링하고 화살표 키로 훑고 loading/empty/error를
|
|
15
|
+
알리는 문제는 `behaviorRegistry.combobox`가 이미 소유한 문제와 다르지 않다 —
|
|
16
|
+
`comboboxRecipe`의 popover/listbox anatomy, `AsyncCollectionState`, IME 조합 중 미리
|
|
17
|
+
필터링/커밋하지 않는다는 `"ime-composition-does-not-prematurely-filter-or-commit"` 시나리오
|
|
18
|
+
전부 그대로 재사용된다. 이 부분에 대해서는 새 recipe나 새 behaviorRegistry entry를 만들지
|
|
19
|
+
않는다.
|
|
20
|
+
|
|
21
|
+
**새 계약이 필요한 지점은 하나뿐이다.** 자유 텍스트 문자열 안에서 "지금 활성 트리거가
|
|
22
|
+
있는가, 그 query는 무엇인가, 커밋 시 어느 범위를 무엇으로 바꾸는가"를 결정하는 문제는
|
|
23
|
+
TextArea에도 Combobox에도 없다 — Combobox는 입력창 전체가 곧 query이지 텍스트 중간의
|
|
24
|
+
트리거를 찾지 않는다. 이 트리거 탐지·삽입 범위 계산이 `src/mentions.ts`가 담는 전부다.
|
|
25
|
+
|
|
26
|
+
## 일반화한 계약
|
|
27
|
+
|
|
28
|
+
### 트리거 탐지는 문자열과 커서 오프셋만 본다
|
|
29
|
+
|
|
30
|
+
`findActiveMentionTrigger(text, cursorPosition, triggers)`는 개별 키 입력 이벤트가 아니라
|
|
31
|
+
현재 확정된 텍스트 값과 커서 위치만 읽는다. 커서에서 왼쪽으로 스캔하다 공백을 만나면
|
|
32
|
+
즉시 포기(활성 트리거 없음)하고, 트리거 문자를 만나되 그 앞이 시작 위치나 공백이면
|
|
33
|
+
활성 매치로 확정한다.
|
|
34
|
+
|
|
35
|
+
- **`"user@example.com"`처럼 트리거 앞이 공백/시작이 아니면 열리지 않는다** — Slack,
|
|
36
|
+
Discord, GitHub 등 모든 멘션 UI가 공유하는 "트리거는 토큰의 시작에서만 유효하다"는
|
|
37
|
+
관례다. validator를 먼저 이 입력으로 시험한 이유가 이것이다: 순진한 구현은 문자열에
|
|
38
|
+
트리거 문자가 있다는 것만으로 열어버리기 쉽다.
|
|
39
|
+
- **공백을 타이핑하면 이미 열린 멘션도 닫힌다** — query가 공백을 건너 무한히 늘어나는
|
|
40
|
+
것을 막는다. 같은 관례.
|
|
41
|
+
- **트리거 바로 다음(빈 query)도 유효한 활성 매치다** — `"@"`만 입력한 순간에도 팝업이
|
|
42
|
+
열려야 기본 후보 목록을 보여줄 수 있다.
|
|
43
|
+
|
|
44
|
+
### 커서 오프셋 기반이라 한글 조합에 특별 처리가 필요 없다
|
|
45
|
+
|
|
46
|
+
이 계약은 텍스트 값이 확정된 뒤에만 동작하므로, 조합 중인 자모(예: "ㅎ")도 그 순간 문자열
|
|
47
|
+
안의 유효한 코드 포인트일 뿐이다. Combobox가 조합 중 필터링을 유예하는 것과 달리, Mentions는
|
|
48
|
+
유예할 이유가 없다 — 렌더러가 시각적 안정성을 위해 `compositionend`까지 팝업 리포지션을
|
|
49
|
+
미루는 것은 허용되지만, 이 계약이 요구하지는 않는다.
|
|
50
|
+
|
|
51
|
+
### 삽입은 항상 트리거 문자를 정확히 한 번 포함한다
|
|
52
|
+
|
|
53
|
+
`resolveMentionInsertion(text, match, cursorPosition, insertedText)`는 트리거를 호출자가
|
|
54
|
+
아니라 이 함수가 붙인다 — 잊거나 중복으로 붙이는 실수 자체를 불가능하게 만든다. 교체
|
|
55
|
+
범위는 항상 `[match.triggerStart, cursorPosition)`이고, 삽입 뒤 공백 하나를 항상 더해
|
|
56
|
+
사용자가 바로 다음 단어를 이어 칠 수 있게 한다.
|
|
57
|
+
|
|
58
|
+
### 다중 트리거는 문자와 id가 모두 유일해야 한다
|
|
59
|
+
|
|
60
|
+
`MentionTriggerConfig`는 `@`(사용자 멘션), `#`(해시태그)처럼 여러 트리거를 동시에 지원할
|
|
61
|
+
수 있게 하되(antd의 `prefix: string | string[]`가 이미 이 요구를 문서화하고 있다), 트리거
|
|
62
|
+
문자 중복과 id 중복을 모두 거부한다 — 어느 후보 소스로 갈지 제품이 `triggerId`로 분기할
|
|
63
|
+
수 있어야 하기 때문이다.
|
|
64
|
+
|
|
65
|
+
## HJM 기본값
|
|
66
|
+
|
|
67
|
+
- 팝업 anatomy·recipe: `comboboxRecipe`를 그대로 재사용. 새 recipe 없음.
|
|
68
|
+
- 팝업 behavior: `behaviorRegistry.combobox`를 그대로 재사용. 새 behaviorRegistry entry
|
|
69
|
+
없음. `mentionsBehaviorScenarios`는 트리거 탐지·삽입 슬라이스만 추가한다.
|
|
70
|
+
- 삽입 후 공백 하나: 고정 기본값, 옵션으로 빼지 않았다 — 측정된 대안 요구가 없다.
|
|
71
|
+
|
|
72
|
+
## 플랫폼 번역
|
|
73
|
+
|
|
74
|
+
Web과 Native 모두 텍스트 값 + 커서 오프셋이라는 같은 입력으로 동작하므로 이 계약 자체는
|
|
75
|
+
플랫폼 중립이다. Web `<textarea>`의 `selectionStart`, RN `TextInput`의 `onSelectionChange`가
|
|
76
|
+
각각 `cursorPosition`을 공급하고, 렌더러가 팝업을 caret 근처에 앵커링하는 방법(Web
|
|
77
|
+
absolute position 계산 vs RN 측정된 caret rect)만 플랫폼별로 다르다 — 이건 Combobox
|
|
78
|
+
popover가 이미 겪는 것과 같은 종류의 렌더러 문제다.
|
|
79
|
+
|
|
80
|
+
## 검증 화면
|
|
81
|
+
|
|
82
|
+
아직 없음. `planned → beta` 승격은 실제 제품 vertical slice 이후 리드가 진행한다.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# v0.2 migration
|
|
2
|
+
|
|
3
|
+
`v0.2`는 기존 `THEMES`, foundations, Button/Surface/Field API를 유지하는 additive
|
|
4
|
+
release입니다. 소비 앱은 태그가 발행된 뒤 아래 순서로 전환합니다.
|
|
5
|
+
|
|
6
|
+
1. 당시 package name인 `@hjm/design-system` Git dependency를 정확한 `#v0.2.0`으로 변경
|
|
7
|
+
2. lockfile이 같은 HJM commit을 가리키는지 확인
|
|
8
|
+
3. 제품 어댑터가 신규 foundation, recipe, type을 명시적으로 re-export
|
|
9
|
+
4. Web token generator가 필요한 신규 foundation을 CSS variable로 변환
|
|
10
|
+
5. 기존 앱 component renderer를 beta recipe에 연결
|
|
11
|
+
6. typecheck, contrast test, Web/RN fixture와 실제 화면을 검증
|
|
12
|
+
|
|
13
|
+
## 신규 foundation
|
|
14
|
+
|
|
15
|
+
- `easing`, `spring`, `motionPreset`와 Reduce Motion fallback
|
|
16
|
+
- `opacity`, `stateLayer`, `stroke`
|
|
17
|
+
- `layout`, `breakpoint`, `layer`
|
|
18
|
+
- `shadow.floating`, `shadow.overlay`, renderer-neutral `backdrop`
|
|
19
|
+
- `semanticColors`
|
|
20
|
+
|
|
21
|
+
## 신규 공통 계약
|
|
22
|
+
|
|
23
|
+
- semantic color reference: `themeColor`, `accentColor`, `resolveColorReference`
|
|
24
|
+
- content/action: `textRecipe`, `iconButtonRecipe`, `chipRecipe`
|
|
25
|
+
- semantic icon/destination: `IconDescriptor`, `LinkDescriptor`, `LinkDestination`
|
|
26
|
+
- field affordance: accessible field label/hint colors, `searchFieldRecipe`
|
|
27
|
+
- selection/navigation: `selectionGroupRecipe`, `selectionControlRecipe`, typed Checkbox/RadioGroup
|
|
28
|
+
selection helpers, `segmentedControlRecipe`, `switchRecipe`, `tabsRecipe`
|
|
29
|
+
- data display: `badgeRecipe`, `counterBadgeRecipe`, `avatarRecipe`, `dividerRecipe`, `listRecipe`, `listRowRecipe`, `sectionRecipe`,
|
|
30
|
+
`StatisticDescriptor`, `resolveStatisticDescriptor`
|
|
31
|
+
- feedback: `noticeRecipe`, `emptyStateRecipe`, `skeletonRecipe`, `spinnerRecipe`, `progressRecipe`
|
|
32
|
+
- adaptive overlay: `dialogRecipe`, `alertDialogRecipe`, `sheetRecipe`, `toastRecipe`,
|
|
33
|
+
`createAlertDialogSession`, `SheetOpenState`, `SheetDismissPolicy`, `canDismissSheet`,
|
|
34
|
+
`createSheetLifecycle`, `ToastDescriptor`, `createToastSession`, `createToastStore`
|
|
35
|
+
- navigation feedback: `BottomNavigationDescriptor`, `LoadMoreState`, `createLoadMoreController`
|
|
36
|
+
- Web overlay planning: `TooltipDescriptor`, `TooltipOpenState`
|
|
37
|
+
- native layout: `topBarRecipe`, `bottomCtaRecipe`
|
|
38
|
+
- scope/maturity registry: `componentCatalog`, typed `recipeRegistry`
|
|
39
|
+
- renderer acceptance: `behaviorRegistry`, state-axis types, collection descriptors and selection models
|
|
40
|
+
- future collection controls: `validateCollection`, `flattenCollectionItems`,
|
|
41
|
+
`resolveCollectionItem`, `getCollectionNavigationTarget`,
|
|
42
|
+
`getCollectionTypeaheadMatch`, `reconcileSelectSelection`, `SelectOpenState`
|
|
43
|
+
- reusable visual fragments: focus indicator, field frame, floating surface, collection item
|
|
44
|
+
- planned/beta expansion recipes: Icon, Stack, Link, CheckboxGroup, RadioGroup, Accordion, Menu,
|
|
45
|
+
AlertDialog, Tooltip, Statistic, LoadMore, BottomNavigation
|
|
46
|
+
|
|
47
|
+
## 호환성 메모
|
|
48
|
+
|
|
49
|
+
- `buttonRecipe`와 `fieldRecipe`에는 `slots`와 `defaults`가 추가됐습니다. 기존
|
|
50
|
+
`tones`, `sizes`, `states` 접근은 그대로 동작합니다.
|
|
51
|
+
- Field label, hint, placeholder는 필수 안내로 취급해 모든 기본 surface에서 AA를 지키는
|
|
52
|
+
`textBody`/`textMuted` 역할을 사용합니다. 검색의 아이콘·지우기 버튼·포커스 계약은
|
|
53
|
+
`searchFieldRecipe`로 분리했습니다.
|
|
54
|
+
- 숫자 알림은 상태 라벨용 `badgeRecipe`가 아니라 solid fill과 `99+` 상한을 가진
|
|
55
|
+
`counterBadgeRecipe`를 사용합니다.
|
|
56
|
+
- `surfaceRecipe`의 기존 index 접근은 그대로 유지합니다.
|
|
57
|
+
- light theme의 `danger` foreground는 tinted feedback surface에서도 WCAG AA를 지키도록
|
|
58
|
+
`#b71919`로 깊어졌습니다. `dangerFill`은 `#b91c1c`, `onDanger`는 white 계약을
|
|
59
|
+
유지합니다.
|
|
60
|
+
- 신규 recipe는 beta입니다. 두 제품과 Web/RN renderer 검증 전에는 prop 이름을
|
|
61
|
+
stable API로 간주하지 않습니다.
|
|
62
|
+
- CheckboxGroup 값은 중복 없는 `ReadonlySet`이며 변경 때마다 새 Set을 emit합니다.
|
|
63
|
+
RadioGroup은 nullable single key를 사용합니다. 단독 Radio를 값 입력으로 사용하지 않고
|
|
64
|
+
RadioGroup item으로만 조합합니다.
|
|
65
|
+
- Native renderer에서 `required`, `readOnly`, `invalid`를 켤 때는 운영체제가 지원하는 공통
|
|
66
|
+
접근성 state가 없으므로 현지화된 상태 문구도 함께 전달합니다. Group `required`는 개별
|
|
67
|
+
checkbox/radio의 required로 복제하지 않습니다.
|
|
68
|
+
- 제품 전용 의미는 계속 어댑터에 남깁니다. BurnTok의 `ai` accent와 Yajalal `live`
|
|
69
|
+
같은 상태/색 이름은 코어로 옮기지 않습니다. 공통 `ai` 아이콘 이름은 특정 제품
|
|
70
|
+
스타일이 아닌 일반 “AI 기능” 의미 역할입니다.
|
|
71
|
+
- 위험 작업은 화면에서 직접 Promise와 loading boolean을 조합하지 않고
|
|
72
|
+
`createAlertDialogSession`의 `idle/busy/error/closing/closed` 전이를 사용합니다. async confirm은
|
|
73
|
+
현지화된 `fallbackErrorMessage`를 필수로 받고, 결과는 renderer exit 완료 후 정산합니다.
|
|
74
|
+
- Sheet의 `dismissible`은 visual `sheetRecipe.defaults`에서 제거되어
|
|
75
|
+
`sheetBehaviorDefaults`/`SheetDismissPolicy`로 이동했습니다. 기존 renderer는 outside,
|
|
76
|
+
Escape/Android back, busy, swipe를 각각 `canDismissSheet(reason, busy, policy)`로 판정하고
|
|
77
|
+
`onOpenChange(false, { reason: dismissReason })`를 전달해야 합니다. 기본 swipe는
|
|
78
|
+
꺼져 있으며 gesture capability와 policy가 모두 활성화된 경우에만 handle을 표시합니다.
|
|
79
|
+
- controlled owner가 `open=false`로 닫는 것은 `programmatic` reason이며 busy 또는
|
|
80
|
+
`dismissible=false`여도 허용합니다. 후속 Dialog/AlertDialog는 Sheet exit/onDismiss가 끝난
|
|
81
|
+
뒤 열어야 합니다.
|
|
82
|
+
- RN Android의 `Modal` 종료 시점을 `InteractionManager`로 추정하지 않습니다. successor surface를
|
|
83
|
+
여는 Sheet는 native animation을 끄고 recipe 기반 enter/exit를 renderer가 소유한 뒤, exit 완료와
|
|
84
|
+
Modal host teardown 이후에 `createSheetLifecycle.completeDismiss`를 호출합니다.
|
|
85
|
+
- Select/Combobox adapter는 section 전체에서 item ID를 유일하게 유지하고 비어 있는
|
|
86
|
+
label/textValue/accessibility label을 거부해야 합니다. Select의 open state는 selection과
|
|
87
|
+
별도 축이며, 사라진 key는 `reconcileSelectSelection`으로 정리합니다.
|
|
88
|
+
- collection item·section의 stable ID는 공백일 수 없습니다. typeahead는 locale-aware 검색을
|
|
89
|
+
사용합니다. external Combobox는 raw `inputValue`와 제품이 정규화한 `queryValue`를 분리하고,
|
|
90
|
+
`resultQuery === queryValue`인 결과만 표시합니다. transient 결과에 선택 항목이 없으면 stable
|
|
91
|
+
key가 같은 `selectedItem` snapshot을 공급합니다. 공통 single/multiple selection도 controlled
|
|
92
|
+
값과 default 값을 동시에 받을 수 없습니다.
|
|
93
|
+
- Select/Combobox renderer는 `selectRecipe`/`comboboxRecipe`를 사용합니다. Web은 popover/listbox,
|
|
94
|
+
Native는 Sheet/radio options로 적응하며 Menu role을 대신 사용하지 않습니다.
|
|
95
|
+
- Toast renderer는 화면별 local timeout 배열 대신 `createToastStore`를 하나만 둡니다. 기존
|
|
96
|
+
`toastRecipe.defaults.duration=4000`은 제거됐고 behavior의 기본 5000ms와 최소 5000ms로
|
|
97
|
+
이동했습니다. action이 있는 Toast는 duration을 명시하지 않으면 persistent입니다.
|
|
98
|
+
- `toastRecipe`은 `surface`, `toneMark`, `title`, `description`, `action`, `close`, `viewport`,
|
|
99
|
+
`placements` anatomy로 나뉘며 `transition.enter/exit`은
|
|
100
|
+
`transition.web.enter/exit`과 `transition.native.enter/exit`으로 바뀌었습니다. 각 renderer는
|
|
101
|
+
exit 또는 Reduce Motion 즉시 종료 뒤 `store.completeExit(id)`를 호출해야 합니다.
|
|
102
|
+
- Toast의 같은 stable id는 기본 `update + preserve timer`로 제자리 갱신됩니다. queue는
|
|
103
|
+
bounded FIFO이고 pending overflow, action, close, timeout, programmatic close, provider teardown은
|
|
104
|
+
모두 구체적인 `ToastDismissReason`을 한 번만 전달합니다. hover/focus/window/gesture pause와
|
|
105
|
+
announcement priority 번역은 [`toast.md`](./toast.md)의 renderer acceptance를 따릅니다.
|
|
106
|
+
- color resolver에는 제품 alias가 아니라 `statusAccents`와 `statusAccentFills`를
|
|
107
|
+
분리해 전달합니다. 예를 들어 BurnTok `ai`는 제품 API에 남고 resolver에는 원래의
|
|
108
|
+
`info` status role이 들어가야 합니다.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# v0.3 migration
|
|
2
|
+
|
|
3
|
+
v0.2 → v0.3. **소비자가 손대야 할 것은 하나뿐이고**, 나머지는 전부 가산 변경이다.
|
|
4
|
+
|
|
5
|
+
## 깨지는 변경 하나 — 열거해 둔 톤 목록
|
|
6
|
+
|
|
7
|
+
`buttonRecipe.tones`에 **`link`**가, `surfaceRecipe`에 **`subtle`**이 늘었다. 따라서
|
|
8
|
+
`SurfaceTone`·`ButtonTone` 유니언이 넓어진다.
|
|
9
|
+
|
|
10
|
+
- **읽기만 하는 코드는 영향 없다.**
|
|
11
|
+
- **톤 목록을 열거해 단정하는 테스트는 고쳐야 한다.** BurnTok의
|
|
12
|
+
`packages/design-system/src/index.test.ts`가 그 사례였다.
|
|
13
|
+
- **`SurfaceTone`/`ButtonTone`을 exhaustive switch로 다루는 renderer는 새 분기를 더해야 한다.**
|
|
14
|
+
|
|
15
|
+
## 왜 늘었는가 — 제품이 먼저 찾은 답을 계약이 받았다
|
|
16
|
+
|
|
17
|
+
v0.3의 recipe 변경은 **새로 설계한 것이 아니라 제품이 이미 고쳐 쓰던 것**이다. `app-rn`은
|
|
18
|
+
이 패키지의 recipe 31개를 손으로 옮겨 적은 사본으로 갖고 있었고(자기 주석에
|
|
19
|
+
`Temporary v0.1 bridge matching the HJM recipe exactly`라고 적어 두었다), 그중 10개는
|
|
20
|
+
값이 달랐다. 확인해 보니 **다른 이유가 전부 실제 화면을 보고 고친 것**이었고 측정한 색까지
|
|
21
|
+
근거로 적혀 있었다. 그래서 앱을 계약에 맞추는 대신 계약이 그 답을 받았다.
|
|
22
|
+
|
|
23
|
+
| 바뀐 값 | 이유 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `badgeRecipe.tones.brand.background` → `surfaceAlt` | 브랜드 틴트가 "선택됨"만 뜻하게 되어, 정적 배지가 줄지어 있으면 옵션 하나가 켜진 필터처럼 읽혔다 |
|
|
26
|
+
| `noticeRecipe.tones.info.background` → `surfaceAlt` | 정보 워시가 캔버스 위에서 `#DCE3F3`로 앉아 배너가 눌러야 할 컨트롤보다 위계가 높아졌다 |
|
|
27
|
+
| `segmentedControlRecipe.item.selected*` → 불투명 `surfaceAccent`/`contentBrand` | primary의 10% 워시는 배경에 따라 값이 변해(`#E8EFFB` / `#DCE5F3`) 같은 "선택됨"이 세 색으로 읽혔다 |
|
|
28
|
+
| `selectionControlRecipe.states.selectedBackground` → `surfaceAccent` | 위와 같은 이유(투명 워시는 배경을 탄다) |
|
|
29
|
+
| `chipRecipe.states.idle.border` → `border` | `textMuted`로 그리면 선택되지 않은 칩이 옆의 선택된 칩보다 무거워져 신호가 뒤집혔다 |
|
|
30
|
+
| `bottomCtaRecipe.background` → `surface` | 헤어라인 하나가 글자를 자르고 깨진 카드처럼 읽혔다. 그림자가 아니라 surface로 층을 쌓는다 |
|
|
31
|
+
|
|
32
|
+
## 늘어난 축 (가산)
|
|
33
|
+
|
|
34
|
+
- `buttonRecipe.tones.link` — 구역 오류 복구처럼 **조용해야 하지만 나아갈 길로 읽혀야 하는**
|
|
35
|
+
동작. 채움은 복구가 화면의 **유일한** 출구일 때만이다.
|
|
36
|
+
- `surfaceRecipe.subtle` — 알리는 면(설명 배너·등급 판·조용한 콜아웃)의 자리. 헤어라인을
|
|
37
|
+
항상 그린다.
|
|
38
|
+
- `searchFieldRecipe.shapes` — 모양이 **축이 되었다**(`radius` 단일값을 대체). `fieldRecipe`와
|
|
39
|
+
같은 키를 쓰므로 두 입력의 모양을 맞추거나 일부러 다르게 하는 것이 둘 다 표현된다.
|
|
40
|
+
- `selectionControlRecipe.presentations.grouped`, `selectionGroupRecipe`의 `grouped` 간격,
|
|
41
|
+
`surfaceRecipe.*.borderAlways`, `switchRecipe`의 disabled 색 6종과 `rowTwoLineMinHeight`,
|
|
42
|
+
`bottomCtaRecipe.shadow`, `badgeRecipe.tones.strong`.
|
|
43
|
+
|
|
44
|
+
## 새 계약 (약 30개)
|
|
45
|
+
|
|
46
|
+
antd 6.6.0 reference inventory 73개 중 계약 완료가 27 → 56개가 되었다. 전부
|
|
47
|
+
`status: "planned"`이므로 **renderer 구현을 약속하지 않는다** — `docs/expansion-roadmap.md`의
|
|
48
|
+
maturity gate 그대로다. 목록은 `src/catalog.ts`와 `docs/ant-design-coverage.md`에 있다.
|
|
49
|
+
|
|
50
|
+
`content-state.ts`가 그중 실제 제품 결함을 닫은 첫 사례다. 상태가 **화면 전체를 대신하는지
|
|
51
|
+
구역만 대신하는지**(`scope: "screen" | "region"`)가 강조 수준과 접근성 발표를 함께 정한다.
|
|
52
|
+
`app-rn`에서 화면 전체 오류가 보조 기술에 아무것도 알리지 않는 동안 구역 오류만 알리고 있던
|
|
53
|
+
것을 이 축이 잡았다.
|
|
54
|
+
|
|
55
|
+
## v0.3에서 남긴 제약 하나 — 쉬는 칩은 `surfaceAlt` 위에 놓지 않는다
|
|
56
|
+
|
|
57
|
+
`chipRecipe.states.idle.border`를 `content.secondary`에서 `border.default`로 옮긴 것(위 표)의
|
|
58
|
+
부작용이다. **`border`와 `surfaceAlt`는 두 테마에서 정확히 같은 색이다**(light `#e5e8eb`,
|
|
59
|
+
dark `#1e293b`). 그래서 쉬는 칩을 `surfaceAlt` 배경 위에 놓으면 테두리가 사라지고, 칩의
|
|
60
|
+
채움(`surface`)도 부모와 거의 같아져(`#f2f4f6` 대 `#e5e8eb`, 약 1.1:1) **칩의 경계가 보이지
|
|
61
|
+
않는다.**
|
|
62
|
+
|
|
63
|
+
이 변경을 받으면서 `test/contracts.test.ts`의 대비 단정에서 이 항목을 뺐다. 그 단정은
|
|
64
|
+
"쉬는 칩 테두리가 `surfaceAlt` 위에서 3:1 이상"을 요구했는데, **쉬는 칩의 테두리는 공용
|
|
65
|
+
헤어라인이지 인터랙티브 경계가 아니다** — 선택된 칩의 테두리(`content.brand`)가 그 역할을
|
|
66
|
+
한다. 즉 단정의 전제가 틀렸다.
|
|
67
|
+
|
|
68
|
+
**그러나 제약은 남는다.** 확인 결과 `app-rn`에서 칩을 쓰는 8개 화면 중 `surfaceAlt` 배경을
|
|
69
|
+
쓰는 곳은 없어서 지금은 안전하다(`surfaceAlt`는 라이브·라인업·경기상세에서만 쓰이고 그
|
|
70
|
+
화면들에는 칩이 없다). 그 조합이 생기면 **칩의 presentation을 `surface`가 아닌 것으로
|
|
71
|
+
바꾸거나 부모 배경을 옮겨야 한다** — 테두리 색을 되돌리는 것은 답이 아니다. 되돌리면
|
|
72
|
+
선택되지 않은 칩이 옆의 선택된 칩보다 무거워져서 필터 줄의 유일한 신호가 다시 뒤집힌다.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# v0.5 migration
|
|
2
|
+
|
|
3
|
+
v0.4 → v0.5는 기존 foundation/recipe 런타임 값을 유지하는 additive release입니다. 다만
|
|
4
|
+
catalog status를 그대로 열거하거나 Showcase route 존재를 renderer 증거로 사용한 소비자는
|
|
5
|
+
아래 두 가지를 확인해야 합니다.
|
|
6
|
+
|
|
7
|
+
## 소비자가 확인할 변경
|
|
8
|
+
|
|
9
|
+
### DesignSystemProvider maturity
|
|
10
|
+
|
|
11
|
+
`DesignSystemProvider`는 `beta`에서 `planned + contract-ready`로 정정됐습니다. 환경 resolver가
|
|
12
|
+
존재한다는 사실만으로 실제 Web/RN Context adapter가 검증됐다고 볼 수 없기 때문입니다.
|
|
13
|
+
catalog status를 exhaustive하게 단정하는 테스트는 새 값을 반영해야 합니다. 공개 환경 API는
|
|
14
|
+
삭제되지 않았습니다.
|
|
15
|
+
|
|
16
|
+
### Ant Design coverage 명칭
|
|
17
|
+
|
|
18
|
+
`summarizeAntDesignCoverage()`의 renderer를 암시하던 필드는 deprecated alias로 유지되며,
|
|
19
|
+
새 코드는 maturity 전용 필드를 사용합니다.
|
|
20
|
+
|
|
21
|
+
| 기존 alias | 새 필드 | 실제 의미 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `fullyPreviewable` | `fullyMature` | 모든 HJM target이 stable/beta |
|
|
24
|
+
| `partiallyPreviewable` | `partiallyMature` | target 일부만 stable/beta |
|
|
25
|
+
| `contractOnly` | `plannedOnly` | target이 모두 planned |
|
|
26
|
+
|
|
27
|
+
실제 Web preview 수는 Showcase evidence registry에서 별도로 읽습니다.
|
|
28
|
+
|
|
29
|
+
## 신규 foundation
|
|
30
|
+
|
|
31
|
+
- `fontFamily.ui | code`
|
|
32
|
+
- `fontWeight.regular | medium | semibold | bold | heavy`
|
|
33
|
+
- `letterSpacing.tight | normal | wide`
|
|
34
|
+
- `numeric.proportional | tabular`
|
|
35
|
+
- `heading.level1 ... level5`
|
|
36
|
+
|
|
37
|
+
기존 `typography` key와 런타임 숫자·weight 값은 유지됩니다. recipe 내부 weight도 같은 foundation
|
|
38
|
+
값을 참조하므로 화면 변화 없이 단일 출처가 됩니다.
|
|
39
|
+
|
|
40
|
+
## Provider 계약
|
|
41
|
+
|
|
42
|
+
`resolveDesignSystemEnvironment()`는 기존 호출을 유지하면서 선택적인 parent/system signal을
|
|
43
|
+
받습니다.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const value = resolveDesignSystemProviderValue(
|
|
47
|
+
{ direction: "rtl" },
|
|
48
|
+
{
|
|
49
|
+
parent: parentValue.environment,
|
|
50
|
+
systemTheme: "dark",
|
|
51
|
+
systemTextScale: 1.25,
|
|
52
|
+
systemReducedMotion: true,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// resolveColorReference에 그대로 전달
|
|
57
|
+
value.palette;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
축별 우선순위는 `explicit input → resolved parent → system signal → HJM default`입니다.
|
|
61
|
+
`parent`는 반드시 이미 해석된 light/dark 환경이어야 하며 `theme: "system"`을 허용하지 않습니다.
|
|
62
|
+
임의 theme/component token override와 React/RN Context는 계속 코어 범위 밖입니다.
|
|
63
|
+
|
|
64
|
+
## Showcase와 CI
|
|
65
|
+
|
|
66
|
+
- 91개 canonical route를 Web reference, contract-only, Web unsupported로 분리합니다.
|
|
67
|
+
- planned route는 구현된 것처럼 보이는 JSX를 렌더링하지 않습니다.
|
|
68
|
+
- Home/Explorer 수치는 실제 evidence registry에서 계산합니다.
|
|
69
|
+
- Storybook manager와 preview가 foundation token을 사용합니다.
|
|
70
|
+
- token-boundary 검사와 static classification 검사가 Showcase check에 포함됩니다.
|
|
71
|
+
- Ant Design reference는 6.6.1로 고정됩니다. v0.7부터 외부 registry drift 검사는 자동 CI가
|
|
72
|
+
아니라 필요할 때 실행하는 `pnpm reference:antd:verify`로 단순화되었습니다.
|
|
73
|
+
- GitHub Pages workflow는 Node 24 기반 action major를 사용합니다.
|
|
74
|
+
|
|
75
|
+
## 권장 전환 순서
|
|
76
|
+
|
|
77
|
+
1. package/tag를 `v0.5.0`으로 고정
|
|
78
|
+
2. Provider adapter가 OS 신호를 한 번만 측정하고 새 resolver에 전달하는지 확인
|
|
79
|
+
3. 로컬 raw font weight 대신 `fontWeight` foundation 사용
|
|
80
|
+
4. AntD coverage UI가 deprecated previewable alias를 renderer 수치로 표시하지 않는지 확인
|
|
81
|
+
5. Web/RN 제품 fixture에서 light/dark, RTL, 200% text, Reduce Motion 검증
|
|
82
|
+
6. `pnpm check`와 제품별 renderer/접근성 테스트 실행
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# v0.6 migration
|
|
2
|
+
|
|
3
|
+
v0.6은 renderer가 없는 계약 패키지의 역할을 이름에서 분명히 하고, Web/RN 앱이 필요한
|
|
4
|
+
graph만 가져가도록 package boundary를 나누는 breaking release입니다. package name은
|
|
5
|
+
`@hjm/design-system`에서 `@hjmds/design-contracts`로 변경됩니다. 이전 이름 alias나 호환
|
|
6
|
+
wrapper는 제공하지 않습니다.
|
|
7
|
+
|
|
8
|
+
같은 release에서 공식 renderer도 monorepo package로 제공됩니다. Web은 `@hjmds/react`,
|
|
9
|
+
React Native는 `@hjmds/react-native`를 선택하고 contracts와 같은 tag를 고정합니다.
|
|
10
|
+
|
|
11
|
+
## 1. dependency와 import를 원자적으로 변경
|
|
12
|
+
|
|
13
|
+
dependency와 source/test/Storybook/build script의 import를 같은 변경에서 전환합니다.
|
|
14
|
+
|
|
15
|
+
```diff
|
|
16
|
+
- "@hjm/design-system": "git+https://github.com/jim1286/hjm-design-system.git#v0.5.2"
|
|
17
|
+
+ "@hjmds/design-contracts": "git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
기존 root symbol은 유지되지만 package specifier가 바뀌므로 모든 import를 갱신해야 합니다.
|
|
21
|
+
|
|
22
|
+
```diff
|
|
23
|
+
- import { spacing, typography } from "@hjm/design-system";
|
|
24
|
+
+ import { spacing, typography } from "@hjmds/design-contracts/foundations";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 2. 앱 runtime은 granular subpath 사용
|
|
28
|
+
|
|
29
|
+
- 토큰 전체: `@hjmds/design-contracts/tokens`
|
|
30
|
+
- foundation만: `@hjmds/design-contracts/foundations`
|
|
31
|
+
- palette만: `@hjmds/design-contracts/colors`
|
|
32
|
+
- window class와 responsive value: `@hjmds/design-contracts/responsive`
|
|
33
|
+
- Grid descriptor와 geometry: `@hjmds/design-contracts/grid`
|
|
34
|
+
- 공통 recipe: `@hjmds/design-contracts/recipes`
|
|
35
|
+
- Button·Surface·Field 최소 recipe: `@hjmds/design-contracts/recipes/base`
|
|
36
|
+
- 공통 anatomy/style contract: `@hjmds/design-contracts/contracts`
|
|
37
|
+
- 현재 contract 버전만: `@hjmds/design-contracts/version`
|
|
38
|
+
- 단일 상태·validator: `@hjmds/design-contracts/components/<name>`
|
|
39
|
+
|
|
40
|
+
예를 들어 Toast adapter는 전체 root 대신 다음처럼 가져옵니다.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createToastStore } from "@hjmds/design-contracts/components/toast";
|
|
44
|
+
import { resolveContentStateAnnouncement } from "@hjmds/design-contracts/components/content-state";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`/recipes/all`, `/behaviors`, `/catalog`, `/evidence`, `/showcase`는 전체 registry 또는 CI
|
|
48
|
+
metadata가 필요한 도구용입니다. JS를 실행하지 않는 CI는
|
|
49
|
+
`@hjmds/design-contracts/manifest.json`과
|
|
50
|
+
`@hjmds/design-contracts/renderer-evidence.json`을 읽을 수 있습니다. RN 화면 runtime에서
|
|
51
|
+
root나 tooling entry를 사용하면 Metro가 불필요한 계약 graph를 따라갈 수 있습니다.
|
|
52
|
+
|
|
53
|
+
## 3. renderer 설치
|
|
54
|
+
|
|
55
|
+
Web 앱:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm add \
|
|
59
|
+
'@hjmds/design-contracts@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts' \
|
|
60
|
+
'@hjmds/react@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/react'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
React Native 앱은 Web package 이름을 유지한 채 path만 바꾸지 말고, Native renderer 이름과
|
|
64
|
+
path를 함께 지정합니다.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm add \
|
|
68
|
+
'@hjmds/design-contracts@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts' \
|
|
69
|
+
'@hjmds/react-native@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/react-native'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
renderer는 contracts를 peer dependency로 요구하므로 두 항목을 모두 명시해야 합니다.
|
|
73
|
+
|
|
74
|
+
## 4. renderer의 localized copy를 명시적으로 주입
|
|
75
|
+
|
|
76
|
+
v0.6 renderer는 한국어 또는 영어 문구를 내부 default로 만들지 않습니다. 아래 prop은
|
|
77
|
+
사용자에게 보이거나 screen reader가 읽는 제품 copy이므로 필수가 되었고, 누락하면
|
|
78
|
+
TypeScript가 migration 지점을 표시합니다.
|
|
79
|
+
|
|
80
|
+
- Web: `SearchField.clearLabel`, `Select.placeholder`,
|
|
81
|
+
`Select.emptySelectionLabel`, `Combobox.emptyMessage`,
|
|
82
|
+
`Combobox.loadingMessage`, `Combobox.selectionRequiredMessage`,
|
|
83
|
+
`Dialog.closeLabel`, `Sheet.closeLabel`, `Table.emptyState`
|
|
84
|
+
- React Native: `Form.fallbackErrorMessage`, `SearchField.clearLabel`,
|
|
85
|
+
`SearchField.busyLabel`, `Select.placeholder`, `Select.dismissLabel`,
|
|
86
|
+
`Combobox.emptyMessage`, `Combobox.loadingMessage`, `Combobox.clearLabel`,
|
|
87
|
+
`Combobox.dismissLabel`, `Menu.dismissLabel`, `Dialog.closeLabel`,
|
|
88
|
+
`Sheet.closeLabel`
|
|
89
|
+
|
|
90
|
+
앱의 i18n catalog에서 각 값을 공급합니다. 선택 목록·결과·패널 region 이름처럼 기존
|
|
91
|
+
control label에서 중립적으로 유도할 수 있는 이름은 optional prop으로 남으며, renderer는
|
|
92
|
+
번역 suffix를 덧붙이지 않습니다.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<SearchField label={t("search.label")} clearLabel={t("search.clear")} />
|
|
96
|
+
<Dialog title={t("settings.title")} closeLabel={t("settings.close")} trigger={trigger} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 5. Web/RN core component API 정규화
|
|
100
|
+
|
|
101
|
+
Text, Surface, Stack, Button, Tag, Card는 두 renderer에서 같은 semantic axis와 기본값을
|
|
102
|
+
사용합니다. 신규 코드는 다음 canonical API를 사용합니다.
|
|
103
|
+
|
|
104
|
+
```diff
|
|
105
|
+
- <Stack direction="row" />
|
|
106
|
+
+ <Stack axis="inline" />
|
|
107
|
+
|
|
108
|
+
- <Button label={t("save")} />
|
|
109
|
+
+ <Button>{t("save")}</Button>
|
|
110
|
+
|
|
111
|
+
- <Tag label={t("new")} />
|
|
112
|
+
+ <Tag>{t("new")}</Tag>
|
|
113
|
+
|
|
114
|
+
- <Surface tone="brand" />
|
|
115
|
+
+ <Surface tone="accent" />
|
|
116
|
+
|
|
117
|
+
- <Grid descriptor={{ columns: { compact: 2 } }} />
|
|
118
|
+
+ <Grid columns={{ compact: 2 }} />
|
|
119
|
+
|
|
120
|
+
- <IconButton accessibilityLabel={t("close")} icon={<Close />} tone="link" />
|
|
121
|
+
+ <IconButton label={t("close")} tone="ghost"><Close /></IconButton>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
위 Native 호출은 0.6에서도 deprecated alias로 동작합니다. `Stack.direction`,
|
|
125
|
+
`Button.label`, `Tag.label`, Surface의 `sunken`/`brand`, `Grid.descriptor`, IconButton의
|
|
126
|
+
`accessibilityLabel`/`icon`/`tone="link"` 제거는 major release에서만
|
|
127
|
+
진행합니다. 하지만 새 예제·Storybook·제품 코드는 alias를 사용하지 않아야 합니다.
|
|
128
|
+
|
|
129
|
+
Button의 `loading`은 이제 `disabled`와 다른 상태입니다. 중복 activation은 막지만
|
|
130
|
+
포커스를 제거하지 않고 busy 상태를 보조 기술에 발표합니다. 로딩 중 포커스가 다른 곳으로
|
|
131
|
+
강제로 이동한다고 가정한 테스트가 있다면 focus 유지 기대값으로 바꿉니다.
|
|
132
|
+
|
|
133
|
+
Card는 더 이상 Native의 단순 Surface alias가 아닙니다. 양쪽에서 `media`, `title`,
|
|
134
|
+
`description`, `children`, `actions`, `selected` anatomy를 공유하며 계약은
|
|
135
|
+
`@hjmds/design-contracts/components/card`와 `cardRecipe`에서 가져옵니다. 전체 정규화 표와
|
|
136
|
+
호환 범위는 [`cross-platform-core-normalization.md`](./cross-platform-core-normalization.md)를
|
|
137
|
+
참고합니다.
|
|
138
|
+
|
|
139
|
+
## 6. 실험적 NumberField·Slider renderer
|
|
140
|
+
|
|
141
|
+
Web과 Native renderer에 `NumberField`와 `./number-field` granular entry가 추가됐습니다.
|
|
142
|
+
값은 `number | null`이고 편집 중 문자열은 blur/submit 전까지 별도 draft로 유지합니다.
|
|
143
|
+
두 플랫폼 모두 증감 action의 현지화된 이름을 필수로 받습니다.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<NumberField
|
|
147
|
+
label={t("partySize.label")}
|
|
148
|
+
decrementLabel={t("partySize.decrement")}
|
|
149
|
+
incrementLabel={t("partySize.increment")}
|
|
150
|
+
min={1}
|
|
151
|
+
max={8}
|
|
152
|
+
defaultValue={2}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
현재 parser는 locale-neutral ASCII decimal/exponent만 지원합니다. 통화·단위·grouping separator,
|
|
157
|
+
locale별 decimal separator를 자동 지원한다고 가정하지 마세요. 실제 제품 numeric-input
|
|
158
|
+
vertical slice가 아직 없으므로 catalog surface는 `planned`이며, 이 API는 0.6에서 먼저
|
|
159
|
+
검증하는 experimental renderer입니다.
|
|
160
|
+
|
|
161
|
+
`Slider`와 `./slider` entry도 두 renderer에 추가됐습니다. NumberField와 같은 min-origin
|
|
162
|
+
clamp/snap resolver를 사용하며, drag/keyboard 중 `onValueChange`와 interaction 종료 시
|
|
163
|
+
`onValueChangeEnd`를 분리합니다. Web은 native range input의 keyboard/pointer semantics를
|
|
164
|
+
사용하고, Native는 dependency-free responder와 `adjustable` action을 사용합니다.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<Slider
|
|
168
|
+
label={t("score.label")}
|
|
169
|
+
min={0}
|
|
170
|
+
max={100}
|
|
171
|
+
step={5}
|
|
172
|
+
defaultValue={50}
|
|
173
|
+
getValueText={(value) => t("score.value", { value })}
|
|
174
|
+
onValueChange={setDraftScore}
|
|
175
|
+
onValueChangeEnd={saveScore}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Native에서는 위 props에 제품이 번역한 `decrementLabel`과 `incrementLabel`도 필수입니다.
|
|
180
|
+
첫 slice는 horizontal single-thumb만 지원합니다. range/multi-thumb, vertical orientation,
|
|
181
|
+
marks, nonlinear scale은 실제 제품 요구와 fixture가 생기기 전까지 열지 않습니다. Slider도
|
|
182
|
+
실제 product numeric flow 증거 전에는 catalog Web/Native surface가 `planned`입니다.
|
|
183
|
+
|
|
184
|
+
## 7. 설치·번들 검증
|
|
185
|
+
|
|
186
|
+
1. lockfile을 다시 생성합니다.
|
|
187
|
+
2. 이전 package name 또는 중복 dependency가 남지 않았는지 검사합니다.
|
|
188
|
+
3. Web production build와 RN Metro/Hermes export를 모두 실행합니다.
|
|
189
|
+
4. 이 저장소에서는 import graph budget까지 포함하는 `pnpm check`를 실행합니다.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
rg '@hjm/design-system' src test package.json
|
|
193
|
+
pnpm check
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
첫 검색은 결과가 없어야 합니다. `v0.5.2`와 이전 tag에는 이전 package name이 들어 있으므로,
|
|
197
|
+
새 package name으로 바꾼 뒤 예전 tag를 계속 가리키는 조합은 설치할 수 없습니다.
|