@designbasekorea/ui-wc 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +45 -0
  4. package/README.md +13 -5
  5. package/THIRD-PARTY-NOTICES.md +5 -0
  6. package/dist/esm/components/db-ad-banner.d.ts +6 -0
  7. package/dist/esm/components/db-ad-banner.js +1 -1
  8. package/dist/esm/components/db-animation-background.d.ts +7 -0
  9. package/dist/esm/components/db-animation-background.js +1 -1
  10. package/dist/esm/components/db-avatar.d.ts +2 -0
  11. package/dist/esm/components/db-avatar.js +1 -1
  12. package/dist/esm/components/db-banner.d.ts +2 -0
  13. package/dist/esm/components/db-banner.js +1 -1
  14. package/dist/esm/components/db-bottom-navigation.d.ts +2 -0
  15. package/dist/esm/components/db-bottom-navigation.js +1 -1
  16. package/dist/esm/components/db-card.d.ts +2 -0
  17. package/dist/esm/components/db-card.js +1 -1
  18. package/dist/esm/components/db-carousel.d.ts +2 -0
  19. package/dist/esm/components/db-carousel.js +1 -1
  20. package/dist/esm/components/db-code-block.d.ts +3 -0
  21. package/dist/esm/components/db-code-block.js +1 -1
  22. package/dist/esm/components/db-color-picker.d.ts +5 -0
  23. package/dist/esm/components/db-color-picker.js +1 -1
  24. package/dist/esm/components/db-color-swatch.d.ts +2 -0
  25. package/dist/esm/components/db-color-swatch.js +1 -1
  26. package/dist/esm/components/db-confirm.d.ts +2 -0
  27. package/dist/esm/components/db-confirm.js +1 -1
  28. package/dist/esm/components/db-context-menu.d.ts +6 -2
  29. package/dist/esm/components/db-context-menu.js +1 -1
  30. package/dist/esm/components/db-cookie-banner.d.ts +2 -0
  31. package/dist/esm/components/db-cookie-banner.js +1 -1
  32. package/dist/esm/components/db-drawer.d.ts +2 -0
  33. package/dist/esm/components/db-drawer.js +1 -1
  34. package/dist/esm/components/db-dropdown.d.ts +2 -0
  35. package/dist/esm/components/db-dropdown.js +1 -1
  36. package/dist/esm/components/db-easing-editor.d.ts +5 -0
  37. package/dist/esm/components/db-empty-state.d.ts +2 -0
  38. package/dist/esm/components/db-empty-state.js +1 -1
  39. package/dist/esm/components/db-file-uploader.d.ts +4 -0
  40. package/dist/esm/components/db-file-uploader.js +1 -1
  41. package/dist/esm/components/db-footer.d.ts +2 -0
  42. package/dist/esm/components/db-footer.js +1 -1
  43. package/dist/esm/components/db-gantt.d.ts +2 -0
  44. package/dist/esm/components/db-gantt.js +1 -1
  45. package/dist/esm/components/db-image-list.d.ts +3 -0
  46. package/dist/esm/components/db-image-list.js +1 -1
  47. package/dist/esm/components/db-keyframe-editor.d.ts +5 -0
  48. package/dist/esm/components/db-layer-list.d.ts +71 -0
  49. package/dist/esm/components/db-layer-list.js +1 -0
  50. package/dist/esm/components/db-lightbox.d.ts +4 -0
  51. package/dist/esm/components/db-lightbox.js +1 -1
  52. package/dist/esm/components/db-list.d.ts +2 -0
  53. package/dist/esm/components/db-list.js +1 -1
  54. package/dist/esm/components/db-menu-item.d.ts +2 -0
  55. package/dist/esm/components/db-menu-item.js +1 -1
  56. package/dist/esm/components/db-modal.d.ts +3 -0
  57. package/dist/esm/components/db-modal.js +1 -1
  58. package/dist/esm/components/db-navbar.d.ts +5 -0
  59. package/dist/esm/components/db-navbar.js +1 -1
  60. package/dist/esm/components/db-onboarding-modal.d.ts +3 -0
  61. package/dist/esm/components/db-onboarding-modal.js +1 -1
  62. package/dist/esm/components/db-page-header.d.ts +3 -0
  63. package/dist/esm/components/db-page-header.js +1 -1
  64. package/dist/esm/components/db-pagination.d.ts +2 -0
  65. package/dist/esm/components/db-pagination.js +1 -1
  66. package/dist/esm/components/db-popover.d.ts +2 -0
  67. package/dist/esm/components/db-popover.js +1 -1
  68. package/dist/esm/components/db-search-bar.d.ts +3 -0
  69. package/dist/esm/components/db-search-bar.js +1 -1
  70. package/dist/esm/components/db-section-app-download.d.ts +2 -0
  71. package/dist/esm/components/db-section-app-download.js +1 -1
  72. package/dist/esm/components/db-section-contact.d.ts +2 -0
  73. package/dist/esm/components/db-section-contact.js +1 -1
  74. package/dist/esm/components/db-section-cta.d.ts +3 -0
  75. package/dist/esm/components/db-section-cta.js +1 -1
  76. package/dist/esm/components/db-section-faq.d.ts +2 -0
  77. package/dist/esm/components/db-section-faq.js +1 -1
  78. package/dist/esm/components/db-section-feature.d.ts +3 -0
  79. package/dist/esm/components/db-section-feature.js +1 -1
  80. package/dist/esm/components/db-section-hero.d.ts +2 -0
  81. package/dist/esm/components/db-section-hero.js +1 -1
  82. package/dist/esm/components/db-section-logo-cloud.d.ts +2 -0
  83. package/dist/esm/components/db-section-logo-cloud.js +1 -1
  84. package/dist/esm/components/db-section-pricing.d.ts +3 -0
  85. package/dist/esm/components/db-section-pricing.js +1 -1
  86. package/dist/esm/components/db-section-stats.d.ts +2 -0
  87. package/dist/esm/components/db-section-stats.js +1 -1
  88. package/dist/esm/components/db-section-testimonials.d.ts +4 -0
  89. package/dist/esm/components/db-section-testimonials.js +1 -1
  90. package/dist/esm/components/db-section.d.ts +2 -0
  91. package/dist/esm/components/db-section.js +1 -1
  92. package/dist/esm/components/db-share.d.ts +5 -0
  93. package/dist/esm/components/db-share.js +1 -1
  94. package/dist/esm/components/db-sidebar.d.ts +7 -0
  95. package/dist/esm/components/db-sidebar.js +1 -1
  96. package/dist/esm/components/db-stat.d.ts +2 -0
  97. package/dist/esm/components/db-stat.js +1 -1
  98. package/dist/esm/components/db-table.d.ts +5 -0
  99. package/dist/esm/components/db-table.js +1 -1
  100. package/dist/esm/components/db-testimonial.d.ts +3 -0
  101. package/dist/esm/components/db-testimonial.js +1 -1
  102. package/dist/esm/components/db-time-picker.d.ts +1 -0
  103. package/dist/esm/components/db-time-picker.js +1 -1
  104. package/dist/esm/components/db-top-banner.d.ts +3 -0
  105. package/dist/esm/components/db-top-banner.js +1 -1
  106. package/dist/esm/components/db-tutorial.d.ts +3 -0
  107. package/dist/esm/components/db-tutorial.js +1 -1
  108. package/dist/esm/components/db-video-timeline.d.ts +34 -2
  109. package/dist/esm/components/db-video-timeline.js +1 -1
  110. package/dist/esm/components/db-world-map.d.ts +5 -0
  111. package/dist/esm/components/media-editor-model.d.ts +10 -1
  112. package/dist/esm/core/i18n-en.js +1 -1
  113. package/dist/esm/core/i18n.d.ts +11 -0
  114. package/dist/esm/core/i18n.js +1 -1
  115. package/dist/esm/index.d.ts +2 -1
  116. package/dist/esm/index.js +1 -1
  117. package/dist/index.cjs +1 -1
  118. package/dist/index.css +1 -1
  119. package/dist/index.d.ts +619 -486
  120. package/dist/index.esm.js +1 -1
  121. package/dist/react/index.cjs +1 -1
  122. package/dist/react/index.d.ts +47 -3
  123. package/dist/react/index.esm.js +1 -1
  124. package/docs/ACCORDION.md +17 -0
  125. package/docs/ACHIEVEMENTS.md +15 -0
  126. package/docs/AI-ELEMENTS.md +86 -0
  127. package/docs/APP-UPDATE.md +60 -0
  128. package/docs/ATTACHMENTS.md +32 -0
  129. package/docs/BUTTON.md +17 -0
  130. package/docs/CDN-GUIDE.md +82 -0
  131. package/docs/COMPONENT-CHECKLIST.md +98 -0
  132. package/docs/CONTEXT-MENU.md +22 -0
  133. package/docs/CREDIT-AI.md +136 -0
  134. package/docs/CREDIT-CARD.md +52 -0
  135. package/docs/CROSS_PLATFORM_COMPONENT_STRATEGY.md +81 -0
  136. package/docs/EASING-EDITOR.md +42 -0
  137. package/docs/EFFECT-KEYFRAMES.md +60 -0
  138. package/docs/LAYER-LIST.md +31 -0
  139. package/docs/MEDIA-EDITORS.md +63 -0
  140. package/docs/MODULE-REUSE.md +43 -0
  141. package/docs/MULTI-PLATFORM-GUIDE.md +246 -0
  142. package/docs/NOTIFICATIONS.md +23 -0
  143. package/docs/PAPER-CARDS.md +61 -0
  144. package/docs/QUALITY-GUIDELINES.md +196 -0
  145. package/docs/RANGE-SLIDER.md +29 -0
  146. package/docs/REACT-WC-INTEGRATION.md +96 -0
  147. package/docs/RECENT-COMPONENTS-QUALITY.md +96 -0
  148. package/docs/SELECT.md +26 -0
  149. package/docs/SELECTION-CONTROLS.md +33 -0
  150. package/docs/SITE-SHELL.md +37 -0
  151. package/docs/STYLING.md +60 -0
  152. package/docs/SUPPORT.md +39 -0
  153. package/docs/TABS.md +19 -0
  154. package/docs/TEXT-FIELDS.md +28 -0
  155. package/docs/TIME-PICKER-DEVELOPMENT.md +115 -0
  156. package/docs/TOGGLE.md +40 -0
  157. package/docs/TRAVEL.md +30 -0
  158. package/docs/UI-NATIVE.md +90 -0
  159. package/docs/VIDEO-EXPORT.md +12 -0
  160. package/docs/VOICE-INPUT.md +24 -0
  161. package/docs/VUE-ANGULAR-INTEGRATION.md +197 -0
  162. package/docs/WORDPRESS-INTEGRATION.md +250 -0
  163. package/docs/WORKSPACE.md +27 -0
  164. package/docs/quality/AXE-REPORT.md +35 -0
  165. package/docs/quality/CREDIT-AI-RELEASE-2026-10-18.md +34 -0
  166. package/docs/quality/ENUM-MIGRATION-WORKSHEET.md +288 -0
  167. package/docs/quality/SCORECARD.md +389 -0
  168. package/package.json +136 -130
@@ -0,0 +1,23 @@
1
+ # 알림 UI — 개발 소스 API
2
+
3
+ NotificationInbox / NotificationDropdown은 실제 ui-wc 컴포넌트다. Button·Badge·공식 bell-active 아이콘과 현재 토큰을 사용한다. native Popover는 살아 있는 inbox 노드와 리스너를 유지한다. 메일/푸시/이벤트 전달·DB/RLS 구현은 포함하지 않는다.
4
+
5
+ ## 데이터와 의도
6
+
7
+ HTML은 `.notifications` 프로퍼티, React는 notifications 배열 prop으로 `{id,title,description,dateLabel,unread,categoryLabel?}[]`를 전달한다. 날짜/상대 시간은 앱에서 포맷한다. 잘못된/중복 id 데이터는 제외하고 제목/본문/시간을 텍스트로 표시한다. getter는 복사본이다.
8
+
9
+ onDbRead는 `{detail:{id}}`, onDbReadAll은 `{detail:{ids}}`, onDbNotificationOpen은 `{detail:{id}}`, onDbRetry는 detail 없는 의도다. 알림을 열었다고 자동으로 읽음 처리하지 않는다. 앱이 서버 응답 후 배열을 교체한다. 서버는 사용자 소유권·동시 변경을 검증해야 한다. readAll은 현재 불러온 목록의 unread ids만 포함하고 페이지 밖 전체 알림은 변경하지 않는다. 임의 URL 실행 없이 앱이 id로 권한 있는 관련 화면을 결정한다.
10
+
11
+ label/loading/pending/error/disabled를 제공한다. loading은 목록 조회, pending은 읽음 저장 대기이며 목록 액션을 비활성화한다. error에서도 기존 데이터는 유지하고 재시도를 제공한다. 필터는 로컬 UI 상태(전체/안읽음)이며 unread 항목 제거 후 포커스는 필터에 남는다. 저장 중에는 비활성 버튼 대신 제목에 포커스를 둔다. 숨겨진 팝오버는 포커스를 가져오지 않는다.
12
+
13
+ Dropdown은 open/onDbOpenChange와 선택 unreadCount를 추가한다. 생략하면 불러온 목록만 집계한다. 전체 페이지 서버 총계가 필요하면 unreadCount를 명시한다. 잘못된 명시 값은 확인 필요, 99 초과 badge는 실제 Badge의 max-count 규칙을 따른다. 헤더는 ArrowDown/Enter로 열고 native Escape/외부 클릭으로 닫는다. focusin이 다른 요소로 이동하면 닫는다. nonmodal popup이며 focus trap은 없다.
14
+
15
+ ## 예제와 호환성
16
+
17
+ `http://127.0.0.1:5180/credit-wallet.html#notification-example`은 동일 배열을 공유하는 헤더+페이지 예제다. 읽음 응답350ms는 모의이며 실패 모드에서는 배열이 바뀌지 않는다. 알림 열기는 로컬 상세에 포커스를 이동한다. 빈 상태/로딩/오류 재시도/초기화를 시험할 수 있다. 새 알림 실시간 수신·DB 저장·재접속 복원은 구현하지 않았다.
18
+
19
+ 기존 exports 삭제·새 runtime dependency·starter Contracts 변경·공개 발행은 없다. Library의 비압축 ESM/CJS/d.ts와 source example을 검증한다. starter r2 설치 snapshot은 변경하지 않았으므로 실제 제품에 도입할 때 새 snapshot과 manifest/lockfile·소비 회귀를 별도로 진행한다. 기존 Credit/AI 컴포넌트의 API는 유지한다.
20
+
21
+ 검사: `node scripts/test-notification-ui.mjs`, `npx tsc -p examples/ui-wc-react/tsconfig.wallet.json --noEmit`, `npx vite build --config examples/ui-wc-react/vite.wallet.config.ts`. 실제 backend 검증과 구분한다.
22
+
23
+ source alias를 쓰는 production 소비에서도 customElements 등록을 보존하도록 package.json sideEffects에 src/define.ts와 src/components/*.ts를 명시한다. 기존 dist의 선언은 유지한다. 고정 Vite example runtime 검사는 DB_UI_EXAMPLE_URL 환경 변수로 지정한 별도 서버에서 실행할 수 있다.
@@ -0,0 +1,61 @@
1
+ # Ticket · BoardingPass · Receipt
2
+
3
+ 티켓·탑승권·영수증을 위한 반응형 Web Components입니다. 같은 API를 React 래퍼로 사용할 수 있습니다. 일반 theme/tokens 및 ui-wc CSS를 먼저 불러옵니다.
4
+
5
+ ```tsx
6
+ import '@designbasekorea/tokens/dist/css/tokens.css';
7
+ import '@designbasekorea/theme/dist/css/theme.css';
8
+ import '@designbasekorea/ui-wc/dist/index.css';
9
+ import { Ticket, BoardingPass, Receipt } from '@designbasekorea/ui-wc/react';
10
+
11
+ <Ticket issuer="Studio June" heading="함께하는 저녁" reference="DB-0128"
12
+ edge="scalloped" layout="auto"
13
+ fields={[{ label: '일시', value: '2026.11.12 19:00' }, { label: '좌석', value: 'A-12' }]}
14
+ actionLabel="티켓 확인" onAction={e => openTicket(e.detail.reference)} />
15
+
16
+ <BoardingPass issuer="Studio Air" origin="ICN" originName="서울"
17
+ destination="JFK" destinationName="뉴욕" passenger="KIM SEOYEON"
18
+ serviceNumber="AB 4723" date="2026.11.12" boardingTime="07:00"
19
+ departureTime="07:40" arrivalTime="10:50" terminal="1" gate="37" seat="10C"
20
+ reference="AB4723211" codeImageSrc={barcodeUrl} codeAlt="예약 AB4723211 바코드"
21
+ layout="auto" />
22
+
23
+ <Receipt merchant="Studio Store" currency="KRW" locale="ko-KR"
24
+ reference="RC-0128" issuedAt="2026.10.21 11:37"
25
+ items={[{ name: '플랫 화이트', quantity: 2, unitAmount: 5500 }]}
26
+ discountAmount={1000} taxAmount={0} paymentMethod="Visa · 4242"
27
+ note="찾아주셔서 감사합니다." edge="serrated" />
28
+ ```
29
+
30
+ `openTicket`과 `barcodeUrl`은 호스트 앱이 제공하는 동작·리소스입니다. 컴포넌트가 결제를 실행하거나 입장·탑승 자격을 인증하지 않습니다. 예제 SVG는 ReportLab Code128로 생성한 `AB4723211`의 실제 코드입니다.
31
+
32
+ ## 공통 동작
33
+
34
+ - `inline-size: 100%`로 부모 너비에 맞춥니다. 화면 폭 대신 자신의 컨테이너 폭을 사용합니다. 티켓·탑승권의 `layout="auto"` 또는 `horizontal`은 600px 이상에서 가로형, 그 아래에서 세로형입니다. 기본은 `vertical`입니다. 영수증은 세로형입니다.
35
+ - `action-label`/`actionLabel`이 있으면 실제 `db-button`이 나타납니다. `db-action`/`onAction`의 detail은 `{ reference: string }`입니다. `disabled`는 이 버튼을 비활성화합니다.
36
+ - `slot="actions"`인 자식으로 별도 액션을 추가할 수 있습니다. 데이터가 바뀌어도 호스트 앱 소유의 자식을 복제하거나 재생성하지 않습니다. 자식의 상태와 이벤트는 앱이 관리합니다. `disabled`는 앱이 추가한 자식 버튼에 자동 전파되지 않습니다.
37
+ - `reference`, `fields`, 품목명 등은 HTML로 해석하지 않습니다. React 속성과 DOM property를 동적으로 바꿀 수 있습니다. `fields`와 `items`는 HTML에서는 JSON attribute로도 전달합니다. property가 설정되면 JSON attribute보다 우선하며 `undefined`로 초기화할 수 있습니다.
38
+ - 티켓·탑승권은 `code-image-src`에 호스트가 생성한 실제 바코드·QR 이미지를 받습니다. 이미지를 생성하거나 모양만 흉내 내지 않습니다. 이미지가 없거나 로딩에 실패하면 참조 번호가 남습니다. `code-alt`로 이미지의 접근 가능한 설명을 지정합니다.
39
+ - `BoardingPass.labels`와 `Receipt.labels` 객체로 필드명을 지역화할 수 있습니다. 텍스트나 날짜는 입력값 그대로 표시합니다. `Receipt.locale`은 통화 포맷에 적용합니다.
40
+ - 인쇄 시 기본 제공 액션과 `slot="actions"` 영역을 숨깁니다.
41
+
42
+ ## 영수증 금액 계약
43
+
44
+ 금액은 **정수 최소 통화 단위**입니다. KRW `5500`은 ₩5,500, USD `5500`은 $55.00입니다. `quantity`는 양의 안전한 정수, `unitAmount`/`discountAmount`/`taxAmount`는 0 이상의 안전한 정수여야 합니다.
45
+
46
+ 합계 = 수량 × 단가의 합 − 할인 + 세금. 세금은 별도로 더해지는 금액이며 세율을 자동 추정하지 않습니다. 세금 포함 가격은 `taxAmount=0`으로 전달합니다. 할인 초과·음수·소수·정수 범위 초과·잘못된 JSON은 오류를 표시하고 합계를 `—`로 표시합니다. 빈 품목은 빈 상태와 0 합계로 표시합니다. 잘못된 통화/locale 조합은 KRW/ko-KR로 폴백합니다. 합계는 입력값에서 계산한 표시값이며 회계 서버의 검증을 대체하지 않습니다.
47
+
48
+ ## 스타일
49
+
50
+ 기본 색·글꼴·간격·라운드는 기존 `--db-*` 토큰을 사용합니다. 장식용 영문 아이브로우를 추가하지 않습니다. 브랜드의 원본 색상이 필요할 때 컴포넌트 범위에서 다음 변수를 지정할 수 있습니다.
51
+
52
+ - 공통: `--db-paper-surface`, `--db-paper-ink`, `--db-paper-muted`, `--db-paper-border`, `--db-paper-notch`.
53
+ - 티켓: `--db-ticket-surface`, `--db-ticket-text`, `--db-ticket-muted`, `--db-ticket-border`.
54
+ - 탑승권 헤더: `--db-boarding-pass-header-bg`, `--db-boarding-pass-header-text`.
55
+ - 코드 이미지 바탕: `--db-paper-code-background`. 스캐너가 읽을 수 있도록 원본 코드의 대비와 여백을 유지합니다.
56
+
57
+ 티켓 `edge="scalloped"`은 위아래의 둥근 절취 모양, 영수증 `edge="serrated"`는 아래의 톱니 모양입니다. 단순한 외곽선은 `edge="smooth"`를 사용합니다.
58
+
59
+ ## 간결한 영수증
60
+
61
+ `Receipt variant="compact"` / `<db-receipt variant="compact">`는 합계를 본문에 두고 하단에 바코드를 배치합니다. `code-image-src` / `codeImageSrc`, `code-alt` / `codeAlt`로 실제 서비스에서 만든 코드 이미지를 전달합니다. 코드 이미지를 자동 생성하거나 참조 번호를 임의 바코드로 변환하지 않습니다. URL 검증과 이미지 오류 시 숨김은 Ticket/BoardingPass와 같습니다. 기본 영수증과 동일한 정수 최소 통화 단위 계산, 할인·세금 검증, labels와 action 이벤트를 사용합니다.
@@ -0,0 +1,196 @@
1
+ # ui-wc 품질 규격
2
+
3
+ > `@designbasekorea/ui-wc` 컴포넌트가 **누가 어느 플랫폼에서 써도 되는 수준**인지 판단하는 단일 기준.
4
+ > 디자인 일치 여부는 이 문서의 범위가 아니다 — 여기서는 접근성·웹표준·성능·DX·UX 를 다룬다.
5
+
6
+ 관련 문서
7
+ - [COMPONENT-CHECKLIST.md](./COMPONENT-CHECKLIST.md) — 컴포넌트 하나를 만들/고칠 때 복사해 쓰는 체크리스트
8
+ - [STYLING.md](./STYLING.md) — Light DOM 결정과 CSS 규약
9
+ - [quality/SCORECARD.md](./quality/SCORECARD.md) — 전체 컴포넌트 자동 채점 결과 (생성물)
10
+
11
+ ---
12
+
13
+ ## 0. 규격 사용법
14
+
15
+ ### 0.1 등급
16
+
17
+ | 등급 | 의미 | 위반 시 |
18
+ |---|---|---|
19
+ | **P0** | 필수. 이걸 어기면 "쓸 수 있는 컴포넌트"가 아니다 | 릴리스 차단 (`npm run audit:quality -- --fail-on=p0`) |
20
+ | **P1** | 권장. 어겨도 동작은 하지만 품질이 떨어진다 | 스코어 감점, 백로그 등록 |
21
+ | **P2** | 지향. 장기 목표 | 기록만 |
22
+
23
+ ### 0.2 규칙 ID 규약
24
+
25
+ `<축>-<이름>` 형식. 자동 검사되는 규칙은 `scripts/audit-wc-quality.mjs` 에 같은 ID로 구현되어 있어야 하며,
26
+ 스크립트는 기동 시 **이 문서와 구현된 규칙 ID 집합이 일치하는지 self-check** 한다.
27
+
28
+ 각 규칙에는 검사 방식을 표기한다.
29
+
30
+ | 표기 | 뜻 |
31
+ |---|---|
32
+ | 🤖 정적 | `npm run audit:quality` — 소스 텍스트 정적 분석 |
33
+ | 🌐 런타임 | `npm run audit:axe` — Playwright + axe-core 실제 렌더 검증 |
34
+ | 👁 수동 | 자동화 불가. 리뷰어가 체크리스트로 확인 |
35
+
36
+ ### 0.3 아키타입
37
+
38
+ 규칙은 전부 모든 컴포넌트에 적용되지 않는다. 감사 스크립트는 소스 시그니처로 아키타입을 판정하고
39
+ 해당 규칙 세트만 적용한다.
40
+
41
+ | 아키타입 | 판정 근거 | 예시 |
42
+ |---|---|---|
43
+ | `overlay` | `role="dialog"`/`document.body` 포털/`open` 속성 | `db-modal` `db-drawer` `db-bottom-sheet` `db-lightbox` `db-confirm` |
44
+ | `form` | `value` + `name` 속성을 함께 가짐 | `db-input` `db-select` `db-checkbox` `db-toggle` `db-radio` |
45
+ | `composite` | 자식 항목을 순회하는 키보드 핸들러 보유 | `db-tabs` `db-select` `db-context-menu` `db-calendar` |
46
+ | `display` | 인터랙션 없음, 표시 전용 | `db-badge` `db-stat` `db-skeleton` |
47
+ | `section` | 마케팅 섹션 (`db-section-*`) | `db-section-hero` |
48
+
49
+ ---
50
+
51
+ ## 1. 접근성 (A11Y)
52
+
53
+ 기준: **WCAG 2.2 AA** + [WAI-ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/).
54
+
55
+ > **범위 밖**: 색 대비(1.4.3)는 `packages/tokens` / `packages/theme` 의 책임이다.
56
+ > 컴포넌트는 `var(--db-*)` 를 올바르게 참조하기만 하면 되며, 여기서 중복 검사하지 않는다.
57
+
58
+ ### P0
59
+
60
+ | ID | 규칙 | 검사 |
61
+ |---|---|---|
62
+ | `A11Y-KEYBOARD` | 마우스로 할 수 있는 모든 조작은 키보드로도 가능해야 한다. 클릭 핸들러가 붙은 비네이티브 요소는 `tabindex` + Enter/Space 핸들러 또는 네이티브 `<button>` 사용 | 🤖 정적 + 👁 |
63
+ | `A11Y-FOCUS-RING` | 포커스 표시는 `:focus-visible` 로만 준다. `:focus` 단독 사용 금지, `outline: none` 단독 금지. 링 토큰은 **`--db-border-focus` 하나로 통일** | 🤖 정적 |
64
+ | `A11Y-NAME` | 아이콘만 있는 컨트롤은 접근 가능한 이름을 가져야 한다 (`aria-label` 또는 `.designbase-wc-sr-only` 텍스트). 정적 검사는 이름의 출처를 완벽히 추적할 수 없어 넓게 통과시키므로, 실제 노출 여부는 axe 런타임 검사가 확정한다 | 🤖 정적 + 🌐 |
65
+ | `A11Y-TRAP` | `overlay` 아키타입은 열려 있는 동안 Tab 이 밖으로 나가지 않아야 하고, 닫을 때 **직전 포커스 요소로 복원**해야 한다. → `core/focus-trap.ts` 사용 | 🤖 정적 + 🌐 |
66
+ | `A11Y-DIALOG` | `overlay` 는 `role="dialog"`(파괴적 확인은 `alertdialog`) + `aria-modal="true"` + `aria-labelledby` 를 가져야 한다 | 🤖 정적 |
67
+ | `A11Y-DECORATIVE` | 순수 장식 노드(아이콘 SVG, 스피너 그래픽 등)는 `aria-hidden="true"` | 🤖 정적 |
68
+ | `A11Y-LIVE` | 사용자 조작 없이 등장하는 상태 변화(토스트, 검증 에러, 진행률 완료)는 live region 으로 알린다. → `core/a11y.ts` 의 `announce()` | 👁 |
69
+ | `A11Y-STATE` | 시각적으로 표현한 상태는 ARIA 로도 표현한다 (열림 `aria-expanded`, 선택 `aria-selected`/`aria-checked`/`aria-pressed`, 비활성 `aria-disabled`, 로딩 `aria-busy`, 오류 `aria-invalid`). **어떤 ARIA 속성이 허용되는지는 role 에 따라 다르다** — role 을 확인하고 고를 것: `role="button"` 토글은 `aria-pressed`, option/tab/row 계열만 `aria-selected`, `aria-busy` 는 전역이라 어디든 가능, `aria-invalid` 는 폼 컨트롤(대개 내부 `<input>`)에 붙인다 | 🤖 정적 + 🌐 |
70
+
71
+ > **`A11Y-STATE` 적용 시 주의**: 속성 이름이 같아도 의미가 다를 수 있다.
72
+ > `loading="lazy"`(네이티브 이미지 지연로딩)는 바쁜 상태가 아니고,
73
+ > 드래그 잠금이나 애니메이션 중지에 쓰는 `disabled` 는 비활성 컨트롤이 아니다.
74
+ > 그런 곳에 ARIA 를 붙이면 개선이 아니라 잘못된 정보가 된다.
75
+
76
+ ### P1
77
+
78
+ | ID | 규칙 | 검사 |
79
+ |---|---|---|
80
+ | `A11Y-APG` | 복합 위젯은 APG 패턴을 따른다 — tabs/radiogroup/toolbar = **roving tabindex** + 화살표 + Home/End, combobox = `aria-activedescendant`, menu = 화살표 + 타입어헤드, slider = `aria-valuemin/max/now/text` | 👁 (레퍼런스: `db-tabs`) |
81
+ | `A11Y-ESCAPE` | 닫을 수 있는 오버레이/팝업은 Escape 로 닫힌다 | 🤖 정적 |
82
+ | `A11Y-REDUCED-MOTION` | `packages/theme` 의 리셋이 `*` 에 대해 전역으로 애니메이션을 잘라내므로 기본은 대응된 상태다. 컴포넌트가 지켜야 할 것은 **그 리셋을 무력화하지 않는 것** — 클래스 + `!important` 로 애니메이션을 선언하면 전역 리셋(낮은 명시도)을 눌러 사용자가 끌 수 없게 된다. 불가피하면 컴포넌트 자체 `@media (prefers-reduced-motion: reduce)` 블록을 함께 둔다 | 🤖 정적 |
83
+ | `A11Y-TARGET` | 인터랙티브 타겟은 최소 24×24 CSS px (WCAG 2.2 SC 2.5.8). 인접 타겟은 44×44 권장 | 👁 |
84
+ | `A11Y-AUTOFOCUS` | 오버레이가 열릴 때 포커스는 `[autofocus]` → 첫 포커서블 → 컨테이너 순으로 이동한다 | 🌐 |
85
+
86
+ ### P2
87
+
88
+ | ID | 규칙 | 검사 |
89
+ |---|---|---|
90
+ | `A11Y-RTL` | 논리 속성(`margin-inline`, `inset-inline`)을 써서 RTL 에서 깨지지 않는다 | 👁 |
91
+ | `A11Y-FORCED-COLORS` | Windows 고대비 모드(`forced-colors: active`)에서 경계가 유지된다 | 👁 |
92
+
93
+ ---
94
+
95
+ ## 2. 웹 표준 / 상호운용 (STD)
96
+
97
+ Web Components 는 React·Vue·Angular·순수 HTML 어디서든 같게 동작해야 한다.
98
+
99
+ ### P0
100
+
101
+ | ID | 규칙 | 검사 |
102
+ |---|---|---|
103
+ | `STD-DEFINE` | 태그는 `db-` 접두사. 정의는 반드시 중복 가드로 감싼다. `HTMLElementTagNameMap` 선언 필수 | 🤖 정적 |
104
+ | `STD-EVENT` | 커스텀 이벤트는 `db-` 로 시작하는 kebab-case, `{ bubbles: true, composed: true }`. 페이로드는 `detail` 에만 담는다 | 🤖 정적 |
105
+ | `STD-ATTR-PROP` | 모든 공개 속성은 attribute ↔ property 양방향 반영. 프레임워크가 프로퍼티로 값을 넣어도 동작해야 한다 | 👁 |
106
+ | `STD-CONTRACT` | enum 성격의 값(variant/size/tone/radius…)은 `@designbasekorea/ui-contracts` 에서 import 한다. 로컬 문자열 리터럴 배열로 재선언 금지 — 웹과 RN 이 조용히 어긋난다 | 🤖 정적 |
107
+ | `STD-COERCE` | 계약에 없는 attribute 값이 들어와도 깨지지 않고 기본값으로 되돌아가야 한다. `coerceToContract()` 사용을 권장하되, 계약 배열에 대한 인라인 `.includes()` 가드도 동등하게 인정한다 — 검사하는 것은 **검증의 존재**지 헬퍼 사용 여부가 아니다 | 🤖 정적 |
108
+ | `STD-FORM` | `form` 아키타입은 **실제로 폼에 참여**해야 한다. Light DOM 이므로 1순위는 `name` 을 가진 네이티브 컨트롤(`<input>`/`<textarea>`/`<select>`)을 그대로 렌더하는 것이고, 그게 불가능한 구조에서만 `static formAssociated = true` + `ElementInternals`(`setFormValue`/`setValidity`) 를 쓴다. 값을 시각적으로만 표현하고 폼 데이터에 넣지 않는 구현은 금지 | 🤖 정적 |
109
+ | `STD-NO-INNERHTML` | 사용자 입력/속성 값을 `innerHTML` 에 문자열 보간하지 않는다. `textContent` 또는 노드 생성 사용 (XSS) | 🤖 정적 |
110
+
111
+ ### P1
112
+
113
+ | ID | 규칙 | 검사 |
114
+ |---|---|---|
115
+ | `STD-SSR` | 모듈 최상위에서 `document`/`window` 를 만지지 않는다. DOM 접근은 `connectedCallback` 이후 | 🤖 정적 |
116
+ | `STD-LIGHT-DOM` | Light DOM 을 유지한다(→ STYLING.md). 따라서 모든 클래스는 `designbase-wc-` 접두사 필수 — 전역 CSS 충돌 방어의 유일한 수단 | 🤖 정적 |
117
+ | `STD-CSS-PREFIX` | 컴포넌트가 정의하는 CSS 변수는 `--db-<component>-*` 네임스페이스 | 👁 |
118
+ | `STD-UPGRADE` | 정의 전에 설정된 프로퍼티를 `connectedCallback` 에서 회수한다 (property upgrade 패턴) | 👁 |
119
+
120
+ ---
121
+
122
+ ## 3. 성능 (PERF)
123
+
124
+ ### P0
125
+
126
+ | ID | 규칙 | 검사 |
127
+ |---|---|---|
128
+ | `PERF-CLEANUP` | `connectedCallback` 에서 등록한 리스너 / `setTimeout` / `setInterval` / `requestAnimationFrame` / `MutationObserver` / `ResizeObserver` / `IntersectionObserver` 는 **전부** `disconnectedCallback` 에서 해제한다. → `core/dom.ts` 의 `cleanupBag` | 🤖 정적 |
129
+ | `PERF-GLOBAL-STATE` | 여러 컴포넌트가 공유하는 전역 상태(body 스크롤락, 포털 컨테이너, live region)는 **참조 카운트**로 관리한다. `document.body.style.overflow` 직접 조작 금지 → `core/scroll-lock.ts` | 🤖 정적 |
130
+
131
+ ### P1
132
+
133
+ | ID | 규칙 | 검사 |
134
+ |---|---|---|
135
+ | `PERF-RECONCILE` | 상태 변화 시 `innerHTML` 전체 재생성 금지. 구조는 한 번 만들고(`ensureStructure`) 이후엔 노드 속성만 갱신 | 🤖 정적 |
136
+ | `PERF-RAF` | `scroll` / `resize` / `pointermove` 핸들러는 `requestAnimationFrame` 으로 병합하고, 스크롤 리스너는 `{ passive: true }` | 🤖 정적 |
137
+ | `PERF-LAZY` | 무거운 서드파티(`prismjs`)는 동적 `import()` 로 실제 필요 시점에 로드 | 🤖 정적 |
138
+ | `PERF-NO-LAYOUT-THRASH` | 루프 안에서 읽기(`getBoundingClientRect`)와 쓰기(`style.*`)를 번갈아 하지 않는다 | 👁 |
139
+
140
+ ### P2
141
+
142
+ | ID | 규칙 | 검사 |
143
+ |---|---|---|
144
+ | `PERF-CSS-SPLIT` | 컴포넌트별 CSS 를 분리해 `dist/index.css` 전량 로드를 피한다 (현재 795KB 단일 번들) | — |
145
+ | `PERF-BUDGET` | 컴포넌트별 gzip 크기 예산을 정하고 CI 에서 회귀 감시 | — |
146
+
147
+ ---
148
+
149
+ ## 4. 개발자 경험 (DX)
150
+
151
+ ### P0
152
+
153
+ | ID | 규칙 | 검사 |
154
+ |---|---|---|
155
+ | `DX-DOC-SYNC` | 실제 `observedAttributes` 와 문서(README, `attrsRegistry.ts`)가 일치한다 | 🤖 `npm run audit:docs` |
156
+ | `DX-NO-THROW` | 잘못된 입력에 throw 하지 않는다. 조용히 fallback 하고, 개발 빌드에서만 `console.warn` | 👁 |
157
+ | `DX-UID` | 엘리먼트 id 생성은 `core/uid.ts` 의 `dbUid()` 사용. `Math.random().toString(36)` 애드혹 금지 | 🤖 정적 |
158
+
159
+ ### P1
160
+
161
+ | ID | 규칙 | 검사 |
162
+ |---|---|---|
163
+ | `DX-I18N` | 사용자에게 노출되는 모든 문자열(aria-label 포함)은 오버라이드 가능해야 한다. 소스에 한국어 리터럴을 박지 말고 `core/i18n.ts` 의 `t()` 경유 | 🤖 정적 |
164
+ | `DX-STORY` | 컴포넌트당 Storybook 스토리 1개 이상 | 🤖 정적 |
165
+ | `DX-TYPES` | 공개 타입은 `src/index.ts` 에서 export 하고, 이벤트 `detail` 타입도 함께 노출 | 👁 |
166
+
167
+ ---
168
+
169
+ ## 5. UX
170
+
171
+ 전부 👁 수동 검토. 자동화 대상이 아니라 **디자인 리뷰의 공통 언어**로 쓴다.
172
+
173
+ ### P1
174
+
175
+ | ID | 규칙 |
176
+ |---|---|
177
+ | `UX-STATES` | 데이터를 다루는 컴포넌트는 기본 / 로딩 / 비어있음 / 오류 네 상태를 모두 정의한다 |
178
+ | `UX-DESTRUCTIVE` | 되돌릴 수 없는 액션은 확인 단계를 거치거나 실행 취소를 제공한다 |
179
+ | `UX-DISMISS` | 오버레이 닫기 방식은 일관된다 — Escape, 바깥 클릭, 닫기 버튼. 데이터 유실 위험이 있으면 바깥 클릭 닫기를 끈다 |
180
+ | `UX-MOTION` | 진입/퇴장 전환은 150~300ms, ease-out 계열. 그 이상은 느리게 느껴진다 |
181
+ | `UX-FEEDBACK` | 100ms 이상 걸리는 액션은 즉각적 피드백(로딩 상태)을 준다 |
182
+ | `UX-ERROR-COPY` | 오류 메시지는 무엇이 잘못됐는지 + 어떻게 고치는지를 담는다 |
183
+
184
+ ---
185
+
186
+ ## 6. 레퍼런스 구현
187
+
188
+ 새 컴포넌트를 만들거나 기존 것을 고칠 때 **복사할 기준**.
189
+
190
+ | 아키타입 | 레퍼런스 | 무엇을 보나 |
191
+ |---|---|---|
192
+ | 호스트-위젯 | `src/components/db-button.ts` | 속성↔프로퍼티, `aria-disabled`/`aria-busy`, Enter/Space, `:focus-visible` |
193
+ | 오버레이 | `src/components/db-modal.ts` | focus trap + 복원, 참조 카운트 스크롤락, `aria-modal`, Escape |
194
+ | 폼 컨트롤 | `src/components/db-checkbox.ts` | 네이티브 `<input name>` 을 품는 폼 참여, 검증 상태 배선 |
195
+ | 복합 위젯 | `src/components/db-tabs.ts` | roving tabindex, 화살표/Home/End, `aria-controls` 배선 |
196
+ | 알림 | `src/components/db-toast.ts` | live region, 타이머 일시정지 |
@@ -0,0 +1,29 @@
1
+ # RangeSlider: numeric state, input and controlled React
2
+
3
+ Import the tokens, theme and `@designbasekorea/ui-wc/dist/index.css`, then `RangeSlider` from `@designbasekorea/ui-wc/react`. Vanilla imports `DbRangeSlider` from `@designbasekorea/ui-wc/components/db-range-slider` to register `db-range-slider`.
4
+
5
+ ```tsx
6
+ const [volume, setVolume] = useState(40);
7
+ <RangeSlider label="Volume" showLabel value={volume}
8
+ onChange={event => setVolume(event.detail.value)} />
9
+
10
+ const [range, setRange] = useState<[number, number]>([20, 80]);
11
+ <RangeSlider label="Price" showLabel range={range} min={0} max={100}
12
+ onRangeChange={event => setRange(event.detail.range)} />
13
+ ```
14
+
15
+ The host owns persistence, submission and server validation. This component does not inject native form fields or save values to a server. Submit your controlled value/range or bind your own hidden inputs to the host form. Programmatic updates do not emit user-change events. A host reset should reset its controlled state.
16
+
17
+ `value` is a number for single mode. `range` is a `[number, number]` tuple; omitting/removing it restores single mode. HTML supports `range="20,80"` and a JSON tuple string. Legacy HTML with an empty `range` attribute and `value="20,80"` is also recognized. React uses a tuple, not `range={true}` or an array in `value`. Public getters return bounded values, and returning/mutating a copied tuple does not mutate internal state.
18
+
19
+ The step grid starts at `min`, not zero. Decimals are normalized to avoid ordinary floating-point tails. Invalid/nonpositive step falls back to 1; non-finite bounds fall back to 0/100. A maximum below the minimum creates a fixed-value domain at the minimum. If the span is not divisible by step, End reaches the highest permitted step. Supplied ranges are sorted and bounded; thumbs cannot cross. Values beyond JavaScript number precision cannot represent arbitrarily small steps. Invalid value attributes use the default 50 before bounding.
20
+
21
+ User changes emit one bubbling, composed `db-change` for each actual value change. Single detail: `{ value: number }`; range detail: `{ range: [number, number] }`. Range changes additionally emit `db-range-change`; React `onRangeChange` listens only to that event. `onChange` / `onDbChange` are typed to the supplied mode, including a discriminated detail union for a dynamic range. Providing multiple handlers for the same action intentionally calls each handler.
22
+
23
+ Arrow keys move one step; PageUp/PageDown move ten. Home/End reach the thumb's permitted endpoints. Both range thumbs retain identity/focus through controlled updates, label/mark changes and mode updates. Readonly stays focusable and exposes its value, while disabled is removed from tab navigation. Both block user changes. `focus({ preventScroll: true })` targets the first thumb.
24
+
25
+ Track clicks choose the nearest range thumb and focus it. Thumb drags use one pointer ID and capture, coalesce movement per animation frame, retain the final pointerup position, and stop on cancel, loss of capture, disable, mode/orientation changes or disconnect. Unrelated pointers cannot change an active gesture. Reconnecting binds one track handler again; stale frames do not resume a prior drag.
26
+
27
+ Use a visible `label` with `showLabel`, or supply `aria-label` / `aria-labelledby`. External descriptions are forwarded with `aria-describedby`. Range endpoints have distinct minimum/maximum names; `labelMinimum` / `labelMaximum` (`label-minimum` / `label-maximum`) customize/localize these suffixes. `aria-valuemin/max/now/text`, orientation and readonly/disabled state describe the actual thumbs. Marks are decorative; labels are text, not HTML.
28
+
29
+ Size is the shared `s | m | l`, default `m`. Variant is `default | primary | success | warning | danger`, default `default`. Values may appear top/left/right/bottom; invalid positions use top with a visible label, otherwise right. Range values use the top header. `vertical`, marks/markLabels, showValue, showThumbTooltip and valueFixed remain supported. Formatting precision clamps to 0–100 to avoid a RangeError; long values wrap. Tooltip/mark text is kept inside the host's horizontal bounds. Semantic thumb surface/border tokens can be overridden on the host. Visible knobs keep their size while pointer targets are at least 24px, or 44px with a coarse pointer. Reduced motion removes visual transitions.
@@ -0,0 +1,96 @@
1
+ <!-- Generated from docs/REACT-WC-INTEGRATION.md by scripts/sync-package-docs.mjs. -->
2
+ # React + ui-wc 통합 가이드
3
+
4
+ `@designbasekorea/ui-wc`(Web Components)와 `@designbasekorea/ui-wc/react`(React thin wrapper) 통합 전략입니다. `ui-wc`가 원본 구현이고, React 공개 API는 `Button`, `Input`, `Tabs` 이름을 유지합니다.
5
+
6
+ ## 언제 무엇을 쓰나
7
+
8
+ | 상황 | 권장 패키지 |
9
+ |------|-------------|
10
+ | 신규 React 앱 (크로스플랫폼) | `@designbasekorea/ui-wc/react` |
11
+ | Vue/Angular/vanilla 신규 프로젝트 | `@designbasekorea/ui-wc` (`<db-*>`) |
12
+ | Storybook·디자인 기준 참고 | `@designbasekorea/ui` (full React, 수정 금지) |
13
+ | React Native | `@designbasekorea/ui-native` |
14
+
15
+ ---
16
+
17
+ ## React에서 Web Components 사용 (권장)
18
+
19
+ ### 1. 설치 & CSS
20
+
21
+ ```bash
22
+ npm install @designbasekorea/ui-wc @designbasekorea/tokens @designbasekorea/theme
23
+ ```
24
+
25
+ ```tsx
26
+ // main.tsx
27
+ import '@designbasekorea/tokens/dist/css/tokens.css';
28
+ import '@designbasekorea/theme/dist/css/theme.css';
29
+ import '@designbasekorea/ui-wc/dist/index.css';
30
+ ```
31
+
32
+ `@designbasekorea/ui-wc/react`를 import하면 WC 정의(`define.ts`)가 함께 로드됩니다.
33
+
34
+ ### 2. 공개 API
35
+
36
+ ```tsx
37
+ import { Button } from '@designbasekorea/ui-wc/react';
38
+
39
+ function App() {
40
+ return <Button variant="primary" onPress={() => console.log('saved')}>저장</Button>;
41
+ }
42
+ ```
43
+
44
+ React 사용자는 `<db-button>`으로 바꾸지 않아도 됩니다. thin wrapper가 내부에서 `<db-button>`을 렌더링하고 `db-click`을 `onPress`로 연결합니다.
45
+
46
+ ### 3. 빌드 산출물
47
+
48
+ `packages/ui-wc` 빌드 시 아래가 함께 생성됩니다.
49
+
50
+ ```txt
51
+ dist/index.esm.js ← WC 진입점
52
+ dist/react/index.esm.js ← React wrapper 진입점
53
+ dist/index.css
54
+ ```
55
+
56
+ `package.json` exports:
57
+
58
+ ```json
59
+ {
60
+ ".": "./dist/index.esm.js",
61
+ "./react": "./dist/react/index.esm.js",
62
+ "./dist/index.css": "./dist/index.css"
63
+ }
64
+ ```
65
+
66
+ ### 4. 이벤트·속성 규칙
67
+
68
+ - WC 이벤트: `db-click`, `db-change`, `db-input` …
69
+ - React wrapper: `onPress`, `onChange`, `onInput` 등으로 별칭 제공 (내부에서 WC 이벤트에 연결)
70
+ - 아이콘: React node 대신 `startIcon="user"` 같은 WC 속성 문자열 사용
71
+
72
+ ---
73
+
74
+ ## `@designbasekorea/ui`와의 관계
75
+
76
+ | | `@designbasekorea/ui-wc/react` | `@designbasekorea/ui` |
77
+ |--|-------------------------------|----------------------|
78
+ | 구현 | WC thin bridge | full React + react-aria |
79
+ | 목적 | Vue/Angular와 동일 원본 공유 | Storybook·디자인 참고 |
80
+ | 수정 | ui-wc 패키지에서 관리 | **보호** (직접 수정 금지) |
81
+
82
+ 같은 페이지에서 혼용 가능합니다. CSS 충돌 방지:
83
+
84
+ - WC 클래스: `designbase-wc-*`
85
+ - React UI 클래스: `designbase-*`
86
+
87
+ 토큰(`--db-*`)은 공유하므로 시각적으로 일관됩니다.
88
+
89
+ ---
90
+
91
+ ## 관련 문서
92
+
93
+ - [MULTI-PLATFORM-GUIDE.md](./MULTI-PLATFORM-GUIDE.md)
94
+ - [CROSS_PLATFORM_COMPONENT_STRATEGY.md](./CROSS_PLATFORM_COMPONENT_STRATEGY.md)
95
+ - [VUE-ANGULAR-INTEGRATION.md](./VUE-ANGULAR-INTEGRATION.md)
96
+ - [packages/ui-wc/README.md](../README.md)
@@ -0,0 +1,96 @@
1
+ # 최근 UI 모듈 품질과 조정 계약
2
+
3
+ 검수 범위는 32개이며 scripts/recent-component-scope.mjs가 정본이다. 모든 UI는 실제 ui-wc와 theme, 공식 아이콘을 사용한다. 구성 요소별 상태는 소비 앱이 전달하며 승인/결제/초대/파일 실행을 자체 수행하지 않는다.
4
+
5
+ ## 간격과 변수
6
+
7
+ 색상·텍스트·간격·모서리는 기존 theme의 의미 토큰을 사용한다. 아래 이름은 **이 컴포넌트들이 소비하는 선택 CSS 변수**이며 theme에 원래 존재한다고 가정하는 토큰이 아니다. 지정하지 않으면 표의 fallback을 사용한다. 인스턴스의 style 또는 해당 컴포넌트를 지정하는 CSS 규칙으로 조정한다. 중첩 컴포넌트는 각각의 스타일 범위를 가진다.
8
+
9
+ | 컴포넌트 변수 | 기본 fallback |
10
+ | --- | --- |
11
+ | `--db-ai-font-size` | `var(--db-text-s)` |
12
+ | `--db-ai-line-height` | `var(--db-leading-normal)` |
13
+ | `--db-ai-row-height` | `28px` |
14
+ | `--db-ai-file-row-height` | `26px` |
15
+ | `--db-ai-icon-size` | `14px` |
16
+ | `--db-ai-indent` | `var(--db-padding-m)` |
17
+ | `--db-ai-conversation-gap` | `var(--db-gap-l)` |
18
+ | `--db-ai-conversation-max-height` | `36rem` |
19
+ | `--db-ai-citation-width` | `280px` |
20
+ | `--db-ai-citation-inset` | `16px` |
21
+ | `--db-workspace-members-padding` | `var(--db-padding-m)` |
22
+ | `--db-workspace-members-row-padding` | `var(--db-padding-s)` |
23
+ | `--db-attachment-list-padding` | `var(--db-padding-m)` |
24
+ | `--db-attachment-list-row-padding` | `var(--db-padding-s)` |
25
+ | `--db-notification-inbox-padding` | `var(--db-padding-m)` |
26
+ | `--db-notification-inbox-row-padding` | `var(--db-padding-s)` |
27
+ | `--db-credit-history-padding` | `var(--db-padding-m)` |
28
+ | `--db-credit-history-row-padding` | `var(--db-padding-s)` |
29
+
30
+ 행·아이콘·인용 너비 등의 길이는 양수 CSS 길이로 지정한다. 인용 너비/여백은 rem/calc도 지원하며 viewport 내로 제한된다. FileTree의 파일 버튼도 글자·아이콘·행 높이·들여쓰기 변수에 맞춰 조정된다. 목록의 외곽 패딩과 행 패딩은 서로 독립적이다. 모바일에서도 같은 변수가 적용된다. 작업 목록 기본 밀도는 유지하며 화면 전체의 theme 값을 임의로 덮어쓰지 않는다.
31
+
32
+ ```tsx
33
+ <AiChainOfThought
34
+ style={{'--db-ai-row-height':'32px','--db-ai-icon-size':'16px'} as React.CSSProperties}
35
+ label="작업 과정" status="running" items={steps}
36
+ />
37
+ <AttachmentList style={{'--db-attachment-list-padding':'20px'} as React.CSSProperties} attachments={files}/>
38
+ ```
39
+
40
+ ## 상태의 가독성
41
+
42
+ 멤버·첨부 상태는 기존 secondary Badge에 공식 상태 아이콘과 텍스트를 조합한다. 작은 글자의 가독성을 우선하며 색만으로 상태를 전달하지 않는다. 다른 Badge 사용처의 기존 variant 동작은 변경하지 않는다.
43
+
44
+ ## 보완한 사용 동작
45
+
46
+ - FileTree의 children: []는 빈 폴더로 표시하며 파일 열기 동작을 만들지 않는다. 빈 트리에도 설명을 제공한다. 다른 부모 밑에서 같은 폴더 ID가 반복되어도 펼침 상태가 섞이지 않으며, 파일 열기용 ID는 전체 트리에서 고유한 경로를 권장한다. 폴더 아이콘을 AI 작업 완료로 읽어주지 않는다.
47
+ - 단계에 코드/설명이 있어도 meta(기간 등)를 요약 행에 표시한다. 명시한 의미 아이콘은 완료 상태의 체크 아이콘으로 덮어쓰지 않는다.
48
+ - Citation은 native Popover의 top layer를 사용한다. 긴 출처는 내부 스크롤하며, 바깥 클릭과 Escape는 닫힘 이벤트를 전달한다. 다른 곳에서 Escape를 눌러도 닫힌 인용으로 포커스를 빼앗지 않는다. 창 크기·스크롤 변화에 위치를 갱신한다.
49
+ - Vanilla Conversation에 append한 새 메시지는 실제 대화 viewport로 이동한다. React는 기존의 안정된 자식 컨테이너를 유지한다.
50
+ - AttachmentList에서 포커스 중인 항목이 제거되면 다음 항목의 동작으로, 마지막 항목도 없어지면 목록 제목으로 포커스를 옮긴다. 외부 영역에서 작업 중인 사용자의 포커스를 빼앗지 않는다.
51
+ - 워크스페이스·첨부·알림·내역 목록은 의미 토큰 기반의 작은 패딩과 제목 크기로 정렬한다. 긴 역할/이메일을 위한 모바일 멤버 목록 배치를 보완한다.
52
+
53
+ ## 검증 명령과 범위
54
+
55
+ ```sh
56
+ node scripts/audit-recent-component-tokens.mjs
57
+ npm run build --prefix packages/ui-wc
58
+ node scripts/test-recent-components-quality.mjs
59
+ ```
60
+
61
+ 새 품질 검사는32개 모듈의 built 출력, light/dark,1280/1024/1023/768/767/390px, 실제 CSS 변수 효과, 긴 텍스트, 의미 글자 확대, 키보드와 overlay를 검사한다. 기존 React 예제·개별 패키지 소비·인스턴스 검사도 별도로 실행하고 결과를 docs/evidence/RECENT_COMPONENTS_QUALITY_2026-10-19.md에 기록한다. 실기기 iOS/Android/스크린리더·실제 서버와 결제·초대·Storage·원격 배포는 이 브라우저 UI 검증과 구분한다.
62
+
63
+ ## 대상
64
+
65
+ - db-credit-balance
66
+ - db-credit-recharge
67
+ - db-credit-dropdown
68
+ - db-credit-recharge-modal
69
+ - db-credit-history
70
+ - db-plan-overview
71
+ - db-ai-model-select
72
+ - db-ai-execution-quote
73
+ - db-ai-task-status
74
+ - db-notification-inbox
75
+ - db-notification-dropdown
76
+ - db-workspace-members
77
+ - db-workspace-invite-modal
78
+ - db-attachment-list
79
+ - db-ai-chain-of-thought
80
+ - db-ai-checkpoint
81
+ - db-ai-confirmation
82
+ - db-ai-context
83
+ - db-ai-conversation
84
+ - db-ai-file-tree
85
+ - db-ai-inline-citation
86
+ - db-ai-message
87
+ - db-ai-plan
88
+ - db-ai-queue
89
+ - db-ai-reasoning
90
+ - db-ai-sources
91
+ - db-ai-stack-trace
92
+ - db-ai-subagent
93
+ - db-ai-task
94
+ - db-ai-terminal
95
+ - db-ai-test-results
96
+ - db-ai-tool
package/docs/SELECT.md ADDED
@@ -0,0 +1,26 @@
1
+ # Select: installation, forms and keyboard behavior
2
+
3
+ ```tsx
4
+ import '@designbasekorea/tokens/dist/css/tokens.css';
5
+ import '@designbasekorea/theme/dist/css/theme.css';
6
+ import '@designbasekorea/ui-wc/dist/index.css';
7
+ import { Select } from '@designbasekorea/ui-wc/react';
8
+
9
+ <Select name="plan" label="Plan" defaultValue="starter" required
10
+ options={[{ value: 'starter', label: 'Starter' }, { value: 'pro', label: 'Pro' }]}
11
+ onChange={event => console.log(event.detail.value)} />
12
+ ```
13
+
14
+ Vanilla imports `DbSelect` from `@designbasekorea/ui-wc/components/db-select` (registers `db-select`). JSON `options` is an array of `{ value, label, disabled? }`. String/number values and labels normalize to strings; malformed entries are ignored and duplicate values use the first entry. An empty value means no selection. A comma separated string remains supported for simple options.
15
+
16
+ `value` is the current serialized string. Multiple selection uses a JSON string array in HTML/Vanilla; React accepts a string array. `defaultValue` / `default-value` sets the reset target. Without it, the first configured value becomes the initial reset target. `form.reset()` restores defaults without emitting a change event. Changing the default before a user selection updates the current value; after interaction it updates only the reset target. For a controlled React field, the host owns state and must also restore its state in the form's `onReset` handler.
17
+
18
+ A named Select participates in native `FormData` and supports a form ID through the `form` attribute. Multiple selections produce repeated name/value entries. An unnamed or disabled control is excluded. Fieldset disabling preserves the explicit disabled attribute. Readonly remains focusable and submitted, but cannot open, clear or change. `required` participates in native form validation; public `checkValidity()`, `reportValidity()`, `validity`, `validationMessage`, `willValidate` and `form` delegate to ElementInternals. No server persistence is included: the host owns persistence and submission results.
19
+
20
+ React `onChange`/`onDbChange` map to one bubbling, composed `db-change` event per user change. Single detail: `{ value: string, label: string }`. Multiple detail: `{ value: string[], labels: string[] }`. The component also emits a native bubbling `change`. Programmatic value changes and reset emit neither. When toggling `multiple`, the first selected item is kept when returning to single mode; update controlled state to the matching shape.
21
+
22
+ Use a visible `label`, or `aria-label` / `aria-labelledby`. External `aria-describedby` is combined with helper/error text. Required, disabled, readonly and explicit error state reach the actual combobox. `helperText`, `error`, `errorMessage` and custom `label-search` / `label-clear` strings can change dynamically. A multiple listbox exposes `aria-multiselectable` and each option exposes `aria-selected`; decorative checks add no extra tab stops.
23
+
24
+ Keyboard: Enter/Space/ArrowDown/ArrowUp opens. Arrows skip disabled options; Home/End reaches the first/last enabled item when focus is on the trigger. Enter or Space selects (Space remains editable text in search). Search receives focus on opening and maintains its active option with `aria-activedescendant`. Escape closes and returns focus, Tab closes and continues navigation, outside pointer interaction closes. Type letters on the non-search trigger to find an option by its prefix. `focus({ preventScroll: true })` targets the real trigger. Native buttons for clear/removal remain individually accessible.
25
+
26
+ Size uses the shared `s | m | l` contract, invalid size falls back to `m`; position is `top | bottom`, invalid values fall back to `bottom`. `dropdownWidth` is `auto | full`; numeric widths are not supported. Invalid/nonpositive max-height falls back to 200px. Long values stay within the available width. Theme and field state colors use semantic `--db-*` variables. Coarse pointers receive 44px minimum targets.
@@ -0,0 +1,33 @@
1
+ # Checkbox and Radio
2
+
3
+ Use the installed package exports. Both controls participate in their native parent form through an internal input. They present local selection; the host owns saving, server validation and persistence.
4
+
5
+ ```tsx
6
+ import '@designbasekorea/tokens/dist/css/tokens.css';
7
+ import '@designbasekorea/theme/dist/css/theme.css';
8
+ import '@designbasekorea/ui-wc/dist/index.css';
9
+ import { Checkbox, Radio } from '@designbasekorea/ui-wc/react';
10
+
11
+ <Checkbox name="newsletter" value="yes" checked={enabled}
12
+ onChange={event => setEnabled(event.detail.checked)}>Product updates</Checkbox>
13
+ <Radio name="plan" value="pro" checked={plan === 'pro'}
14
+ onChange={event => setPlan(event.detail.value)}>Pro</Radio>
15
+ ```
16
+
17
+ For a standalone Vanilla module, import `@designbasekorea/ui-wc/components/db-checkbox` or `@designbasekorea/ui-wc/components/db-radio` instead of the React export. No monorepo source aliases are required.
18
+
19
+ - `checked` / `selected`: current boolean state. `selected` is an alias. A user change reflects the canonical `checked` attribute and removes the old `selected` attribute. Both properties reflect to the native input, including values assigned before registration.
20
+ - `defaultChecked` / `defaultSelected` (HTML: `default-checked` / `default-selected`): reset default. If absent, the initial mounted selection is the reset default. Subsequent user changes or controlled `checked` changes do not replace it. `form.reset()` restores both the native input and host state without a user-change event.
21
+ - `name` / `value`: form field and submitted value. The default value is `on`, as with native checkbox/radio inputs. An explicit empty value stays empty. Unchecked/disabled controls are not submitted. A readonly selected control remains part of the form.
22
+ - `disabled`, `readOnly` (HTML: `readonly`), `required`: boolean states. Readonly blocks user selection while retaining focus and the form value; programmatic updates remain possible.
23
+ - `size`: `s`, `m`, `l`. Invalid values fall back to `m`. Visual marks retain their size scale; the actual label target is at least 24px and at least 44px for coarse pointers.
24
+ - `hasLabel` (HTML: `has-label="false"`): show/hide visible children. Supply `aria-label` or `aria-labelledby` when hiding the label. `aria-label`, `aria-labelledby` and `aria-describedby` reflect to the native input, including subsequent changes.
25
+ - `focus(options?)`: focuses the native input. Native Space interaction is supported; radio groups also use native arrow navigation.
26
+ - Checkbox additionally supports the `indeterminate` boolean; selection clears it.
27
+ - `db-change` / React `onChange` or `onDbChange`: Checkbox detail is `{ checked, selected }`; Radio detail adds `{ value }`. It bubbles and is composed. Programmatic property changes and form resets do not emit a user-change event.
28
+
29
+ Give related radios the same `name`, separate forms or different names for independent groups, and a fieldset/legend for the group purpose. Do not add a second change listener to both `onChange` and `onDbChange` for one action unless you intentionally want both callbacks.
30
+
31
+ The browser's native required validation is used. Connect remote error messages from the host; these elements do not submit an API request, create an account, charge a card or persist data themselves. The checkbox and radio component pages include copyable React/HTML examples; the React radio group example keeps the selected value in state.
32
+
33
+ React labels can change without replacing the internal native input. `label` takes precedence over children. Plain children use the label attribute; rich children stay in a stable React-owned slot so links retain their href and callbacks. Switching between plain/rich content and removing an explicit label preserves the rich slot and current selection. In Vanilla, update the `label` property/attribute for plain text without replacing the host contents. A link inside a checkbox/radio label remains independently operable; avoid nesting other form controls.
@@ -0,0 +1,37 @@
1
+ # Header and Footer
2
+
3
+ Use `Navbar` for a responsive site Header and `Footer` for the site footer. Both are independent exports from `@designbasekorea/ui-wc/react`; Vanilla consumers use `db-navbar` and `db-footer`. Load the shipped theme and component styles as shown in the package installation guide.
4
+
5
+ ```tsx
6
+ import { Navbar, Footer, Button } from '@designbasekorea/ui-wc/react';
7
+
8
+ export function SiteHeader() {
9
+ return <header>
10
+ <Navbar fullWidth position="static" items={[
11
+ { id: 'library', label: 'Library', href: '/library' },
12
+ { id: 'pricing', label: 'Pricing', href: '/pricing' },
13
+ ]} onItemClick={event => {
14
+ window.location.assign(event.detail.item.href || '/');
15
+ }}>
16
+ <a slot="brand" href="/">My studio</a>
17
+ <div slot="actions"><Button onPress={() => window.location.assign('/account')}>My account</Button></div>
18
+ </Navbar>
19
+ </header>;
20
+ }
21
+
22
+ export function SiteFooter() {
23
+ return <Footer fullWidth columns={[
24
+ { id: 'support', title: 'Support', items: [
25
+ { id: 'docs', label: 'Documentation', href: '/docs' },
26
+ ] },
27
+ ]} copyright="© My studio" onLinkClick={(event: CustomEvent<{link: {href?: string}}>) => {
28
+ window.location.assign(event.detail.link.href || '/');
29
+ }} />;
30
+ }
31
+ ```
32
+
33
+ Navbar uses desktop links when its own allocated width is at least 1024px, and a collapsible menu below 1024px. Container queries make embedded previews and side panels respond to their available width independently of the browser window. Escape and navigation close the mobile menu; resizing the component back to desktop width clears its open state. Mega-menu columns wrap to preserve readable widths; long names wrap within their own column, and narrow desktop menus place the featured card below the links. Custom `brand` and `actions` Light DOM slots preserve the supplied nodes and their framework listeners through updates. Supplying actions replaces the default sign-in controls; include any mobile purchase/account links in `items` as appropriate. Host classes are preserved.
34
+
35
+ Keep the site's sticky container outside Navbar (`position="static"`), so the banner and navigation have one measured offset. A sticky PageHeader's `--db-page-header-sticky-offset` must equal that container's full height, including any test or announcement banner. Do not simultaneously give the same navigation a separate fixed position.
36
+
37
+ `PageHeader sticky compactOnScroll` reserves the expanded flow height while compacting. This prevents scroll anchoring from moving the sentinel and repeatedly collapsing/expanding at the threshold. Its spacer is removed on disconnect or when compact sticky behavior is disabled. The app owns route handling, session state and entitlements; Navbar and Footer emit link events rather than implementing a router.