@hjmds/design-contracts 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/alert-dialog.d.ts +1 -1
- package/dist/alert-dialog.d.ts.map +1 -1
- package/dist/alert-dialog.js.map +1 -1
- 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/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 +346 -3
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +35 -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 +1294 -239
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +65 -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 +16 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +16 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +76 -4
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +38 -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 +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -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 +15 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +14 -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/architecture.md +10 -0
- package/docs/asset.md +26 -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 +23 -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 +39 -23
- package/docs/generated/renderer-evidence.json +2921 -642
- package/docs/generated/renderer-evidence.md +53 -6
- package/docs/generated/showcase-manifest.json +1087 -64
- 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 +120 -6
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# React·React Native 컴포넌트 완성 작업
|
|
2
|
+
|
|
3
|
+
기준: 2026-09-16 · 사용자 요청: Flutter 제외, React/RN을 채우고 토스 UI를 중심 모티브로 사용.
|
|
4
|
+
이전 8개 앱 감사의 작은 수정 묶음과 별도인 지속 작업이다. 목록 등록만으로 완료하지 않는다.
|
|
5
|
+
|
|
6
|
+
## 완성 기준
|
|
7
|
+
|
|
8
|
+
- 지원 플랫폼의 공개 renderer, 타입·granular export, 실제 조작 가능한 Showcase를 제공한다.
|
|
9
|
+
- 키보드/터치·focus·선택·비동기 상태·큰 글자·RTL·dark·reduced motion 중 해당하는 계약을 검증한다.
|
|
10
|
+
- 외부 비교를 API와 시각 결정에 연결한다. 의도적으로 합성하는 항목은 작동하는 합성 예제를 제공한다.
|
|
11
|
+
- 기존 입력 설명/focus·Dialog 종료 계약과 토스 모티브의 목록/제목/하단 행동 영역도 보강한다.
|
|
12
|
+
- 전체 구현과 검증을 마친 뒤 minor 버전으로 패키지를 게시하고 모든 React·React Native
|
|
13
|
+
소비 앱의 dependency·lockfile·계약·표준 projection을 갱신해 회귀 검증한다. Flutter는 제외한다.
|
|
14
|
+
- 원본 catalog·evidence에서 projection을 생성한다. 지원하지 않는 플랫폼과 검증되지 않은
|
|
15
|
+
stable 승격을 완료 숫자로 바꾸지 않는다. 모든 필수 항목이 끝나기 전 전체 목표는 진행 중이다.
|
|
16
|
+
|
|
17
|
+
## 시각적 중심: 토스 UI
|
|
18
|
+
|
|
19
|
+
이번 명시 요청으로 TDS를 가장 우선하는 시각 참조로 둔다. 기존 HJM의 조용한 canvas,
|
|
20
|
+
명확한 주 행동, 읽기 쉬운 목록이라는 정체성과 연결한다.
|
|
21
|
+
|
|
22
|
+
| 참조 | 확인한 원칙 | HJM 적용 |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| [TDS ListRow](https://tossmini-docs.toss.im/tds-mobile/components/ListRow/list-row-overview/) | leading/content/trailing의 위계와 행 밀도 | 장식 카드보다 평평한 목록, 제목/설명/보조 행동의 정렬, compact/comfortable 예제 |
|
|
25
|
+
| [TDS Button](https://tossmini-docs.toss.im/tds-mobile/components/button/) | 행동 중요도·크기·진행 상태의 구분 | 주요 fill 하나, 보조 tint/ghost, 짧은 명시적 문구, loading 크기 보존 |
|
|
26
|
+
| [TDS BottomCTA](https://tossmini-docs.toss.im/tds-mobile/components/BottomCTA/check-first/) · [FixedBottomCTA](https://tossmini-docs.toss.im/tds-mobile/components/BottomCTA/fixed-bottom-cta/) | 본문과 마지막 행동을 연결하는 하단 영역 | Web 지원 추가, 본문 가림·safe area·큰 글자·두 행동 배치 검증 |
|
|
27
|
+
| [Radix Dialog](https://www.radix-ui.com/primitives/docs/components/dialog) | focus·dismiss·controlled 구성 | overlay 종료와 focus 복구 후 다음 overlay 연결 |
|
|
28
|
+
| [React Aria Calendar](https://react-aria.adobe.com/Calendar) | 날짜 탐색·현지화·접근 가능한 선택 | 현재 Calendar 계약을 실제 Web/RN 화면으로 연결 |
|
|
29
|
+
| [Ant Design 목록](https://ant.design/components/overview/) | 입력·탐색·데이터·피드백 범위 | 누락 목록 점검, 데스크톱 기능과 모바일 표현을 구별 |
|
|
30
|
+
| [MUI composition](https://mui.com/material-ui/guides/composition/) | 합성 시 host 의미와 전달 props 보존 | 링크/버튼/행의 중첩 제어와 공개 ref 경계 검증 |
|
|
31
|
+
| [WAI Carousel](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/) | 숨긴 카드의 focus 제외, 수동 이동·재생 정지 | stable key, 위치 표시, 비활성 카드 격리, 기본 자동재생 없음 |
|
|
32
|
+
|
|
33
|
+
토스의 자산·폰트 파일·구현을 포함하지 않고 위 구성을 HJM 토큰과 독립 코드로 구현한다.
|
|
34
|
+
검증 화면은 같은 데이터로 Web/RN을 비교하며 한영 장문, 320px, 큰 글자를 포함한다.
|
|
35
|
+
|
|
36
|
+
## 전체 누락 목록과 작업 순서
|
|
37
|
+
|
|
38
|
+
초기 catalog는94종, Web/RN 각63개 renderer다. 아래26개는 planned에 포함된 기능/합성 범위다.
|
|
39
|
+
공통화를 거절한 AppProvider/BorderBeam/Utility3개는 별도 컴포넌트를 만들지 않는 기존 결정이다.
|
|
40
|
+
Context 배선/정적 토큰 사용은 예제로 설명하며 BorderBeam은 모티브의 정보 위계에 맞지 않는다.
|
|
41
|
+
|
|
42
|
+
| 묶음 | 대상 | 지원 방식 | 상태 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| 이동·모바일 구조 | Carousel, FloatingActionButton; Web TopBar/BottomCTA 보완 | React/RN, Web의 safe area와 RN 터치 관습 반영 | 구현·로컬 검증 완료, 앱 채택 대기 |
|
|
45
|
+
| 날짜·선택 | Calendar, TimePicker, Rating | React/RN, 합성도 공개된 사용 경로와 동작 예제로 완결 | Calendar 구현·검증, TimePicker/Rating 작동 조합 제공 |
|
|
46
|
+
| 계층·다중 선택 | Mentions, TransferList, Cascader, TreeSelect, Tree | Web 완전 탐색, RN에서 계약이 지원하는 흐름 | Tree·TransferList·Mentions는 Web renderer 완료(RN 미구현), TreeSelect·Cascader는 조합으로 제공 |
|
|
47
|
+
| Web 탐색 | Breadcrumb, Pagination, Anchor, CommandPalette | React의 키보드·링크·scroll 계약 | 네 개 모두 구현·로컬 검증 완료 |
|
|
48
|
+
| 보조 표면 | Popover, ConfirmPopover, SidePanel, Tour, Splitter | React; 모달/비모달·focus 경계 보존 | Popover·SidePanel·Tour·Splitter 구현·로컬 검증 완료, ConfirmPopover는 조합으로 제공 |
|
|
49
|
+
| 데이터·배치 | DataTable, Masonry, VirtualList, QRCode | 계약별 플랫폼 구분; 가상화/코드 생성은 실제 출력 검증 | DataTable 구현 완료. 나머지 셋은 각 문서의 "검증할 화면이 없음" 판정이 유효해 planned 유지 |
|
|
50
|
+
| 기타 Web 기능 | ColorPicker, Watermark, Affix | React의 해당 기능, 명시된 지원 범위 | 세 문서 모두 "만들지 않는다" 판정이라 구현하지 않는다(아래 근거) |
|
|
51
|
+
| 기존 품질 | Field/TextArea 설명·Checkbox focus, Dialog 종료, ListRow/Section/버튼 조합 | React/RN + 토스 모티브 실제 화면 | 전부 완료 |
|
|
52
|
+
|
|
53
|
+
플랫폼 자체가 다른 Web 전용 기능을 RN에 이름만 같은 빈 wrapper로 추가하지 않는다.
|
|
54
|
+
누락 구현과 합성 예제, 의도적 비지원은 각각 증거를 남긴다. 새 공개 API는 Changeset에 기록한다.
|
|
55
|
+
|
|
56
|
+
## 실행 기록
|
|
57
|
+
|
|
58
|
+
- 2026-09-16: 현재 소스/미커밋 변경을 재확인하고 위 공식 문서를 비교했다.
|
|
59
|
+
- Web TopBar/BottomCTA 추가. 작은 화면의 상단 행동 줄바꿈, 큰 글자의 footer flex 높이,
|
|
60
|
+
busy 버튼 폭·focus, sticky 본문 가림을 검증했다. [설계와 사용법](./screen-chrome.md).
|
|
61
|
+
- Web/RN Switch의 `labelVisibility`와 설정 조합 예제 추가. Web List의 들여쓴 구분선이
|
|
62
|
+
행 전체를 이동시키던 문제와 Button/IconButton의 `aria-disabled` 전달 누락을 수정했다.
|
|
63
|
+
- Web/RN Carousel 추가. Web keyboard·숨김·controlled·autoplay 및 Native swipe·adjustable·
|
|
64
|
+
screen-reader/reduced-motion guard를 검사했다. [API와 근거](./carousel.md).
|
|
65
|
+
- 브라우저에서 320px 설정 선택/저장, 2배 글자·dark·RTL의 정렬, Carousel 이동과 focus
|
|
66
|
+
유지를 확인했다. Showcase의 옛 BottomCTA/Carousel 데모 CSS를 제거하고 클래스 중복을
|
|
67
|
+
검사한다. 실제 Native 기기·배포 검증은 아직 아니다.
|
|
68
|
+
- 현재 독립 renderer 지원은 Web 71 / Native 66다. stable은 각 4개로 유지한다. planned 23개에는
|
|
69
|
+
기존 declined 3개와 완성된 TimePicker/Rating 조합 2개가 포함되어 기능/합성 잔여는 18개다.
|
|
70
|
+
조합은 새 renderer가 아니므로 지원 개수에 더하지 않는다. 이 기록은 전체 완료가 아니다.
|
|
71
|
+
- Breadcrumb/Pagination은 기존 공개 소스를 분리·보완했고 Table은 기존 Web 공개 소스가 있다.
|
|
72
|
+
DataTable 작업에서는 기존 Table과 계약·API·검증·Showcase를 대조해 중복 구현을 피한다.
|
|
73
|
+
- minor 게시·모든 호환 앱 적용은 전체 구현 뒤 실행한다. 현재 버전은 1.1.1이며 게시 전이다.
|
|
74
|
+
- Carousel/chrome 묶음의 `pnpm ci:check` exit 0: contracts 731, Web SSR 134 + browser 120,
|
|
75
|
+
Native 259 = 패키지 테스트 1,244개. Showcase Web 19 / Native 1, 타입·문서·evidence·
|
|
76
|
+
플랫폼/번들 경계와 정적 Storybook 검증도 통과했다. 마지막 큰 글자 Carousel 배치는
|
|
77
|
+
별도 browser 8개를 다시 통과했고 최종 빌드 후 번들 예산도 재확인했다.
|
|
78
|
+
- Native Metro Android production bundle: 577 modules, raw 1400.4 KiB / gzip 345.4 KiB.
|
|
79
|
+
React Native 포함 pre-Hermes 측정이며 실제 기기 성능 수치나 설치 증거가 아니다.
|
|
80
|
+
- FAB 후속 묶음: Web/RN 공개 renderer·scroll hook·실측 본문 여백과 기록 작성 조합을 추가했다.
|
|
81
|
+
[MUI FAB](https://mui.com/material-ui/react-floating-action-button/)의 원형/확장 행동 구분을
|
|
82
|
+
비교하고 HJM의 큰 primary Button과 토스 모티브의 평평한 목록을 사용했다.
|
|
83
|
+
모양 전환 중 동일한 버튼과 접근성 이름을 유지하고, micro motion 계약에 맞춰 reduced motion은
|
|
84
|
+
즉시 전환한다. [API와 결정 근거](./floating-action-button.md).
|
|
85
|
+
- 브라우저 320px에서 작성 → 저장 → 첫 행 반영 → FAB focus 복귀, 목록 끝 도달과 축소를
|
|
86
|
+
확인했다. 화면 조합의 자체 viewport에 Showcase padding이 중복돼 생기던 두 번째 scrollbar는
|
|
87
|
+
명시적 `hjm.edgeToEdge` 옵션으로 제거했다. 일반 컴포넌트 데모의 여백은 그대로 유지한다.
|
|
88
|
+
- FAB 포함 `pnpm ci:check` exit 0: contracts 731, Web SSR 135 + browser 124, Native 263 =
|
|
89
|
+
패키지 테스트 1,253개. Showcase Web 19 / Native 1, 타입·번들·문서·evidence·정적 Storybook도
|
|
90
|
+
통과했다. Metro Android 578 modules, raw 1404.1 KiB / gzip 346.1 KiB (pre-Hermes).
|
|
91
|
+
정적 Storybook은 Web renderer 67 / contract-only 27로 검증했다. 게시·제품 채택·기기 검증은 남아 있다.
|
|
92
|
+
- Calendar를 Web/RN 공개 entry로 추출하고 DatePicker가 같은 격자를 사용하도록 바꿨다.
|
|
93
|
+
비활성 날짜 focus/활성화 분리, product-owned 경계 날짜·월 이동, default/controlled 선택,
|
|
94
|
+
큰 글자·RTL·7열 최소 target·주석 행 정렬을 검증했다. [API와 근거](./calendar.md).
|
|
95
|
+
DatePicker가 열린 채 disabled/readOnly로 바뀌는 경우에도 선택을 막고 비활성 상태를 알린다.
|
|
96
|
+
- 브라우저 320px에서 9월 30일 → 10월 1일 방향키 이동 중 선택 유지, Enter 확정, dark·RTL·
|
|
97
|
+
2배 글자를 확인했다. Calendar의 옛 Showcase CSS를 제거하고 화면 조합의 제목/본문에만
|
|
98
|
+
여백을 줘 기본 320px에서 7열을 보여준다. 더 좁은 호스트는 격자만 스크롤한다.
|
|
99
|
+
- TimePicker는 시·분 Select와 최종 확정·초기화, Rating은 Slider 정수/반점 입력·저장과
|
|
100
|
+
Statistic 읽기 전용 평균 예제를 양쪽에 제공한다. 시만 선택하면 확정 불가, 23:59 확정·초기화,
|
|
101
|
+
키보드 3 → 3.5점 변경·저장 흐름을 브라우저에서 확인했다. 독립 renderer/API를 추가한 것은 아니다.
|
|
102
|
+
[시간 조합](./time-picker.md), [점수 조합](./rating.md). Native 기기 조작은 아직 검증하지 않았다.
|
|
103
|
+
- Calendar·조합 묶음 최종 `pnpm ci:check` exit 0: contracts 731, Web SSR 136 + browser 131,
|
|
104
|
+
Native 268 = 패키지 테스트 1,266개. Showcase Web 19 / Native 1, 타입·문서·evidence·번들 경계와
|
|
105
|
+
정적 Storybook(Web renderer 68 / contract-only 26)도 통과했다.
|
|
106
|
+
Metro Android production은 579 modules, raw 1407.1 KiB / gzip 347.0 KiB (pre-Hermes).
|
|
107
|
+
- Calendar medium/large의 최소 날짜 target은 기존 recipe대로 모두 44px다. large는 글자 tier를
|
|
108
|
+
바꾸며 target을 축소하지 않는다. 날짜 주석 영역을 확보해 점 유무에 따른 숫자 높이 차이를 막았다.
|
|
109
|
+
- 잔여 18개 기능/합성, 기존 Field/TextArea·Checkbox·Dialog 품질, minor 게시·모든 JS 소비 앱의
|
|
110
|
+
dependency/lock/계약/projection 적용과 회귀 검증은 계속 진행한다. 버전은 아직 1.1.1이다.
|
|
111
|
+
|
|
112
|
+
- Web 탐색 묶음: Breadcrumb/Pagination을 granular entry로 분리하고 기존 root/navigation
|
|
113
|
+
경로를 유지했다. Anchor는 문서·별도 스크롤 영역, section focus, reduced motion, fragment
|
|
114
|
+
history와 동적 본문 관찰을 구현했다. Web 전용이며 Native wrapper를 추가하지 않았다.
|
|
115
|
+
[Breadcrumb](./breadcrumb.md), [Pagination](./pagination.md), [Anchor](./anchor.md).
|
|
116
|
+
- Patterns/WebNavigation은 125개 기록의 페이지별 목록 교체와 보관함 왕복을 제공한다.
|
|
117
|
+
경로 변경 후 사라진 링크 대신 새 본문으로 focus를 옮기며 초기 mount에서는 focus를 뺏지 않는다.
|
|
118
|
+
Patterns/Anchor는 세 부분의 실제 읽기 가이드에서 목차·스크롤·focus를 연결한다.
|
|
119
|
+
- 탐색 묶음 `pnpm ci:check` exit 0: contracts 733, Web SSR 139 + browser 140,
|
|
120
|
+
Native 268 = 패키지 테스트 1,280개. Showcase Web 19 / Native 1과 타입·문서·evidence·
|
|
121
|
+
번들·정적 Storybook(Web 71 / contract-only 23 / unsupported 0)이 통과했다.
|
|
122
|
+
Native Metro Android는 580 modules, raw 1409.2 KiB / gzip 347.5 KiB (pre-Hermes).
|
|
123
|
+
Web 전용 Anchor 계약이 공통 metadata graph에 추가되어 Metro도 1 module 증가했다.
|
|
124
|
+
- 브라우저 320px·2배 글자·dark·RTL에서 경로와 페이지 버튼 줄바꿈을 확인했다. 마지막 페이지에
|
|
125
|
+
121–125번째 기록이 보이고 상위 보관함으로 왕복한다. Anchor 마지막 목차를 누르면 해당
|
|
126
|
+
본문으로 이동하며 current location·section focus가 일치하고 가로 넘침이 없었다.
|
|
127
|
+
이 결과는 라이브러리 로컬 검증이며 제품 채택·배포·기기 검증을 뜻하지 않는다.
|
|
128
|
+
|
|
129
|
+
- 보조 표면 묶음(Popover): 이름 있는 비모달 dialog, 문맥 form의 초점 지정, 보호된 dismiss,
|
|
130
|
+
자체 소유 중첩 portal, 뷰포트 충돌 시 대체 배치, 나가는 표면의 inert 처리를 구현했다.
|
|
131
|
+
바깥 pointer/Tab이 향한 곳을 그대로 두고, 자식 popover가 감싸는 Dialog보다 Escape를 먼저 받는다.
|
|
132
|
+
[API와 근거](./popover.md). ConfirmPopover는 새 renderer가 아니라 Popover의 확인/취소 조합으로
|
|
133
|
+
제공하고 파괴적 동작은 AlertDialog에 남긴다. [조합 근거](./confirm-popover.md).
|
|
134
|
+
- Patterns/Popover는 필터 적용과 되돌릴 수 있는 보관 두 화면을 제공한다. 보관하면 트리거가
|
|
135
|
+
사라지므로 제품이 초점을 보관 취소 버튼으로 옮기고, 초기 mount에서는 초점을 뺏지 않는다.
|
|
136
|
+
- 묶음 종료 시 두 예산이 실측에서 걸려 근거와 함께 올렸다. contracts 루트 barrel은
|
|
137
|
+
111.8 -> 112.3 kB gzip인데 모듈별로 재면 catalog.js +522 B(Popover·ConfirmPopover maturity
|
|
138
|
+
문구)와 나머지 190 B이고 maxModules는 Anchor 때의 71 그대로다 — 새 import 경로가 아니다.
|
|
139
|
+
`@hjmds/react`의 `./popover` raw 43.0 kB는 마지막 focus/dismiss 보강 전 추정값이라
|
|
140
|
+
실측 44.0 kB에서 걸렸고, 공유 모듈 6개가 그대로여서 다른 entry와 같은 약 8% 여유로 맞췄다.
|
|
141
|
+
검사 생략이나 모듈 증가를 동반한 상향은 없다.
|
|
142
|
+
- Popover 포함 `pnpm ci:check` exit 0: contracts 733, Web SSR 140 + browser 151,
|
|
143
|
+
Native 268 = 패키지 테스트 1,292개. Showcase Web 19 / Native 1, governance 20,
|
|
144
|
+
타입·문서·evidence·번들 경계와 정적 Storybook(canonical 94 = Web renderer 72 /
|
|
145
|
+
contract-only 22 / Web 미지원 0, navigation 13)도 통과했다.
|
|
146
|
+
Metro Android production은 580 modules, raw 1409.2 KiB / gzip 348.1 KiB (pre-Hermes).
|
|
147
|
+
이 실행은 라이브러리 로컬 검증이며 게시·제품 채택·기기 검증이 아니다.
|
|
148
|
+
|
|
149
|
+
- SidePanel(Web): 가장자리에 도킹하는 모달/비모달 보조 패널을 추가했다. 모달은 Dialog·Sheet와
|
|
150
|
+
**같은** 모달 스택·스크롤 락·초점 복귀를 쓰고, 비모달은 스택에 들어가지 않으며 backdrop도
|
|
151
|
+
만들지 않아 뒤 페이지가 계속 클릭·탭 가능하다. 논리 edge가 RTL에서 뒤집히고, dismiss는
|
|
152
|
+
사유 하나를 보고하며 완료 콜백은 한 번만 온다. [계약과 renderer 근거](./side-panel.md).
|
|
153
|
+
- 이를 위해 모달 기계장치를 `packages/react/src/modal.tsx`로 분리했다. `overlays.tsx`에 두면
|
|
154
|
+
SidePanel의 granular entry가 Menu·Tooltip 그래프까지 끌고 오고, 복사하면 `activeModalStack`이
|
|
155
|
+
둘로 갈려 Dialog와 SidePanel이 서로를 최상위로 오인한다. 공개 API는 그대로이며
|
|
156
|
+
`overlays`는 `OverlayTrigger`를 계속 재export한다.
|
|
157
|
+
- 구현 중 실제 결함을 하나 찾았다. 비모달 Escape handler가 effect 시점의 `contentRef`를
|
|
158
|
+
잡아두면 portal이 한 commit 늦게 mount되므로 패널의 첫 키 입력에서 값이 null이다.
|
|
159
|
+
이벤트마다 ref를 읽도록 고쳤고 `test/side-panel.browser.test.tsx`가 이 경로를 검사한다.
|
|
160
|
+
- 브라우저(chromium) 검증 6개: 도킹·초점 가둠·스크롤 락과 flush 모서리, RTL `start` 미러링과
|
|
161
|
+
wide 560px, 비모달의 살아 있는 페이지·내부 Escape만 dismiss, 사유별 단일 보고와 busy 중
|
|
162
|
+
outside 차단, controlled owner의 busy 종료와 1회 완료, 320px·2배 글자에서 가로 넘침 없음.
|
|
163
|
+
`Patterns/SidePanel`은 기록 편집(모달)과 도움말(비모달) 두 화면을 제공한다.
|
|
164
|
+
- 예산은 실측으로 넷을 조정했다. `./side-panel` 45.9 kB raw / 11.0 kB gzip(6 modules),
|
|
165
|
+
`./overlays`는 modal.js 분리로 6→7 modules(81.2/16.7 kB, 의존성 추가 아님),
|
|
166
|
+
루트는 36→38 modules(322.3/66.3 kB, 바이트 한도 그대로), `./styles.css`는 도킹·크기·footer
|
|
167
|
+
규칙 2.1 kB 증가로 105.8 kB raw. `./evidence`는 claim 하나분 메타데이터다.
|
|
168
|
+
- antd 대응표에서 decomposed Drawer의 두 갈래(Sheet·SidePanel)가 모두 구현되며
|
|
169
|
+
partial maturity가 1 → 0이 됐다. fully mature 52 → 53이고 planned only 20은 그대로다.
|
|
170
|
+
- SidePanel 포함 `pnpm ci:check` exit 0: contracts 733, Web SSR 141 + browser 157,
|
|
171
|
+
Native 268 = 패키지 테스트 1,299개. Showcase Web 19 / Native 1, 정적 Storybook은
|
|
172
|
+
canonical 94(Web renderer 73 / contract-only 21 / Web 미지원 0)과 navigation 13이다.
|
|
173
|
+
Metro Android는 580 modules, raw 1409.2 KiB / gzip 348.1 KiB (pre-Hermes) 그대로 —
|
|
174
|
+
Web 전용 추가라 Native 그래프는 늘지 않았다. 게시·제품 채택·기기 검증은 여전히 남아 있다.
|
|
175
|
+
- 잔여 기능/합성은 17개다(Tour, Splitter, CommandPalette, Mentions, TransferList, Cascader,
|
|
176
|
+
TreeSelect, Tree, DataTable, Masonry, VirtualList, QRCode, ColorPicker, Watermark, Affix와
|
|
177
|
+
기존 Field/TextArea·Checkbox·Dialog 품질 항목). 버전은 아직 1.1.1이고 게시 전이다.
|
|
178
|
+
|
|
179
|
+
- Splitter(Web): 두 pane의 경계를 드래그·키보드로 옮긴다. 두 경로 모두 계약의 같은 숫자
|
|
180
|
+
판정을 호출하므로 renderer에는 별도 산술이 없고, RTL에서는 드래그와 방향키가 **함께**
|
|
181
|
+
논리 방향으로 뒤집힌다. pane 축이 아닌 방향키는 pane 콘텐츠의 것으로 남긴다.
|
|
182
|
+
[계약과 renderer 근거](./splitter.md).
|
|
183
|
+
- Splitter 구현 중 실제 결함을 잡았다. `onValueChangeEnd?.(commit(next))`는 handler가
|
|
184
|
+
없으면 optional call이 인자 평가까지 통째로 건너뛰어 키보드 조절이 조용히 죽는다.
|
|
185
|
+
commit을 먼저 하고 알리도록 고쳤고, 경계에서 값이 그대로면 settle로 보고하지 않는다.
|
|
186
|
+
- Tour(Web): 제품이 소유한 opaque anchor 키를 `resolveAnchor`로 넘겨받아 단계 카드를
|
|
187
|
+
붙인다. 초점은 단계마다 카드로 가고(anchor는 inert 배경), veil에는 dismiss handler가
|
|
188
|
+
없어 바깥 pointer로 끝나지 않으며, 마지막 단계의 다음은 `complete`, unmount는
|
|
189
|
+
`interrupted`로 한 번만 정산한다. [계약과 renderer 근거](./tour.md).
|
|
190
|
+
- Tour renderer가 `hjm-tour` 클래스를 소유하면서 Showcase의 옛 목업 CSS 규칙에서 그
|
|
191
|
+
선택자를 제거했다. style-boundary 검사가 이 충돌을 잡아 준다 — DS-10에서 세운 규칙대로
|
|
192
|
+
renderer가 생기면 데모 CSS를 먼저 비운다.
|
|
193
|
+
- 예산은 실측으로 넷을 조정했다. `./splitter` 8.9 kB raw / 2.9 kB gzip(2 modules),
|
|
194
|
+
`./tour` 51.0 kB raw / 12.0 kB gzip(7 modules), 루트는 39→40 modules에 gzip
|
|
195
|
+
66.3 → 69.9 kB(세 renderer 자체 코드, 새 의존성 없음), `./styles.css`는 Splitter·Tour의
|
|
196
|
+
기능 CSS 3.4 kB 증가로 109.2 kB raw다.
|
|
197
|
+
- antd 대응표는 fully mature 55 / partial maturity 0 / planned only 18이 됐다.
|
|
198
|
+
- Splitter·Tour 포함 `pnpm ci:check` exit 0: contracts 733, Web SSR 143 + browser 168,
|
|
199
|
+
Native 268 = 패키지 테스트 1,312개. Showcase Web 19 / Native 1, 정적 Storybook은
|
|
200
|
+
canonical 94(Web renderer 75 / contract-only 19 / Web 미지원 0)과 navigation 13이다.
|
|
201
|
+
Metro Android는 580 modules, raw 1409.2 KiB / gzip 348.1 KiB (pre-Hermes) 그대로다.
|
|
202
|
+
게시·제품 채택·기기 검증은 남아 있다.
|
|
203
|
+
- 잔여 기능/합성은 15개다(CommandPalette, Mentions, TransferList, Cascader, TreeSelect,
|
|
204
|
+
Tree, DataTable, Masonry, VirtualList, QRCode, ColorPicker, Watermark, Affix와 기존
|
|
205
|
+
Field/TextArea·Checkbox·Dialog 품질 항목). 버전은 아직 1.1.1이고 게시 전이다.
|
|
206
|
+
|
|
207
|
+
- CommandPalette(Web): 모달 검색 표면과 결과 실행을 추가했다. 실행은 정책이 거부할 수 없는
|
|
208
|
+
종료 사유이고, 다음 표면을 여는 명령은 팔레트가 사라진 뒤에 실행된다. 여는 단축키는
|
|
209
|
+
제품 소유로 남긴다. [근거](./command-palette.md).
|
|
210
|
+
- DataTable(Web): 정렬 버튼을 header 안에 두고 방향을 `aria-sort`로 흘리며, tri-state
|
|
211
|
+
선택이 disabled 행을 분모에서 뺀다. 표시 전용 표는 기존 `Table`이 그대로 담당한다 —
|
|
212
|
+
둘의 경계를 [문서](./data-table.md)에 적었다.
|
|
213
|
+
- 기존 품질 두 건을 닫았다. Field/TextArea는 오류가 떠도 지원 문구를 계속 보여 주고
|
|
214
|
+
`aria-describedby`를 소비자 값·설명·오류 순으로 병합한다. Dialog에는 Sheet와 같은
|
|
215
|
+
`onDismissComplete`를 넣어 portal·초점 정리가 끝난 시점을 제품이 타이머로 추측하지
|
|
216
|
+
않게 했다. Checkbox는 이미 ref가 input에 닿아 있어 회귀 테스트만 추가했다.
|
|
217
|
+
[감사 항목 마무리](./product-audit-2026-09-15.md).
|
|
218
|
+
|
|
219
|
+
## 남은 planned 행에 대한 판정 (2026-09-18)
|
|
220
|
+
|
|
221
|
+
`Masonry`, `VirtualList`, `QRCode`, `ColorPicker`, `Watermark`, `Affix`는 "아직 못 만든
|
|
222
|
+
것"이 아니다. 각자 `docs/<id>.md`(Masonry는 `docs/catalog-decision-status.md`의 감사표)에
|
|
223
|
+
**검증할 화면이 없다 / 만들지 않는다**는 판정이 이미 적혀 있고, 뒤집히는 조건도 전부
|
|
224
|
+
"실제 제품 화면·엔지니어링 증거가 나타나면"이다. 그 조건이 아직 충족되지 않았으므로
|
|
225
|
+
renderer를 만들지 않고 planned로 남긴다 — 이번 작업에서 판정을 뒤집을 새 근거를 찾지
|
|
226
|
+
못했고, 근거 없이 계약을 여는 것이 이 표준이 금지하는 일이다.
|
|
227
|
+
|
|
228
|
+
따라서 **이 완성 작업의 구현 대상은 모두 닫혔다.** 남은 것은 구현이 아니라 릴리스다:
|
|
229
|
+
minor 게시, 모든 JS 소비 앱의 dependency·lock·계약·projection 갱신과 회귀 검증,
|
|
230
|
+
그리고 제품 우회(BurnTok Toast·Taground 설명 복사·Diairy Notice 등) 제거다.
|
|
231
|
+
Native renderer가 없는 Web 전용/Web 우선 항목(TransferList·Mentions의 RN 쪽 포함)은
|
|
232
|
+
catalog의 surface 행렬에 그대로 남아 있다.
|
|
233
|
+
|
|
234
|
+
## 마지막 전체 검증 (2026-09-18)
|
|
235
|
+
|
|
236
|
+
`pnpm ci:check` exit 0: contracts 733, Web SSR 148 + browser 198, Native 268 =
|
|
237
|
+
패키지 테스트 1,347개. Showcase Web 19 / Native 1, governance 20,
|
|
238
|
+
정적 Storybook은 canonical 94(Web renderer 80 / contract-only 14 / Web 미지원 0)과
|
|
239
|
+
navigation 13이다. Showcase 토큰 경계는 66개 선언·66개 문서화된 예외다.
|
|
240
|
+
Metro Android production은 580 modules, raw 1409.2 KiB / gzip 348.1 KiB (pre-Hermes)로
|
|
241
|
+
이번 추가가 전부 Web 전용이라 Native 그래프는 늘지 않았다.
|
|
242
|
+
|
|
243
|
+
contracts 루트 barrel은 478.4 kB raw / 113.1 kB gzip이며 maxModules는 71 그대로다 —
|
|
244
|
+
이번에 늘어난 것은 catalog의 maturity 문구뿐이고 새 import 경로는 없다. `@hjmds/react`
|
|
245
|
+
루트는 36 → 45 modules(370.8 kB raw / 77.4 kB gzip), `styles.css`는 119.5 kB raw /
|
|
246
|
+
18.7 kB gzip이다. 모든 예산 조정에 실측값과 근거를 주석으로 남겼고 검사 생략은 없다.
|
|
247
|
+
|
|
248
|
+
이 결과는 라이브러리 로컬 검증이다. npm 게시, 제품 채택, 실제 기기·보조기기 검증은
|
|
249
|
+
아직 하지 않았다.
|
|
250
|
+
|
|
251
|
+
## 커버리지 감사 후속 — P0 (2026-09-18)
|
|
252
|
+
|
|
253
|
+
메타 저장소의 커버리지 감사(`app-portfolio` 저장소 `docs/design/HJM_COVERAGE_AUDIT_2026-09-18.md`)가
|
|
254
|
+
"새 제품이 첫 주에 막히는 것"으로 꼽은 항목을 닫았다. `@hjm/icons`는 외부 아이콘
|
|
255
|
+
라이브러리를 쓰기로 한 사용자 결정(2026-09-18)에 따라 범위에서 뺐다.
|
|
256
|
+
|
|
257
|
+
- **Agreement**(Web/RN): 전체 동의는 항목의 합에서 파생하고 저장되지 않는다. 필수 항목만
|
|
258
|
+
제출 가능 여부를 정하고, 필수이면서 비활성인 항목은 descriptor가 거절한다 — 사용자가
|
|
259
|
+
영원히 진행할 수 없는 화면이 조용히 만들어지는 것을 막는다. 전문 보기는 체크박스와
|
|
260
|
+
다른 tab stop이고 눌러도 동의되지 않는다. [계약](./agreement.md).
|
|
261
|
+
- **Top**(Web/RN): 화면 본문의 첫 제목 블록. 고정 크롬인 TopBar와 분리했고 실제 heading
|
|
262
|
+
요소를 낸다. BurnTok `AppScreenHeader`·Taground `screen-shell`·Diairy `DetailPanel`이
|
|
263
|
+
각자 만들던 자리다. [계약](./top.md).
|
|
264
|
+
- **AuthProviderButton**(Web/RN): 소셜 로그인 버튼. 색은 각 제공자 가이드라인 값이고
|
|
265
|
+
테마는 제공자 자신의 light/dark 변형 중 하나를 고를 뿐이다. 로고 자산과 문구는 제품이
|
|
266
|
+
넘긴다 — BurnTok이 `.hjm-button.bt-provider-*` CSS 네 줄로 덮던 자리를 계약으로 올렸다.
|
|
267
|
+
[계약](./provider-button.md).
|
|
268
|
+
- **테마 주입 문서**: [theming.md](./theming.md). 메커니즘(`brandPalette`)은 이미 있었고
|
|
269
|
+
문서가 없었다. BurnTok의 실제 사용을 근거로 "덮어도 되는 key"와 "하지 말아야 할 세 가지"를
|
|
270
|
+
적었다. 예제 API는 실행해서 확인했다.
|
|
271
|
+
- P0 포함 `pnpm ci:check` exit 0: contracts 747, Web SSR 151 + browser 207, Native 271.
|
|
272
|
+
정적 Storybook canonical 97(Web renderer 83 / contract-only 14 / 미지원 0).
|
|
273
|
+
Metro Android 586 modules, raw 1423.9 KiB / gzip 351.6 KiB (pre-Hermes).
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## 커버리지 감사 후속 — P1·P2 마무리와 Native 공백 (2026-09-18)
|
|
277
|
+
|
|
278
|
+
P0에 이어 감사의 P1·P2를 전부 닫고, Web 전용으로 남아 있던 네 개에 Native renderer를
|
|
279
|
+
붙였다. 순서는 ①P1 ②P2 ③P2-b(데스크톱 메뉴·disclosure) ④P2-c(토큰·정책) ⑤Native다.
|
|
280
|
+
|
|
281
|
+
### P1 — 화면 하나를 끝까지 만들게 하는 것
|
|
282
|
+
|
|
283
|
+
Heading(Web/RN), 원형 Progress(Web/RN), ListRow 로딩 행과 `relaxed`·`spacious` 밀도(Web),
|
|
284
|
+
ToggleGroup(Web/RN), TagsInput(Web), SkipNav(Web), BottomInfo(Web/RN), Sidebar(Web),
|
|
285
|
+
Sheet detent + 핸들(Web), DateRangePicker(Web), `useDialog`/`useSheet`(Web), 로케일 필수
|
|
286
|
+
formatters, RN 키보드 회피·햅틱 계약.
|
|
287
|
+
|
|
288
|
+
두 가지는 **감사가 틀렸다.** heading 스케일은 이미 있었고 노출만 없었다. Tabs의
|
|
289
|
+
`size`·`overflow`도 이미 공개 prop이었다 — 제품의 CSS 우회는 API 공백이 아니라 시각적 선택이다.
|
|
290
|
+
|
|
291
|
+
### P2 — TextFormat, Clipboard, CounterBadge dot, AvatarGroup
|
|
292
|
+
|
|
293
|
+
`Kbd`/`Code`/`Blockquote`를 "상호작용이 없어서 채택하지 않는다"고 적어 두었던 판정을
|
|
294
|
+
뒤집었다. 문제는 상호작용이 아니라 **같은 표현이 제품마다 다른 요소로 그려지는 것**이었고,
|
|
295
|
+
그 요소 선택이 계약의 값이다.
|
|
296
|
+
|
|
297
|
+
### P2-b — Collapsible · ContextMenu · Menubar · Popover hover
|
|
298
|
+
|
|
299
|
+
- **Collapsible**(Web/RN): Accordion과 다른 것은 개수가 아니라 **관계**다. 이웃이 없으니
|
|
300
|
+
그룹 키보드·구분선·"하나만 열림" 정책이 없다. 닫힌 내용은 숨기지 않고 트리에서 뺀다.
|
|
301
|
+
- **ContextMenu**(Web): Menu와 목록은 같고 **앵커가 좌표**다. 트리거가 없으므로 Shift+F10과
|
|
302
|
+
메뉴 키를 반드시 준다 — 그것을 빼면 마우스 없이는 존재하지 않는 기능이다. 키보드로 열면
|
|
303
|
+
초점 요소의 상자를 앵커로 쓴다.
|
|
304
|
+
- **Menubar**(Web): 막대 전체가 **키보드 단위 하나**다. 열린 상태에서 ←/→가 옆 메뉴로
|
|
305
|
+
넘어가고, 한 번에 하나만 열리며, tab stop은 하나다. Menu 세 개로는 성립하지 않는다.
|
|
306
|
+
- **Popover `openOn="hover"`**: 클릭 경로를 대체하지 않고 **더한다**. 터치와 키보드에는
|
|
307
|
+
hover가 없다. 양쪽 지연(300/150ms)이 있어야 스쳐 지나가도 안 열리고 패널까지 갈 수 있다.
|
|
308
|
+
|
|
309
|
+
### P2-c — Asset · Chart 토큰 · 전역 density · 버튼 라벨 정책
|
|
310
|
+
|
|
311
|
+
- **Asset**(Web/RN): 아이콘·이미지·Lottie·비디오를 같은 액자에 넣는다. **재생기는 슬롯으로
|
|
312
|
+
받아** 이 패키지가 의존하지 않는다. [asset.md](./asset.md).
|
|
313
|
+
- **Chart**: 렌더러를 만들지 않는다. 계열 팔레트·축·격자·범례 토큰(`dataviz`)만 고정하고
|
|
314
|
+
그리기는 제품 라이브러리에 위임한다. 실제로 어긋난 것은 색이었다. [chart.md](./chart.md).
|
|
315
|
+
- **전역 density**: Provider의 한 축이 목록·메뉴·표의 기본 밀도가 된다. 컴포넌트 prop이
|
|
316
|
+
언제나 이긴다. 어휘는 통일하지 않고 매핑 함수를 한 곳에 뒀다. [density.md](./density.md).
|
|
317
|
+
- **버튼 라벨 wrap**: 자르지 않고 두 줄까지 접는다. 큰 글자에서는 상한을 푼다(WCAG 1.4.4).
|
|
318
|
+
[button-label.md](./button-label.md).
|
|
319
|
+
|
|
320
|
+
### Native 공백 네 개
|
|
321
|
+
|
|
322
|
+
TagsInput · DateRangePicker · Mentions · TransferList에 RN renderer가 들어왔다. 네 곳 모두
|
|
323
|
+
계약 판정을 그대로 쓰고, **표면이 다른 지점만** 따로 적었다.
|
|
324
|
+
|
|
325
|
+
- TagsInput: 확정 키 어휘가 return 하나뿐이다. Comma/Space/Blur는 Web 전용으로 남는다.
|
|
326
|
+
- DateRangePicker: hover 미리보기가 없다. 구간은 날짜 이름과 점으로만 드러난다.
|
|
327
|
+
- Mentions: 캐럿을 `onSelectionChange`로만 알 수 있어 따로 추적한다. `value.length`로
|
|
328
|
+
추측하면 문장 중간 편집이 전부 깨진다.
|
|
329
|
+
- TransferList: 두 칸을 나란히 놓을 폭이 없어 세로로 쌓는다. 읽기 순서가 "목록 → 무엇을
|
|
330
|
+
할지 → 다른 목록"이 된다.
|
|
331
|
+
|
|
332
|
+
### 마지막 전체 검증
|
|
333
|
+
|
|
334
|
+
`pnpm ci:check` exit 0: contracts 793, Web SSR 163 + browser 239, Native 280.
|
|
335
|
+
정적 Storybook canonical 110(Web renderer 95 / contract-only 15 / 미지원 0), navigation 13.
|
|
336
|
+
Showcase 토큰 경계는 68개 선언·68개 문서화된 예외다.
|
|
337
|
+
Metro Android production은 families=37 / modules=611 / raw 1475.9 KiB / gzip 364.9 KiB
|
|
338
|
+
(pre-Hermes, headroom 9.8% raw / 9.7% gzip)로, 이번에 Native renderer가 여섯 개 늘어난 만큼
|
|
339
|
+
그래프도 늘었다.
|
|
340
|
+
|
|
341
|
+
예산은 전부 실측값으로 올렸고 근거를 주석으로 남겼다. 하나는 특히 의식적인 선택이다:
|
|
342
|
+
`@hjmds/react`의 `./data-table`이 전역 density를 읽으면서 provider 모듈을 그래프에
|
|
343
|
+
끌어와 2 → 4 모듈(2.6 → 5.2 kB gzip)이 됐다. provider 예산의 경고를 알고 올렸다 —
|
|
344
|
+
제품은 어차피 `HjmProvider`를 항상 싣고, 대신 얻는 것이 목록·메뉴·표의 같은 밀도다.
|
|
345
|
+
|
|
346
|
+
이 결과는 라이브러리 로컬 검증이다. npm 게시, 제품 채택, 실제 기기·보조기기 검증은
|
|
347
|
+
아직 하지 않았다.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 화면 제목과 마지막 행동
|
|
2
|
+
|
|
3
|
+
2026-09-16: 앱 감사에서 Native 전용 TopBar/BottomCTA 때문에 같은 제품의 Web에
|
|
4
|
+
별도 헤더와 푸터가 생기는 공백을 확인했다. 기존 Native recipe를 React에 연결한다.
|
|
5
|
+
TDS의 [ListRow](https://tossmini-docs.toss.im/tds-mobile/components/ListRow/list-row-overview/)와
|
|
6
|
+
[BottomCTA](https://tossmini-docs.toss.im/tds-mobile/components/BottomCTA/check-first/)에서
|
|
7
|
+
제목/본문/보조 정보의 위계, 하나의 강한 주 행동을 참조한다. 색·형상·간격은 HJM 소유다.
|
|
8
|
+
|
|
9
|
+
## 공개 경로와 경계
|
|
10
|
+
|
|
11
|
+
| 구성 | React | React Native |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| 제목 | `@hjmds/react/top-bar` | `@hjmds/react-native/top-bar` |
|
|
14
|
+
| 마지막 행동 | `@hjmds/react/bottom-cta` | `@hjmds/react-native/bottom-cta` |
|
|
15
|
+
| 행동 이벤트 | `onClick` | `onPress` |
|
|
16
|
+
| 위치 | 기본 flow, 선택적 sticky | 제품의 화면 레이아웃이 배치 |
|
|
17
|
+
|
|
18
|
+
Native의 기존 `navigation`/`actions` 경로도 유지한다. 새 경로는 그 모듈의 alias이므로
|
|
19
|
+
기능별 파일 분리나 번들 감소를 의미하지 않는다. manifest 변경 이유는 Web/RN에서
|
|
20
|
+
같은 기능을 찾기 쉽게 하면서 기존 import를 깨지 않기 위해서다.
|
|
21
|
+
|
|
22
|
+
TopBar는 leading, title, trailing/actions 슬롯을 받는다. title 클릭은 실제 버튼,
|
|
23
|
+
이동은 슬롯의 Link로 표현한다. actions와 trailing을 동시에 전달하면 거절한다.
|
|
24
|
+
Web의 headingLevel은 페이지 구조가 정한다. div root이므로 Dialog 안에 넣어도
|
|
25
|
+
두 번째 banner landmark를 만들지 않는다. `centered`는 남은 제목 영역 내 정렬이다.
|
|
26
|
+
짧은 행동 문구를 압축하던 동일 폭 좌우 열 대신 콘텐츠 폭을 보장하고 제목이 줄바꿈한다.
|
|
27
|
+
큰 글자에서는 제목을 다음 행으로 내려 행동과 겹치지 않게 한다.
|
|
28
|
+
|
|
29
|
+
BottomCTA는 primaryAction 하나와 선택적인 secondaryAction, description을 받는다.
|
|
30
|
+
loading은 표시 문구의 폭을 유지하고 중복 실행을 막는다. 큰 글자에서 세로로 쌓을 때
|
|
31
|
+
가로 배치용 flex-basis를 해제한다. 가로 배치 값이 세로 높이로 해석되어 거대한 공백을
|
|
32
|
+
만들었던 320px 브라우저 재현이 근거다. 간격은 Native와 같은 `bottomCtaRecipe.gap`이다.
|
|
33
|
+
|
|
34
|
+
Web sticky는 문서 흐름 안에 자리를 유지한다. fixed overlay는 본문 spacer 측정이
|
|
35
|
+
추가로 필요하므로 제공하지 않는다. safeAreaTop/Bottom은 유한한 0 이상 숫자이며
|
|
36
|
+
브라우저 env inset과 명시적 inset 중 큰 값을 쓴다. 키보드가 열린 제품 화면의 실제
|
|
37
|
+
viewport 처리는 제품 QA에 남는다. 버튼 그룹은 일반 Tab 순서이며 toolbar가 아니다.
|
|
38
|
+
|
|
39
|
+
## 검증
|
|
40
|
+
|
|
41
|
+
`packages/react/test/screen-chrome.browser.test.tsx`는 320/390px, 1/2배 글자,
|
|
42
|
+
LTR/RTL, light/dark에서 hit target, 글자 범위, overflow, 제목 분리와 footer 간격을
|
|
43
|
+
검증한다. ref/heading/link, busy 중 중복 실행 방지와 폭/focus 보존, sticky footer 위
|
|
44
|
+
마지막 본문 행동의 도달 가능성도 검사한다. 기본 SSR renderer fixture를 추가한다.
|
|
45
|
+
|
|
46
|
+
`Patterns/Notification settings`는 Web/RN에서 동일 문구·선택·저장 상태를 합성한다.
|
|
47
|
+
행 제목과 스위치 문구가 중복되면 `labelVisibility="hidden"`을 사용한다. `label`은
|
|
48
|
+
계속 필수이며 Web의 visually-hidden 텍스트와 Native의 accessibilityLabel로 남는다.
|
|
49
|
+
Web List의 indented 구분선은 행 margin이 아닌 별도 선으로 그린다. 320px 조합에서
|
|
50
|
+
두 번째 제목부터 들여쓰기되던 재현을 고쳤고 Native의 기존 선 전용 inset과 맞췄다.
|
|
51
|
+
Storybook의 조작과 브라우저 검사는 Native 기기의 VoiceOver/TalkBack 검증을 대체하지 않는다.
|
|
52
|
+
catalog beta는 구현 지원 범위이며 stable 또는 앱 배포 완료 선언이 아니다.
|
package/docs/side-panel.md
CHANGED
|
@@ -67,5 +67,26 @@ target은 `control.minTouchTarget`(44) 이상을 유지한다.
|
|
|
67
67
|
버튼 클릭(`close-action`)은 renderer가 직접 `requestClose`를 호출하는 일반
|
|
68
68
|
동작이라 "플랫폼이 감지하는 중단 벡터" 목록에 넣지 않는다.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
## Web renderer (2026-09-18)
|
|
71
|
+
|
|
72
|
+
`@hjmds/react/side-panel`의 `SidePanel`이 위 계약을 실행한다. catalog는 Web `beta`,
|
|
73
|
+
Native `unsupported`다. 제품 채택·보조기기 실측은 아직 없으므로 `stable`이 아니다.
|
|
74
|
+
|
|
75
|
+
- **modal만 모달이다.** `modal: true`는 Dialog·Sheet와 **같은** 모달 스택에 들어가
|
|
76
|
+
초점을 가두고 body 스크롤을 잠근다. 이를 위해 그 기계장치를 `packages/react/src/modal.tsx`로
|
|
77
|
+
꺼냈다. 복사했다면 `activeModalStack`이 둘로 갈려 Dialog와 SidePanel이 서로를
|
|
78
|
+
최상위로 오인한다. `modal: false`는 스택에 들어가지 않고 backdrop 요소 자체를
|
|
79
|
+
렌더링하지 않는다 — 뒤 페이지가 계속 클릭·탭 가능하다.
|
|
80
|
+
- **비모달의 Escape는 패널 안에서만 듣는다.** 살아 있는 페이지가 자기 Escape를
|
|
81
|
+
그대로 쓰기 때문이다. 문서 전역 handler는 `contentRef`를 **이벤트마다** 읽는다 —
|
|
82
|
+
portal은 effect보다 한 commit 늦게 mount되므로 effect 시점에 잡아둔 값은 첫 키 입력에서
|
|
83
|
+
아직 null이다.
|
|
84
|
+
- **`dismissPolicy`는 `Partial<>`이 아니라 전체 union이다.** `Partial<SidePanelDismissPolicy>`는
|
|
85
|
+
두 갈래를 선택 필드로 합쳐 `{ modal: false, outsideDismiss: true }`를 다시 컴파일
|
|
86
|
+
가능하게 만든다. 계약이 타입으로 막은 조합을 renderer가 열어 주지 않는다.
|
|
87
|
+
- **`onDismissComplete`는 한 번만 부른다.** Sheet의 lifecycle counter는 두지 않았다(위 4번).
|
|
88
|
+
Web에서는 닫은 render와 함께 unmount되므로 reduced motion에서도 기다릴 exit transition이
|
|
89
|
+
없다. 사용자 동작 없이 owner가 닫으면 `programmatic`으로 보고한다.
|
|
90
|
+
- 로컬 검증: `test/side-panel.browser.test.tsx` 6개(도킹·초점 가둠·스크롤 락, RTL `start`
|
|
91
|
+
미러링과 폭, 비모달의 살아 있는 페이지와 내부 Escape, 사유별 단일 보고와 busy 차단,
|
|
92
|
+
controlled owner의 busy 종료와 1회 완료, 320px·2배 글자 배치)와 `Patterns/SidePanel`.
|
package/docs/sidebar.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Sidebar contract
|
|
2
|
+
|
|
3
|
+
**문제.** 데스크톱 웹의 세로 내비게이션 — 관리자 화면, 문서, 작업 도구처럼 넓은 화면의
|
|
4
|
+
왼쪽. 커버리지 감사에서 "데스크톱 앱 셸이 없다"로 꼽힌 자리다.
|
|
5
|
+
|
|
6
|
+
**BottomNavigation과 합치지 않는다.** 두 축이 다르다.
|
|
7
|
+
|
|
8
|
+
| | BottomNavigation | Sidebar |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| 항목 수 | 3~5개 최상위 목적지 | 그룹이 있는 긴 목록 |
|
|
11
|
+
| 그룹 | 없음 | 있음(제목 있는 묶음) |
|
|
12
|
+
| 밀도 | 고정 | 접힘 |
|
|
13
|
+
| 표면 | 모바일 하단 고정 | 데스크톱 좌측 |
|
|
14
|
+
|
|
15
|
+
`orientation` 축으로 합치면 "그룹이 있는 하단 탭바"라는 존재하지 않는 조합이 타입에 생긴다.
|
|
16
|
+
|
|
17
|
+
**Layout과도 다르다.** Layout은 **자리**(폭·순서·반응형)를 갖고 내용을 모른다. 이 계약은
|
|
18
|
+
그 자리에 들어가는 내비게이션 자체다.
|
|
19
|
+
|
|
20
|
+
**접힘은 밀도이지 내용이 아니다.** 접혀도 항목은 전부 남고 라벨만 숨는다. 접을 때 항목을
|
|
21
|
+
빼면 "접었더니 메뉴가 사라졌다"가 되고 그건 다른 컴포넌트다. 라벨이 숨으면 아이콘만으로는
|
|
22
|
+
이름을 알 수 없으므로 renderer가 `aria-label`로 접근성 이름을 유지하고, 배지는 접힌
|
|
23
|
+
상태에서도 남는다 — 배지가 곧 그 항목을 봐야 할 이유이기 때문이다.
|
|
24
|
+
|
|
25
|
+
**현재 위치는 색이 아니라 `aria-current="page"`가 말한다.**
|
|
26
|
+
|
|
27
|
+
**tab stop.** 각 항목이 링크이고 문서 순서대로 놓인다. 화살표 grid 탐색을 도입하지 않는다.
|
|
28
|
+
|
|
29
|
+
**Web 전용.** 폰은 BottomNavigation을 쓰고, 네이티브 태블릿의 split view는 navigator의
|
|
30
|
+
문제다.
|
package/docs/skip-nav.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SkipNav contract
|
|
2
|
+
|
|
3
|
+
**문제.** 반복되는 머리 부분을 건너뛰고 본문으로 바로 가는 링크. WCAG 2.4.1(Bypass
|
|
4
|
+
Blocks)이 요구하고, 키보드·스위치 사용자가 매 화면에서 내비게이션 20개를 지나지 않게 한다.
|
|
5
|
+
|
|
6
|
+
**`VisuallyHidden`으로 만들면 절반이 빠진다.** 이 링크는 숨어 있다가 **포커스를 받으면
|
|
7
|
+
반드시 보여야** 한다. 계속 숨겨 두면 포커스가 화면 밖으로 사라지는 것과 같아 WCAG
|
|
8
|
+
2.4.7(Focus Visible)을 어긴다. "숨었다가 포커스에서 나타난다"가 이 계약의 전부다.
|
|
9
|
+
|
|
10
|
+
**해시만으로는 부족하다.** `#main`은 스크롤만 옮긴다. 초점은 방금 건너뛴 내비게이션에
|
|
11
|
+
남아 있어서 다음 Tab이 그 링크들로 돌아간다. 그래서 renderer가 대상에 `tabindex="-1"`을
|
|
12
|
+
붙이고 실제로 focus를 옮긴다.
|
|
13
|
+
|
|
14
|
+
**`#`가 붙은 targetId는 거절한다.** 잘라내 주면 `#main`과 `main` 두 표기가 섞이고 어느
|
|
15
|
+
쪽이 맞는지 아무도 모르게 된다.
|
|
16
|
+
|
|
17
|
+
**첫 tab stop이어야 한다.** 제품이 문서 맨 앞에 두지 않으면 아무것도 해결하지 못한다 —
|
|
18
|
+
그 배치는 renderer가 강제할 수 없으므로 계약의 scenario로 적어 둔다.
|
|
19
|
+
|
|
20
|
+
**Web 전용.** 네이티브에는 건너뛸 문서 흐름이 없고 화면 리더가 rotor로 이동한다.
|
package/docs/splitter.md
CHANGED
|
@@ -51,5 +51,25 @@
|
|
|
51
51
|
PageUp/PageDown(10배 이동)은 넣지 않았다 — 분할 패널 크기 조정은 그 정도로 큰
|
|
52
52
|
점프가 필요하다는 요구가 측정되지 않았다.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
## Web renderer (2026-09-18)
|
|
55
|
+
|
|
56
|
+
`@hjmds/react/splitter`의 `Splitter`가 이 계약을 실행한다. catalog는 Web `beta`,
|
|
57
|
+
Native `unsupported`다. 제품 채택·보조기기 실측은 아직 없다.
|
|
58
|
+
|
|
59
|
+
- **드래그와 키보드가 같은 값을 만든다.** 둘 다 계약의 `resolveSplitterDragValue`/
|
|
60
|
+
`getNextSplitterValue`를 호출하므로 renderer에 별도 숫자 산술이 없다.
|
|
61
|
+
- **방향은 논리 기준이다.** RTL에서는 primary pane이 오른쪽에서 자라므로 포인터 거리를
|
|
62
|
+
그 가장자리에서 재고, 같은 반전을 방향키에도 적용한다(RTL에서 ArrowLeft가 increment).
|
|
63
|
+
둘 중 하나만 뒤집으면 드래그와 키보드가 서로 반대로 움직인다.
|
|
64
|
+
- **pane 축이 아닌 방향키는 건드리지 않는다.** 가로 splitter의 위/아래 키는 pane 안
|
|
65
|
+
콘텐츠의 것이다.
|
|
66
|
+
- **경계에서의 step은 settle이 아니다.** 값이 그대로면 `onValueChangeEnd`를 부르지 않는다 —
|
|
67
|
+
크기를 저장하는 owner가 의미 없는 쓰기를 하지 않도록.
|
|
68
|
+
- 구현 중 실제 결함을 하나 잡았다: `onValueChangeEnd?.(commit(next))`는 handler가 없으면
|
|
69
|
+
인자 평가까지 통째로 건너뛰어 키보드 조절이 조용히 죽는다. commit을 먼저 하고 알린다.
|
|
70
|
+
- 로컬 검증: `test/splitter.browser.test.tsx` 5개(separator 의미·수직 방향과 44px hit
|
|
71
|
+
target, 방향키 step과 Home/End 경계, 드래그 스냅과 드래그당 1회 end, RTL 드래그·키보드,
|
|
72
|
+
disabled)와 `Patterns/Splitter`.
|
|
73
|
+
|
|
74
|
+
**검증 화면.** 제품 vertical slice는 아직 없다. `beta`는 renderer 수준 증거를 뜻하며
|
|
75
|
+
실제 화면 채택은 별도로 기록한다.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# stable 승격 절차
|
|
2
|
+
|
|
3
|
+
2026-09-18 기준 `stable`은 네 개다: Surface, Button, Field, TextArea. 나머지 90여 개
|
|
4
|
+
Web renderer는 `beta`이고, 그 뜻은 **"renderer는 있고 제품 실측은 없다"**이다. 커버리지
|
|
5
|
+
감사가 "컴포넌트 수가 아니라 이 숫자가 배포 준비를 막는다"고 지적한 자리다.
|
|
6
|
+
|
|
7
|
+
이 문서는 그 숫자를 움직이는 **조건과 순서**만 정한다. 승격 자체는 증거가 모인 뒤에 한다.
|
|
8
|
+
|
|
9
|
+
## 왜 자동으로 올리지 않는가
|
|
10
|
+
|
|
11
|
+
`beta → stable`은 "이 API를 바꾸지 않겠다"는 약속이다. 테스트가 초록이라는 것은 우리가
|
|
12
|
+
작성한 시나리오를 통과했다는 뜻이지, 실제 사용자가 그 화면을 통과했다는 뜻이 아니다.
|
|
13
|
+
정적 검사로 승격하면 그 약속의 근거가 우리 자신의 테스트뿐이게 된다.
|
|
14
|
+
|
|
15
|
+
## 승격 조건 (네 가지 전부)
|
|
16
|
+
|
|
17
|
+
1. **제품 채택** — 등록된 제품 최소 한 곳의 실제 화면에서 쓰이고, 그 화면이 배포돼 있다.
|
|
18
|
+
Showcase 사용은 채택이 아니다.
|
|
19
|
+
2. **우회 없음** — 그 제품이 이 컴포넌트를 `.hjm-*` CSS로 덮거나 recipe 값을 다시 읽어
|
|
20
|
+
인라인 style로 싣고 있지 않다. 우회가 있으면 그것이 곧 API 공백이고, 공백이 있는 API를
|
|
21
|
+
고정하면 안 된다.
|
|
22
|
+
3. **보조기기 실측** — 해당 표면에서 한 번 이상 실제 확인. Web은 화면 리더 1종 + 키보드
|
|
23
|
+
전용 통과, Native는 VoiceOver 또는 TalkBack 통과. 기록은 제품 저장소에 남긴다.
|
|
24
|
+
4. **환경 4종** — dark, RTL, 2배 글자, reduced motion에서 그 **제품 화면**이 깨지지 않는다.
|
|
25
|
+
(renderer evidence의 시나리오와 별개다. 그쪽은 우리 스토리, 이쪽은 실제 화면이다.)
|
|
26
|
+
|
|
27
|
+
## 첫 후보 (다음 minor 게시 이후)
|
|
28
|
+
|
|
29
|
+
BurnTok·Diairy가 실제로 소비 중이고 우회가 남아 있지 않은 것부터 본다.
|
|
30
|
+
|
|
31
|
+
| 후보 | 채택처 | 남은 확인 |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| ListRow | BurnTok, Diairy, Taground | 우회 제거 후 재확인, 보조기기 |
|
|
34
|
+
| Dialog | BurnTok(AppModal) | `onDismissComplete` 소비 후 0ms 타이머 제거 |
|
|
35
|
+
| Sheet | BurnTok(AppSheet) | 같음 |
|
|
36
|
+
| Toast | BurnTok | DS-01 우회 제거 |
|
|
37
|
+
| Notice | Diairy | DS-11 우회 제거 |
|
|
38
|
+
| Tabs | BurnTok | compact 우회가 제품 선택인지 API 공백인지 판정 |
|
|
39
|
+
| TextField | BurnTok, Diairy | 설명·오류 동시 표시 변경 반영 확인 |
|
|
40
|
+
| Select · Checkbox · Switch | 3개 제품 | 보조기기 |
|
|
41
|
+
|
|
42
|
+
**순서**: minor 게시 → 소비 앱 dependency·lock 갱신 → 우회 제거 → 보조기기·환경 확인 →
|
|
43
|
+
증거를 제품 저장소에 기록 → 이 표를 근거로 승격 PR.
|
|
44
|
+
|
|
45
|
+
## 승격하지 않는 것
|
|
46
|
+
|
|
47
|
+
- 제품이 아직 쓰지 않는 컴포넌트(이번에 추가한 Sidebar·TagsInput·Agreement 등). renderer가
|
|
48
|
+
있다는 것과 안심하고 쓸 수 있다는 것은 다른 말이다.
|
|
49
|
+
- Native가 `planned`인 채로 Web만 성숙한 항목의 **contract status**. `status`는 계약
|
|
50
|
+
성숙도이고 표면별 성숙도는 `surfaceStatus`가 따로 갖는다 — 한쪽 표면만으로 계약을
|
|
51
|
+
stable로 올리면 다른 표면이 조용히 약속에 포함된다.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# TagsInput contract
|
|
2
|
+
|
|
3
|
+
**문제.** 자유 입력으로 여러 값을 모으는 필드 — 해시태그, 초대할 사람, 검색 필터.
|
|
4
|
+
|
|
5
|
+
**Combobox와의 경계.** Combobox는 **주어진 목록에서 고른다**. TagsInput은 목록이
|
|
6
|
+
없거나, 있어도 **목록 밖의 값을 만들 수 있다**. Combobox에 `multiple` 축을 붙이면
|
|
7
|
+
"목록에 없는 값을 고를 수 있는 Select"라는 모순이 생긴다. 후보 목록을 함께 보여 주고
|
|
8
|
+
싶은 제품은 이 필드와 Combobox의 listbox를 조합한다 — 그것은 합성이지 모드가 아니다.
|
|
9
|
+
|
|
10
|
+
**이 계약이 소유하는 두 가지.**
|
|
11
|
+
|
|
12
|
+
1. **언제 확정되는가.** 기본 확정 키는 `Enter` 하나다. 쉼표·공백을 기본으로 넣지 않은
|
|
13
|
+
이유는 그 문자가 언어에 따라 값의 일부이기 때문이다(한국어 태그에 공백이 흔하다).
|
|
14
|
+
제품이 자기 도메인을 알 때 `commitKeys`로 켠다.
|
|
15
|
+
2. **빈 입력에서 지우면 무엇이 지워지는가.** 첫 Backspace는 마지막 태그를 **고르기만**
|
|
16
|
+
한다. 한 번 더 눌러야 지워진다. 한 번에 지우면 키 반복으로 태그가 조용히 사라진다.
|
|
17
|
+
|
|
18
|
+
**판정은 여기, 문장은 제품.** 중복·최대 개수·글자 규칙은 `TagsInputPolicy`로 받고,
|
|
19
|
+
거절되면 `reason`(`empty`/`duplicate`/`limit`/`invalid`)만 돌려준다. "이미 있어요" 같은
|
|
20
|
+
문장은 제품이 쓴다.
|
|
21
|
+
|
|
22
|
+
**삭제는 위치 기준이다.** 중복을 허용한 제품에서 값 기준으로 지우면 엉뚱한 것이 사라진다.
|
|
23
|
+
|
|
24
|
+
**Native.** 아직 renderer가 없다(catalog `planned`). 자유 입력 + 칩 목록은 키보드
|
|
25
|
+
회피·IME와 함께 검증해야 해서 Web 먼저 낸다.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# TextFormat contract
|
|
2
|
+
|
|
3
|
+
**문제.** 단축키(`⌘S`), 코드(`pnpm add …`), 인용 — 도움말·개발자 문서·약관 본문이 반복해서
|
|
4
|
+
필요로 하는 글자 조각들. 지금까지는 제품이 `<kbd>`에 직접 CSS를 붙였다.
|
|
5
|
+
|
|
6
|
+
**왜 `Text`의 variant가 아닌가.** 이들은 **크기가 아니라 요소**다. `<kbd>`, `<code>`,
|
|
7
|
+
`<blockquote>`는 각자 의미를 가진 HTML 요소이고 보조기기가 다르게 읽는다. variant로 두면
|
|
8
|
+
`<span>`에 코드처럼 보이는 스타일만 입히게 되고 그 차이가 사라진다. 그래서 `kind`가
|
|
9
|
+
크기 축이 아니라 요소 축이다.
|
|
10
|
+
|
|
11
|
+
**Native는 미지원이다.** RN에는 대응하는 의미 요소가 없다. `Text`에 monospace 글꼴만
|
|
12
|
+
입히는 것은 같은 것이 아니므로, 이름만 같은 빈 wrapper를 만들지 않는다.
|
|
13
|
+
|
|
14
|
+
**키 이름은 제품 문구다.** 같은 키가 플랫폼마다 다르게 불린다(⌘ / Ctrl). renderer가
|
|
15
|
+
정하지 않는다.
|