@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.en.md +45 -0
- package/README.md +13 -5
- package/THIRD-PARTY-NOTICES.md +5 -0
- package/dist/esm/components/db-ad-banner.d.ts +6 -0
- package/dist/esm/components/db-ad-banner.js +1 -1
- package/dist/esm/components/db-animation-background.d.ts +7 -0
- package/dist/esm/components/db-animation-background.js +1 -1
- package/dist/esm/components/db-avatar.d.ts +2 -0
- package/dist/esm/components/db-avatar.js +1 -1
- package/dist/esm/components/db-banner.d.ts +2 -0
- package/dist/esm/components/db-banner.js +1 -1
- package/dist/esm/components/db-bottom-navigation.d.ts +2 -0
- package/dist/esm/components/db-bottom-navigation.js +1 -1
- package/dist/esm/components/db-card.d.ts +2 -0
- package/dist/esm/components/db-card.js +1 -1
- package/dist/esm/components/db-carousel.d.ts +2 -0
- package/dist/esm/components/db-carousel.js +1 -1
- package/dist/esm/components/db-code-block.d.ts +3 -0
- package/dist/esm/components/db-code-block.js +1 -1
- package/dist/esm/components/db-color-picker.d.ts +5 -0
- package/dist/esm/components/db-color-picker.js +1 -1
- package/dist/esm/components/db-color-swatch.d.ts +2 -0
- package/dist/esm/components/db-color-swatch.js +1 -1
- package/dist/esm/components/db-confirm.d.ts +2 -0
- package/dist/esm/components/db-confirm.js +1 -1
- package/dist/esm/components/db-context-menu.d.ts +6 -2
- package/dist/esm/components/db-context-menu.js +1 -1
- package/dist/esm/components/db-cookie-banner.d.ts +2 -0
- package/dist/esm/components/db-cookie-banner.js +1 -1
- package/dist/esm/components/db-drawer.d.ts +2 -0
- package/dist/esm/components/db-drawer.js +1 -1
- package/dist/esm/components/db-dropdown.d.ts +2 -0
- package/dist/esm/components/db-dropdown.js +1 -1
- package/dist/esm/components/db-easing-editor.d.ts +5 -0
- package/dist/esm/components/db-empty-state.d.ts +2 -0
- package/dist/esm/components/db-empty-state.js +1 -1
- package/dist/esm/components/db-file-uploader.d.ts +4 -0
- package/dist/esm/components/db-file-uploader.js +1 -1
- package/dist/esm/components/db-footer.d.ts +2 -0
- package/dist/esm/components/db-footer.js +1 -1
- package/dist/esm/components/db-gantt.d.ts +2 -0
- package/dist/esm/components/db-gantt.js +1 -1
- package/dist/esm/components/db-image-list.d.ts +3 -0
- package/dist/esm/components/db-image-list.js +1 -1
- package/dist/esm/components/db-keyframe-editor.d.ts +5 -0
- package/dist/esm/components/db-layer-list.d.ts +71 -0
- package/dist/esm/components/db-layer-list.js +1 -0
- package/dist/esm/components/db-lightbox.d.ts +4 -0
- package/dist/esm/components/db-lightbox.js +1 -1
- package/dist/esm/components/db-list.d.ts +2 -0
- package/dist/esm/components/db-list.js +1 -1
- package/dist/esm/components/db-menu-item.d.ts +2 -0
- package/dist/esm/components/db-menu-item.js +1 -1
- package/dist/esm/components/db-modal.d.ts +3 -0
- package/dist/esm/components/db-modal.js +1 -1
- package/dist/esm/components/db-navbar.d.ts +5 -0
- package/dist/esm/components/db-navbar.js +1 -1
- package/dist/esm/components/db-onboarding-modal.d.ts +3 -0
- package/dist/esm/components/db-onboarding-modal.js +1 -1
- package/dist/esm/components/db-page-header.d.ts +3 -0
- package/dist/esm/components/db-page-header.js +1 -1
- package/dist/esm/components/db-pagination.d.ts +2 -0
- package/dist/esm/components/db-pagination.js +1 -1
- package/dist/esm/components/db-popover.d.ts +2 -0
- package/dist/esm/components/db-popover.js +1 -1
- package/dist/esm/components/db-search-bar.d.ts +3 -0
- package/dist/esm/components/db-search-bar.js +1 -1
- package/dist/esm/components/db-section-app-download.d.ts +2 -0
- package/dist/esm/components/db-section-app-download.js +1 -1
- package/dist/esm/components/db-section-contact.d.ts +2 -0
- package/dist/esm/components/db-section-contact.js +1 -1
- package/dist/esm/components/db-section-cta.d.ts +3 -0
- package/dist/esm/components/db-section-cta.js +1 -1
- package/dist/esm/components/db-section-faq.d.ts +2 -0
- package/dist/esm/components/db-section-faq.js +1 -1
- package/dist/esm/components/db-section-feature.d.ts +3 -0
- package/dist/esm/components/db-section-feature.js +1 -1
- package/dist/esm/components/db-section-hero.d.ts +2 -0
- package/dist/esm/components/db-section-hero.js +1 -1
- package/dist/esm/components/db-section-logo-cloud.d.ts +2 -0
- package/dist/esm/components/db-section-logo-cloud.js +1 -1
- package/dist/esm/components/db-section-pricing.d.ts +3 -0
- package/dist/esm/components/db-section-pricing.js +1 -1
- package/dist/esm/components/db-section-stats.d.ts +2 -0
- package/dist/esm/components/db-section-stats.js +1 -1
- package/dist/esm/components/db-section-testimonials.d.ts +4 -0
- package/dist/esm/components/db-section-testimonials.js +1 -1
- package/dist/esm/components/db-section.d.ts +2 -0
- package/dist/esm/components/db-section.js +1 -1
- package/dist/esm/components/db-share.d.ts +5 -0
- package/dist/esm/components/db-share.js +1 -1
- package/dist/esm/components/db-sidebar.d.ts +7 -0
- package/dist/esm/components/db-sidebar.js +1 -1
- package/dist/esm/components/db-stat.d.ts +2 -0
- package/dist/esm/components/db-stat.js +1 -1
- package/dist/esm/components/db-table.d.ts +5 -0
- package/dist/esm/components/db-table.js +1 -1
- package/dist/esm/components/db-testimonial.d.ts +3 -0
- package/dist/esm/components/db-testimonial.js +1 -1
- package/dist/esm/components/db-time-picker.d.ts +1 -0
- package/dist/esm/components/db-time-picker.js +1 -1
- package/dist/esm/components/db-top-banner.d.ts +3 -0
- package/dist/esm/components/db-top-banner.js +1 -1
- package/dist/esm/components/db-tutorial.d.ts +3 -0
- package/dist/esm/components/db-tutorial.js +1 -1
- package/dist/esm/components/db-video-timeline.d.ts +34 -2
- package/dist/esm/components/db-video-timeline.js +1 -1
- package/dist/esm/components/db-world-map.d.ts +5 -0
- package/dist/esm/components/media-editor-model.d.ts +10 -1
- package/dist/esm/core/i18n-en.js +1 -1
- package/dist/esm/core/i18n.d.ts +11 -0
- package/dist/esm/core/i18n.js +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +619 -486
- package/dist/index.esm.js +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +47 -3
- package/dist/react/index.esm.js +1 -1
- package/docs/ACCORDION.md +17 -0
- package/docs/ACHIEVEMENTS.md +15 -0
- package/docs/AI-ELEMENTS.md +86 -0
- package/docs/APP-UPDATE.md +60 -0
- package/docs/ATTACHMENTS.md +32 -0
- package/docs/BUTTON.md +17 -0
- package/docs/CDN-GUIDE.md +82 -0
- package/docs/COMPONENT-CHECKLIST.md +98 -0
- package/docs/CONTEXT-MENU.md +22 -0
- package/docs/CREDIT-AI.md +136 -0
- package/docs/CREDIT-CARD.md +52 -0
- package/docs/CROSS_PLATFORM_COMPONENT_STRATEGY.md +81 -0
- package/docs/EASING-EDITOR.md +42 -0
- package/docs/EFFECT-KEYFRAMES.md +60 -0
- package/docs/LAYER-LIST.md +31 -0
- package/docs/MEDIA-EDITORS.md +63 -0
- package/docs/MODULE-REUSE.md +43 -0
- package/docs/MULTI-PLATFORM-GUIDE.md +246 -0
- package/docs/NOTIFICATIONS.md +23 -0
- package/docs/PAPER-CARDS.md +61 -0
- package/docs/QUALITY-GUIDELINES.md +196 -0
- package/docs/RANGE-SLIDER.md +29 -0
- package/docs/REACT-WC-INTEGRATION.md +96 -0
- package/docs/RECENT-COMPONENTS-QUALITY.md +96 -0
- package/docs/SELECT.md +26 -0
- package/docs/SELECTION-CONTROLS.md +33 -0
- package/docs/SITE-SHELL.md +37 -0
- package/docs/STYLING.md +60 -0
- package/docs/SUPPORT.md +39 -0
- package/docs/TABS.md +19 -0
- package/docs/TEXT-FIELDS.md +28 -0
- package/docs/TIME-PICKER-DEVELOPMENT.md +115 -0
- package/docs/TOGGLE.md +40 -0
- package/docs/TRAVEL.md +30 -0
- package/docs/UI-NATIVE.md +90 -0
- package/docs/VIDEO-EXPORT.md +12 -0
- package/docs/VOICE-INPUT.md +24 -0
- package/docs/VUE-ANGULAR-INTEGRATION.md +197 -0
- package/docs/WORDPRESS-INTEGRATION.md +250 -0
- package/docs/WORKSPACE.md +27 -0
- package/docs/quality/AXE-REPORT.md +35 -0
- package/docs/quality/CREDIT-AI-RELEASE-2026-10-18.md +34 -0
- package/docs/quality/ENUM-MIGRATION-WORKSHEET.md +288 -0
- package/docs/quality/SCORECARD.md +389 -0
- package/package.json +136 -130
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Accordion: expansion, content and ownership
|
|
2
|
+
|
|
3
|
+
Load tokens, theme and `@designbasekorea/ui-wc/dist/index.css`. React imports `Accordion` from `@designbasekorea/ui-wc/react`; Vanilla imports `DbAccordion` from `@designbasekorea/ui-wc/components/db-accordion`.
|
|
4
|
+
|
|
5
|
+
Use `items` with a title and optional string content. Unique stable item ids preserve trigger/panel identity, input values and focus through updates and reordering. Missing ids fall back to index strings; duplicate ids are disambiguated with #2, #3 suffixes. Each instance creates independent DOM ids; arbitrary item ids are never used as DOM selectors. Malformed JSON or invalid entries are ignored without executing HTML. Generated content is text, and changes replace the old fallback.
|
|
6
|
+
|
|
7
|
+
`expandMode` is multiple (default), single (at most one), or all (initially expanded). `allowMultiple` is a legacy alias for the already-default multiple mode. An explicit `expandedItems` array or JSON/comma-separated HTML attribute controls the opened keys; the getter returns the normalized actual keys. Empty [] closes all, even in all mode. Removing the attribute restores the mode's initial expansion. Programmatic updates do not emit user changes. User toggles emit one bubbling, composed db-change with `{ expandedItems: string[] }`. React onChange/onItemChange/onDbChange receive this event; nested controls' own db-change events are not mis-typed as expansion events. A controlled host must apply the new expandedItems back to state.
|
|
8
|
+
|
|
9
|
+
Enter/Space toggle native triggers. Tab skips disabled triggers and all descendants of collapsed panels. ArrowUp/Down and Home/End move among enabled triggers in current item order. Collapsing a panel that contains focus returns it to its trigger. A removed/disabled focused trigger falls back to a nearby enabled one. Styling updates retain current DOM/focus. Expanded content has no fixed 1000px cutoff.
|
|
10
|
+
|
|
11
|
+
Each trigger has aria-expanded and aria-controls; panels have their trigger's name and become inert/aria-hidden when collapsed. An authored aria-label or aria-labelledby names the outer region without being overwritten. Decorative prefixes/icons are hidden from assistive technology. Icon names come from the official icon registry. Invalid size/variant values fall back to m/default. Sizes s/m/l and variants default/bordered/separated/contained use shared contracts and semantic tokens. Long titles wrap and triggers have at least 44px targets. Reduced motion removes transitions.
|
|
12
|
+
|
|
13
|
+
For rich content, top-level children use slot="content-N". N is a zero-based item position. Vanilla keeps those nodes and their slot attributes, projects them into the current panel, and preserves temporarily unassigned nodes in an inert hidden stash. `refresh()` rechecks external slot structure without resetting expansion or emitting user events. Removing a slot node restores generated fallback content.
|
|
14
|
+
|
|
15
|
+
The React adapter uses portals: React owns projected children and their cleanup, while the native component owns panel chrome. It applies content/target changes together after native props update, so keyed children and live form state survive reorder/style updates without React removeChild errors. Removing rich content restores fallback text. Supply unique item ids and stable child keys when reordering. Native refs expose the real custom element, including focus() and refresh().
|
|
16
|
+
|
|
17
|
+
Accordion does not persist expansion or form data to a server. The host owns persistence, errors and any async content. Keep one expansion handler unless you intentionally need multiple aliases called for the same event.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# AchievementList / AchievementModal
|
|
2
|
+
|
|
3
|
+
획득 컬렉션 UI이며 기존 상태 라벨 db-badge와 구분한다. 공식 award/star/bulb/heart 아이콘과 theme 의미 토큰을 사용한다. 획득 조건 계산, 인증, 원격 저장, 보상 지급은 owner 책임이다.
|
|
4
|
+
|
|
5
|
+
- `db-achievement-list.items`: `{id,title,description,state,current?,target?,earnedLabel?,motif?}[]`; state locked/earned/needs_review. 배열은 복사하고 HTML 없이 안전한 텍스트를 표시한다.
|
|
6
|
+
- 목록 loading/error/empty-label; `db-select` detail `{id}`. 빈 목록/오류/로딩을 구분한다.
|
|
7
|
+
- `db-achievement-modal.items`: 첫 항목 대표, 나머지는 함께 받은 뱃지. open, mode=celebration|detail, loading, error, confirm-label.
|
|
8
|
+
- `db-dismiss` detail `{ids}`는 owner에게 저장/닫기를 요청한다. owner가 저장 성공 후 open=false; 실패 시 error를 표시하고 유지. loading에는 닫기 차단.
|
|
9
|
+
- 모달은 DbModal의 Escape/포커스 트랩/닫은 후 원래 포커스/스크롤 잠금을 사용한다. 네트워크·스토리지 접근은 없다. 획득 장식 애니메이션은 아래 고도화 규칙을 따른다.
|
|
10
|
+
|
|
11
|
+
React: `AchievementList`, `AchievementModal`, `DbAchievementItem`을 `@designbasekorea/ui-wc/react`에서 import. Web Component: `@designbasekorea/ui-wc/components/db-achievement-list`, `.../db-achievement-modal`을 import한 뒤 items 프로퍼티 할당. theme CSS와 `@designbasekorea/ui-wc/dist/index.css`가 필요하다.
|
|
12
|
+
|
|
13
|
+
예제/검증은 sibling starter `apps/badge-trial`, `tests/badges-browser`에 있다. 설치된 공개 버전에 이 API가 있다고 가정하지 말고 해당 snapshot의 built 타입/실행을 확인한다.
|
|
14
|
+
|
|
15
|
+
고도화: 목록 action은 secondary. 모달의 장식 헤더 없이 실제 뱃지 이름이 dialog 제목이며, 24px 균일 패딩·CSS 3D 코인·획득 시 한 번의 로컬 DbConfetti를 제공한다. 모달 밖 canvas는 만들지 않는다. 기본 CTA는 earned/needs_review '확인', locked '아직 획득하지 못했어요'. 확인은 지급 의도가 아니며 기존 confirm-label override는 유지한다. prefers-reduced-motion에서는 회전/콘페티가 꺼지고 close/disconnect 시 효과를 정리한다.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# AI Elements
|
|
2
|
+
|
|
3
|
+
에이전트의 대화·작업 추적·출처·실행 결과를 작은 요소로 조합합니다. 사용자가 제공한 [shadcn AI Elements](https://ui.shadcn.com/docs/ai-elements/aria/chain-of-thought)의 구조를 참고하며, 렌더링은 Designbase Button, Badge, CodeBlock, Spinner, Progressbar, Select, ChatComposer, ChatSuggestions 및 공식 db-icons를 사용합니다. 색상·간격·서체는 현재 theme의 의미 토큰을 소비합니다. React Aria/shadcn SDK를 설치하거나 그 API를 호환 구현한 것은 아닙니다.
|
|
4
|
+
|
|
5
|
+
## 모듈 선택
|
|
6
|
+
|
|
7
|
+
| 참고 요소 | WC / React | 역할 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Chain of Thought | db-ai-chain-of-thought / AiChainOfThought | 접이식 요약·중첩 작업 단계·코드·출처 |
|
|
10
|
+
| Chat | db-ai-conversation / AiConversation | 읽는 위치를 유지하는 대화 스크롤 |
|
|
11
|
+
| Checkpoint | db-ai-checkpoint / AiCheckpoint | 복원 위치와 복원 요청 |
|
|
12
|
+
| Confirmation | db-ai-confirmation / AiConfirmation | 승인·거절과 결정 결과 |
|
|
13
|
+
| Context | db-ai-context / AiContext | 제품이 전달한 토큰 사용량·비용 |
|
|
14
|
+
| File Tree | db-ai-file-tree / AiFileTree | 중첩 폴더와 파일 열기 요청 |
|
|
15
|
+
| Inline Citation | db-ai-inline-citation / AiInlineCitation | 본문 인용의 상세 출처 |
|
|
16
|
+
| Message | db-ai-message / AiMessage | 사용자 말풍선·AI 본문·응답 동작 |
|
|
17
|
+
| Model Selector | db-ai-model-select / AiModelSelect | 기존 모델 카탈로그 선택 |
|
|
18
|
+
| Plan | db-ai-plan / AiPlan | 사전에 정한 단계와 완료 수 |
|
|
19
|
+
| Prompt Input | db-chat-composer / ChatComposer | 기존 첨부·입력·전송·중지 |
|
|
20
|
+
| Queue | db-ai-queue / AiQueue | 대기 요청과 개별 제거 |
|
|
21
|
+
| Reasoning | db-ai-reasoning / AiReasoning | 제품이 제공하는 응답 요약 펼치기 |
|
|
22
|
+
| Sources | db-ai-sources / AiSources | 참고한 외부 링크 목록 |
|
|
23
|
+
| Stack Trace | db-ai-stack-trace / AiStackTrace | 오류·프레임 코드와 위치 |
|
|
24
|
+
| Subagent | db-ai-subagent / AiSubagent | 보조 작업의 상태·단계·취소 요청 |
|
|
25
|
+
| Suggestion | db-chat-suggestions / ChatSuggestions | 기존 추천 질문 선택 |
|
|
26
|
+
| Task | db-ai-task / AiTask | 실행 중 발견하는 단계별 작업 |
|
|
27
|
+
| Terminal | db-ai-terminal / AiTerminal | 명령과 출력 표시 |
|
|
28
|
+
| Test Results | db-ai-test-results / AiTestResults | 통과·실패·대기 상태와 실패 출력 |
|
|
29
|
+
| Tool | db-ai-tool / AiTool | 도구 호출의 입력·출력·오류 |
|
|
30
|
+
|
|
31
|
+
기존 AI Task Status, AiGlow, ChatOrb, ChatWelcome, AiChat 등도 갤러리의 **AI Elements**에 함께 있습니다. 기존 공개 export와 URL은 유지합니다. 모델·견적 API는 [CREDIT-AI](./CREDIT-AI.md)를 따릅니다.
|
|
32
|
+
|
|
33
|
+
## 시작
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import '@designbasekorea/tokens/dist/css/tokens.css';
|
|
37
|
+
import '@designbasekorea/theme/dist/css/theme.css';
|
|
38
|
+
import '@designbasekorea/ui-wc/dist/index.css';
|
|
39
|
+
import { AiConversation, AiMessage, AiChainOfThought, ChatComposer } from '@designbasekorea/ui-wc/react';
|
|
40
|
+
|
|
41
|
+
<AiConversation autoScroll>
|
|
42
|
+
<AiMessage from="user">배포가 오래 걸리는 원인을 찾아줘.</AiMessage>
|
|
43
|
+
<AiMessage from="assistant">
|
|
44
|
+
<AiChainOfThought status="running" label="배포 과정을 확인하고 있어요" items={[
|
|
45
|
+
{id:'logs',label:'빌드 로그 확인',status:'complete',code:'images 3m 52s',open:true},
|
|
46
|
+
{id:'cache',label:'캐시 설정 확인 중',status:'running',description:'이미지 캐시 키를 살펴봅니다.'},
|
|
47
|
+
]}/>
|
|
48
|
+
</AiMessage>
|
|
49
|
+
</AiConversation>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Vanilla에서는 루트 모듈 또는 `@designbasekorea/ui-wc/components/db-ai-chain-of-thought`를 import한 뒤 `element.items = [...]`를 할당합니다. JSON `items` 속성도 지원합니다. React wrapper는 배열을 property로 전달합니다. 실행 상태 갱신 중 사용자가 접은 단계와 요약 포커스를 유지합니다. `open` 속성을 명시적으로 변경하면 루트 펼침을 제어할 수 있습니다.
|
|
53
|
+
|
|
54
|
+
`DbAiItem`: 필수 `id`, `label`; 선택 `status`, `description`, `code`, `language`, `href`, `meta`, `children`, `open`. 형제 ID는 고유해야 합니다. 작업 단계의 children은 출처 목록, FileTree의 children은 하위 경로입니다. status는 `pending | running | complete | error | cancelled | approval`입니다. 앱이 실행 상태를 전달하며 시간·진행률·비용을 임의로 생성하지 않습니다. Reasoning에는 사용자에게 공개할 수 있는 요약만 전달합니다.
|
|
55
|
+
|
|
56
|
+
## 속성과 이벤트
|
|
57
|
+
|
|
58
|
+
데이터 요소 공통: `items`, `label`, `description`, `status`, `open`, `disabled`. React는 `AiElementProps`를 공유합니다. `db-open-change` detail은 `{id, open}`이며 루트 id는 `root`, 작업 상세는 `item:<id>`입니다.
|
|
59
|
+
|
|
60
|
+
| 요소 | 추가 입력 | 이벤트 / React |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| Checkpoint | item-id, action-label | db-restore / onDbRestore |
|
|
63
|
+
| Confirmation | item-id, code, language, decision=accepted/rejected | db-confirm, db-reject / onDbConfirm, onDbReject |
|
|
64
|
+
| Context | used, limit, cost-label | 표시·펼침 |
|
|
65
|
+
| FileTree, StackTrace | items | db-file-open / onDbFileOpen |
|
|
66
|
+
| Plan | action-label | db-execute / onDbExecute |
|
|
67
|
+
| Queue | items | db-remove / onDbRemove |
|
|
68
|
+
| Subagent | action-label | db-cancel / onDbCancel |
|
|
69
|
+
| Tool | input, output, language | 표시·펼침 |
|
|
70
|
+
| Terminal | input(명령), output(로그) | 표시·펼침 |
|
|
71
|
+
| Message | from=user/assistant, streaming, actions=copy,regenerate,like,dislike | db-copy, db-copy-error, db-regenerate, db-like, db-dislike |
|
|
72
|
+
| Conversation | auto-scroll, label | 과거 응답 읽기 중 위치 보존·최신 응답 버튼 |
|
|
73
|
+
|
|
74
|
+
작업 요청 detail은 `{id, action}`입니다. 승인·복원·취소 이벤트가 실제 실행을 수행하지 않습니다. 앱에서 서버 상태를 갱신하고 결정 결과를 다시 전달합니다. 복원 시에는 저장한 메시지/파일의 실제 상태를 앱이 복원해야 합니다. Terminal은 로그를 렌더할 뿐 명령을 실행하지 않습니다. FileTree/StackTrace도 경로 이벤트를 내보낼 뿐 로컬 파일을 읽지 않습니다.
|
|
75
|
+
|
|
76
|
+
Message 복사는 브라우저 clipboard API를 사용하며 실패 시 `db-copy-error`를 발행합니다. 추천 질문과 입력의 이벤트는 기존 ChatSuggestions/ChatComposer 계약을 유지합니다. 토큰 사용량이 없거나 잘못되면 Context는 ‘확인 불가’를 표시합니다.
|
|
77
|
+
|
|
78
|
+
## 예제와 검증
|
|
79
|
+
|
|
80
|
+
갤러리 `/components/aielements`는 전체 조합과 21개 대응 요소로 연결합니다. 각 요소의 `/components/ai…` 페이지와 `/preview/…`에서 개별 동작과 사용 코드를 확인합니다. 로컬 예제는 스크립트된 응답이며 실제 모델을 호출하지 않습니다.
|
|
81
|
+
|
|
82
|
+
`npm run build --prefix packages/ui-wc -- --forceExit` 후 갤러리를 localhost:5176에서 켜고 `node scripts/test-ai-elements.mjs` 및 `node scripts/test-ai-elements-live-updates.mjs`를 실행합니다. 별도 preview 서버를 사용하면 `AI_GALLERY_URL=http://127.0.0.1:5181`을 지정합니다. built WC와 React 예제는 별도 경로로 검증합니다. 신규 의존성·프로바이더·인증·DB·크레딧 정산은 포함하지 않습니다.
|
|
83
|
+
|
|
84
|
+
사용자 밀도 피드백 반영: AI 본문은 text-s, 상태 행 28px·파일 행 26px, 아이콘 14px 기준이다. 파일 트리의 외곽 프레임을 없애고 기존 ghost Button으로 파일을 표시한다. 들여쓰기와 예제 여백도 줄인다.
|
|
85
|
+
|
|
86
|
+
세부 간격 변수·모바일·포커스 보완은 [최근 모듈 품질 계약](./RECENT-COMPONENTS-QUALITY.md)을 따른다.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# AppUpdate · 사이드바 OTA 안내
|
|
2
|
+
|
|
3
|
+
`db-app-update` / React `AppUpdate`는 사이드바에 넣는 얇은 안내 바다. 짧은 버전과 오른쪽 chevron을 표시하며, 클릭하면 기존 Modal을 조합한 작은 대화상자를 연다. 기존 Button·Progressbar·Spinner·Toast 및 의미 토큰을 사용한다.
|
|
4
|
+
|
|
5
|
+
## 기본 흐름
|
|
6
|
+
|
|
7
|
+
1. `available`: 안내 바를 눌러 모달을 연다. 버전과 “주요 변경 내용” 목록을 먼저 표시한다. “완료 후 자동으로 다시 시작됩니다.”는 배경이 있는 주요 변경 내용 영역의 목록 바로 아래 짧은 보조 안내로 표시한다.
|
|
8
|
+
2. 업데이트 버튼은 `db-update`를 요청한다. 호스트가 저장과 실제 다운로드를 시작하고 `downloading`을 연결한다. 진행 중에는 닫기·Escape·바깥 클릭을 차단한다.
|
|
9
|
+
3. 다운로드·무결성 검증이 끝나면 호스트가 `ready`로 전환한다. 승인된 다운로드 흐름에서는 `db-restart`가 **한 번 자동 발생**한다. 재시작 버튼을 다시 누르지 않는다. 이미 다운로드된 ready로 처음 마운트한 경우에는 업데이트 버튼으로 사용자 승인을 받는다.
|
|
10
|
+
4. 호스트가 엔진의 적용·재시작을 실행하고 `applying`을 연결한다. UI 자체는 페이지를 reload하지 않는다.
|
|
11
|
+
5. **실제 재실행 후 새 버전의 정상 시작을 확인한 뒤** `complete`로 연결한다. 모달과 안내 바는 사라지고 기존 성공 Toast만 4초 표시한다. 완료 확인창·확인 버튼은 없다. complete 상태로 새로 마운트해도 모달 없이 토스트를 표시한다.
|
|
12
|
+
|
|
13
|
+
## 상태 / 요청 이벤트
|
|
14
|
+
|
|
15
|
+
| status | 표현 / 액션 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| checking | 확인 중, 조작 없음 |
|
|
18
|
+
| up-to-date | 최신 버전 안내 |
|
|
19
|
+
| available | 업데이트 / 나중에 |
|
|
20
|
+
| downloading | 실제 진행률 또는 퍼센트 없는 진행 안내 |
|
|
21
|
+
| ready | 승인된 다운로드 뒤 자동 db-restart; 최초 준비 상태는 업데이트 승인 버튼 |
|
|
22
|
+
| applying | 자동 재시작·적용 안내 |
|
|
23
|
+
| complete | 완료 토스트만, 완료 모달 없음 |
|
|
24
|
+
| error / offline | 구체적 오류·연결 안내와 재시도 |
|
|
25
|
+
|
|
26
|
+
이벤트는 `db-open`, `db-close`, `db-update`, `db-restart`, `db-retry`, `db-defer`, `db-dismiss`, `db-complete`. 모두 bubbles/composed이며 detail은 `{ status, version }`이다. `db-complete`는 호스트가 검증한 complete를 전달받아 토스트를 표시했다는 알림이다. 엔진 작업이 끝났다는 자체 판단 이벤트가 아니다.
|
|
27
|
+
|
|
28
|
+
- `open`: 모달 열림. React에서 제어할 때 `onDbOpen`과 `onDbClose`에서 호스트 상태를 동기화한다.
|
|
29
|
+
- `hide-trigger`: 자체 바를 숨기고 외부 `Sidebar.updateBanner` 등과 연결할 때 사용한다.
|
|
30
|
+
- `version`, `current-version`: 표시용 버전. 실제 실행 중인 업데이트 식별자와는 별도로 검증한다.
|
|
31
|
+
- `progress`: 0–100. 생략/null/비정상 값은 미확인. **100%는 complete가 아니다.**
|
|
32
|
+
- `mandatory`: 미루기와 모달 닫기를 차단한다. 진행 중에는 mandatory와 무관하게 닫기를 차단한다. 앱의 최소 버전·사용 제한 정책은 호스트가 관리한다.
|
|
33
|
+
- `pending`: 엔진 요청 수락 대기 중 버튼 비활성화. 호스트도 중복 다운로드·재시작을 막아야 한다.
|
|
34
|
+
- `retryable="false"`: 복구 불가 오류의 재시도 숨김. 필수 오류에서 호스트는 스토어·지원 등 회복 경로를 제공해야 한다.
|
|
35
|
+
- `release-notes`, `error-text`: 일반 텍스트. HTML을 실행하지 않는다.
|
|
36
|
+
- 한국어/영어 및 기존 `dbSetLocale` 사용자 문자열 재정의 (`appUpdate.*`)를 지원한다.
|
|
37
|
+
|
|
38
|
+
## 사이드바 연결
|
|
39
|
+
|
|
40
|
+
`Sidebar`의 `nav-bottom` 슬롯에 AppUpdate를 넣으면 자체 안내 바를 프로필 위에 배치할 수 있다. 기존 `Sidebar.updateBanner`를 유지할 경우에는 AppUpdate의 `hideTrigger`를 켜고 `onDbUpdateCtaClick`에서 `open`을 true로 설정한다. 기존 사이드바의 색상·폰트·모서리 스타일을 바꾸지 않는다.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<AppUpdate
|
|
44
|
+
status={engineStatus}
|
|
45
|
+
version={targetVersion}
|
|
46
|
+
currentVersion={runningVersion}
|
|
47
|
+
progress={downloadProgress}
|
|
48
|
+
pending={pending}
|
|
49
|
+
onDbUpdate={saveThenDownload}
|
|
50
|
+
onDbRestart={applyAndRestart}
|
|
51
|
+
onDbRetry={retryFailedStage}
|
|
52
|
+
onDbComplete={rememberNotifiedVersion}
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 재실행 경계
|
|
57
|
+
|
|
58
|
+
재시작 전에 적용 대상 식별자·대기 상태를 앱 저장소에 기록한다. 다음 실행에서 엔진의 실제 실행 버전과 정상 시작 결과를 대조한 뒤 complete를 전달한다. `db-complete`에서 버전별 안내 확인 상태를 앱 저장소에 기록해 이후 실행마다 토스트가 반복되지 않게 한다. 같은 인스턴스에서는 동일 버전 토스트를 한 번만 띄운다. 토스트의 수명은 안내 바와 분리되어 있어 호스트가 컴포넌트를 제거해도 자동 만료된다.
|
|
59
|
+
|
|
60
|
+
실패하면 error/offline로 전환한다. 복구가 실제 확인된 경우에만 `error-text`에 이전 버전 복구 문구를 넣는다. 이 컴포넌트는 OTA SDK·실제 저장·다운로드·재실행·health check를 구현하지 않는다. 갤러리는 이 과정을 시뮬레이션한다.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# AttachmentList
|
|
2
|
+
|
|
3
|
+
Additive source API, paired with the existing Dropzone. No dependencies were added. Existing FileUploader and its simulation semantics are unchanged. This is controlled presentation; no upload, timer, URL, storage credential or persisted file is owned by AttachmentList.
|
|
4
|
+
|
|
5
|
+
## React
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<AttachmentList
|
|
9
|
+
attachments={attachments}
|
|
10
|
+
onDbCancel={event => requestCancel(event.detail.id)}
|
|
11
|
+
onDbRetry={event => requestRetry(event.detail.id)}
|
|
12
|
+
onDbRemove={event => removeFromList(event.detail.id)}
|
|
13
|
+
onDbOpen={event => inspectAttachment(event.detail.id)}
|
|
14
|
+
onDbReload={() => reloadAttachments()}
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
WC tag: `db-attachment-list`. Set the `attachments` **property**, not a JSON attribute. Items are cloned. Each item has id, name, bytes (nonnegative safe integer), state (queued/uploading/processing/ready/error/cancelled), optional progress (0..100 or null/undefined), error, canOpen, canRetry. Duplicate IDs and malformed entries are ignored. Text is not rendered as HTML. Optional label/loading/disabled/error are reflected attributes or React props. The error state preserves rows and enables a list reload intent while blocking row intents.
|
|
19
|
+
|
|
20
|
+
`db-cancel/db-retry/db-remove/db-open` emit `{id}` detail; `db-reload` has no payload. Cancel is available during queued/uploading. Retry requires error/cancelled and canRetry. Open requires ready and canOpen. Remove is offered for ready/error/cancelled only, and refers to list removal rather than server deletion. Loading/disabled block all actions. A progress value of 100 never changes state to ready. Unavailable progress is shown as unknown and does not use a false zero-valued bar.
|
|
21
|
+
|
|
22
|
+
Files, retry payloads, upload cancellation, idempotency, server confirmation, storage authorization and signed URLs belong to the consuming product. The product must handle late responses, unmount and account/workspace changes. UI filename/MIME/size checks do not verify actual bytes or grant storage access. Server processing is deliberately distinct from transport progress.
|
|
23
|
+
|
|
24
|
+
## Local Recipe
|
|
25
|
+
|
|
26
|
+
`examples/ui-wc-react/src/demos/wc/attachment-example.tsx` composes actual Dropzone, AttachmentList, Button, Checkbox and Modal. Client policy allows PNG/JPG/PDF/TXT, >0..2MB per file, 5 total, duplicate name+size rejection. These are example policy choices. Dropzone's silent accept/max-size filters are omitted so the Recipe can explain rejected files. It does not create preview/download URLs or transmit file bytes. Metadata Modal is explicit about this limitation.
|
|
27
|
+
|
|
28
|
+
Local mock progress/failure uses run tokens. Cancel/retry/reset invalidates old callbacks; File objects survive failure/cancel until removal/reset. Unmount clears timers. Reload resets all local state. Real remote cancellation and resumable uploads are unverified.
|
|
29
|
+
|
|
30
|
+
Source API and independent frozen production example are separate from an installed release. Starter r2 snapshot is unchanged. Generated standalone declarations can be checked independently; full package build/installation and the known Rollup CLI termination issue remain separate. No release/publish claim.
|
|
31
|
+
|
|
32
|
+
세부 간격 변수·모바일·포커스 보완은 [최근 모듈 품질 계약](./RECENT-COMPONENTS-QUALITY.md)을 따른다.
|
package/docs/BUTTON.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Button: actions, links and forms
|
|
2
|
+
|
|
3
|
+
Import the tokens, theme and `@designbasekorea/ui-wc/dist/index.css`. React uses `Button` from `@designbasekorea/ui-wc/react`; Vanilla imports `DbButton` from `@designbasekorea/ui-wc/components/db-button`.
|
|
4
|
+
|
|
5
|
+
The default type is `button`, including invalid type strings. `type="submit"` runs native form validation and submission, and `type="reset"` restores the form's defaults. `form="form-id"` supports a form outside the button's ancestors. Name/value are submitted only for the activated submit button. `formAction`, `formMethod`, `formEnctype`, `formTarget` and `formNoValidate` (HTML: lowercase native attribute names) forward to the native submitter. Disabled fieldsets, including the first legend exception, follow browser form association.
|
|
6
|
+
|
|
7
|
+
A custom element cannot be passed to `requestSubmit` as a native submitter. Button creates a temporary hidden native button, calls `requestSubmit(submitter)`, then removes it. A separate hidden native button inside each type=submit host lets implicit Enter submission in multi-input forms use the same cancelable activation; it is disabled or removed with the host state/mode. SubmitEvent.submitter is the temporary native proxy with `data-db-submitter`, not the db-button host. Use `new FormData(form, event.submitter)` when collecting data in a submit handler and you want the activated button's name/value. For React, read `(event.nativeEvent as SubmitEvent).submitter`. The browser's normal submission also includes those fields. Invalid forms do not submit; canceled submit/reset events retain native behavior.
|
|
8
|
+
|
|
9
|
+
`db-click` bubbles, crosses boundaries and is cancelable; React `onPress`/`onDbClick` receive it. React `onClick` retains its ordinary native click semantics. Calling preventDefault on either event cancels navigation/submission/reset. Default actions run in the next task, after bubbling handlers have had the chance to cancel them. Do not add another requestSubmit handler to a type=submit button: handle submission on the form. If your existing manual handler performs the action itself, retain type=button or preventDefault on the activation event.
|
|
10
|
+
|
|
11
|
+
Enter activates once per press. Space activates on keyup; blur, disable, loading or disconnect cancels a pending press. Readable text names the button. Icon-only buttons need aria-label or aria-labelledby. Disabled/loading buttons block activation and leave tab navigation. Author tabindex is restored on re-enable, including tabindex=-1. Busy state uses aria-busy. loadingText is displayed while loading without replacing framework-owned children; restoring or changing children remains safe. Explicit accessible labels take precedence.
|
|
12
|
+
|
|
13
|
+
A safe href gives the host link semantics and native anchor navigation (target, rel, download and modifier keys). Space scrolls rather than activating a link; Enter activates it. Unsafe execution schemes are rejected. Links opened in a new tab use noopener/noreferrer. Child interactive controls should not be nested in a Button; if present, their own clicks are not reinterpreted as a second button action.
|
|
14
|
+
|
|
15
|
+
Variants: primary, secondary, tertiary, destructive, ghost, accent. Legacy danger aliases destructive. Sizes: s/m/l. Radius and accentStyle use the shared contracts. Accent styles: solid, gradient, backlight, outline, prism. User classes and semantic CSS token overrides remain intact. Long labels wrap; coarse pointers have at least 44px targets, including small icon buttons. Reduced motion follows the shared theme.
|
|
16
|
+
|
|
17
|
+
Button does not save to a server. Your host owns the submit handler, async pending/error state and persistence. See the copied forms guide for a local FormData example, then connect it to your API.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!-- Generated from docs/CDN-GUIDE.md by scripts/sync-package-docs.mjs. -->
|
|
2
|
+
# CDN 사용 가이드
|
|
3
|
+
|
|
4
|
+
빌드 도구 없이 `<script type="module">` 만으로 `@designbasekorea/ui-wc` 를 쓰는 방법입니다.
|
|
5
|
+
npm 에 배포된 패키지는 unpkg / jsDelivr 로 바로 사용할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
> 프레임워크(React·Vue·Angular)와 함께 쓰려면 [멀티플랫폼 가이드](./MULTI-PLATFORM-GUIDE.md) 를 보세요.
|
|
8
|
+
|
|
9
|
+
## 🚀 빠른 시작
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<!DOCTYPE html>
|
|
13
|
+
<html lang="ko" data-theme="light">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="UTF-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
17
|
+
<title>Designbase ui-wc</title>
|
|
18
|
+
|
|
19
|
+
<!-- CSS 순서: tokens → theme → ui-wc -->
|
|
20
|
+
<link rel="stylesheet" href="https://unpkg.com/@designbasekorea/tokens@latest/dist/css/tokens.css">
|
|
21
|
+
<link rel="stylesheet" href="https://unpkg.com/@designbasekorea/theme@latest/dist/css/theme.css">
|
|
22
|
+
<link rel="stylesheet" href="https://unpkg.com/@designbasekorea/ui-wc@latest/dist/index.css">
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<db-button variant="primary" id="save">저장</db-button>
|
|
26
|
+
<db-input label="이메일" placeholder="you@example.com"></db-input>
|
|
27
|
+
|
|
28
|
+
<!-- 모든 db-* 컴포넌트 등록 (의존성 없는 단일 ESM) -->
|
|
29
|
+
<script type="module" src="https://unpkg.com/@designbasekorea/ui-wc@latest/dist/index.esm.js"></script>
|
|
30
|
+
<script type="module">
|
|
31
|
+
document.getElementById('save').addEventListener('db-click', () => alert('저장'));
|
|
32
|
+
</script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`dist/index.esm.js` 는 외부 의존성이 없는 자체 완결 번들이라 import map 이 필요 없습니다.
|
|
38
|
+
이벤트 이름은 `db-*` kebab-case 입니다 (예: `db-click`, `db-change`, `db-input`).
|
|
39
|
+
|
|
40
|
+
## 📦 CDN 링크
|
|
41
|
+
|
|
42
|
+
| 용도 | URL |
|
|
43
|
+
|------|-----|
|
|
44
|
+
| 토큰 CSS | `https://unpkg.com/@designbasekorea/tokens@latest/dist/css/tokens.css` |
|
|
45
|
+
| 테마 CSS | `https://unpkg.com/@designbasekorea/theme@latest/dist/css/theme.css` |
|
|
46
|
+
| ui-wc CSS | `https://unpkg.com/@designbasekorea/ui-wc@latest/dist/index.css` |
|
|
47
|
+
| ui-wc 전체 등록 (ESM) | `https://unpkg.com/@designbasekorea/ui-wc@latest/dist/index.esm.js` |
|
|
48
|
+
| 개별 컴포넌트 (ESM) | `https://unpkg.com/@designbasekorea/ui-wc@latest/dist/esm/components/db-button.js` |
|
|
49
|
+
| 아이콘 웹폰트 (선택) | `https://unpkg.com/@designbasekorea/icons-webfont@latest/dist/css/designbase-icons.css` |
|
|
50
|
+
|
|
51
|
+
jsDelivr 는 호스트만 `https://cdn.jsdelivr.net/npm/<패키지>@<버전>/...` 로 바꾸면 됩니다.
|
|
52
|
+
|
|
53
|
+
## ✂️ 필요한 컴포넌트만 로드
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<script type="module">
|
|
57
|
+
import 'https://unpkg.com/@designbasekorea/ui-wc@latest/dist/esm/components/db-button.js';
|
|
58
|
+
import 'https://unpkg.com/@designbasekorea/ui-wc@latest/dist/esm/components/db-modal.js';
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 🌗 다크 모드
|
|
63
|
+
|
|
64
|
+
`<html data-theme="dark">` 로 전환합니다. 사용자 선호를 따르려면:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
|
|
68
|
+
document.documentElement.dataset.theme = prefersDark ? 'dark' : 'light';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## ⚠️ 주의사항
|
|
72
|
+
|
|
73
|
+
- **버전 고정**: 운영 환경에서는 `@latest` 대신 `@0.8.12` 처럼 버전을 고정하세요. `latest` 는 캐시 때문에 갱신이 늦거나 의도치 않게 바뀔 수 있습니다.
|
|
74
|
+
- **CSS 순서**: tokens → theme → ui-wc 순서가 바뀌면 `--db-*` 변수가 적용되지 않습니다.
|
|
75
|
+
- **Light DOM**: 컴포넌트가 Shadow DOM 을 쓰지 않으므로 페이지 CSS 가 내부 `designbase-wc-*` 클래스에 영향을 줄 수 있습니다. 규칙은 [스타일 전략](./STYLING.md) 참고.
|
|
76
|
+
- **SRI**: 보안이 중요하면 버전 고정 후 `integrity` 속성을 함께 쓰세요.
|
|
77
|
+
|
|
78
|
+
## 🔗 관련 문서
|
|
79
|
+
|
|
80
|
+
- [ui-wc README](../README.md)
|
|
81
|
+
- [멀티플랫폼 가이드](./MULTI-PLATFORM-GUIDE.md)
|
|
82
|
+
- [WordPress 통합](./WORDPRESS-INTEGRATION.md)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 컴포넌트 체크리스트
|
|
2
|
+
|
|
3
|
+
컴포넌트를 새로 만들거나 기존 것을 고칠 때 **그대로 복사해서** PR 본문에 붙이고 채운다.
|
|
4
|
+
각 항목의 근거는 [QUALITY-GUIDELINES.md](./QUALITY-GUIDELINES.md) 의 같은 이름 규칙 ID.
|
|
5
|
+
|
|
6
|
+
자동 검사부터 돌리고 시작하면 손으로 확인할 항목이 줄어든다:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm run audit:quality -- --only db-<name>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 공통 (모든 컴포넌트)
|
|
15
|
+
|
|
16
|
+
### 접근성
|
|
17
|
+
- [ ] `A11Y-KEYBOARD` 마우스로 되는 모든 조작이 키보드로도 된다
|
|
18
|
+
- [ ] `A11Y-FOCUS-RING` 포커스 표시는 `:focus-visible` 에만, 링 색은 `--db-border-focus`
|
|
19
|
+
- [ ] `A11Y-NAME` 아이콘만 있는 컨트롤에 접근 가능한 이름이 있다
|
|
20
|
+
- [ ] `A11Y-DECORATIVE` 장식용 SVG/그래픽에 `aria-hidden="true"`
|
|
21
|
+
- [ ] `A11Y-STATE` 시각 상태가 ARIA 로도 표현된다 (`aria-expanded` / `aria-selected` / `aria-checked` / `aria-disabled` / `aria-busy` / `aria-invalid`)
|
|
22
|
+
- [ ] `A11Y-REDUCED-MOTION` 애니메이션이 `prefers-reduced-motion` 을 존중한다
|
|
23
|
+
- [ ] `A11Y-TARGET` 인터랙티브 타겟 24×24 CSS px 이상
|
|
24
|
+
|
|
25
|
+
### 웹 표준
|
|
26
|
+
- [ ] `STD-DEFINE` `db-` 접두사 + 중복 정의 가드 + `HTMLElementTagNameMap` 선언
|
|
27
|
+
- [ ] `STD-EVENT` 이벤트가 `db-*` kebab-case, `bubbles: true, composed: true`
|
|
28
|
+
- [ ] `STD-ATTR-PROP` attribute 로 넣어도, property 로 넣어도 같게 동작한다
|
|
29
|
+
- [ ] `STD-CONTRACT` enum 값이 `@designbasekorea/ui-contracts` 에서 온다
|
|
30
|
+
- [ ] `STD-COERCE` 잘못된 값이 들어와도 `coerceToContract()` 로 기본값 fallback
|
|
31
|
+
- [ ] `STD-NO-INNERHTML` 사용자 값이 `innerHTML` 문자열 보간에 들어가지 않는다
|
|
32
|
+
- [ ] `STD-LIGHT-DOM` 모든 클래스명에 `designbase-wc-` 접두사
|
|
33
|
+
|
|
34
|
+
### 성능
|
|
35
|
+
- [ ] `PERF-CLEANUP` 등록한 리스너/타이머/observer 를 `disconnectedCallback` 에서 전부 해제
|
|
36
|
+
- [ ] `PERF-GLOBAL-STATE` 전역 상태를 직접 만지지 않고 `core/` 유틸 경유
|
|
37
|
+
- [ ] `PERF-RECONCILE` 갱신 시 `innerHTML` 전체 재생성을 하지 않는다
|
|
38
|
+
- [ ] `PERF-RAF` scroll/resize/pointermove 를 rAF 로 병합, 스크롤은 `{ passive: true }`
|
|
39
|
+
|
|
40
|
+
### DX
|
|
41
|
+
- [ ] `DX-DOC-SYNC` README·`attrsRegistry.ts` 가 실제 `observedAttributes` 와 일치 (`npm run audit:docs`)
|
|
42
|
+
- [ ] `DX-UID` id 생성에 `dbUid()` 사용
|
|
43
|
+
- [ ] `DX-I18N` 노출 문자열이 `t()` 경유 (소스에 한국어 리터럴 없음)
|
|
44
|
+
- [ ] `DX-STORY` Storybook 스토리 존재
|
|
45
|
+
|
|
46
|
+
### UX
|
|
47
|
+
- [ ] `UX-STATES` 기본 / 로딩 / 비어있음 / 오류 상태가 정의됨 (해당하는 경우)
|
|
48
|
+
- [ ] `UX-MOTION` 전환 150~300ms
|
|
49
|
+
- [ ] `UX-FEEDBACK` 오래 걸리는 액션에 즉각 피드백
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 아키타입별 추가
|
|
54
|
+
|
|
55
|
+
### 오버레이 (`db-modal` `db-drawer` `db-bottom-sheet` `db-lightbox` `db-confirm` `db-popover` …)
|
|
56
|
+
- [ ] `A11Y-TRAP` `createFocusTrap()` 으로 Tab 가둠 + **닫을 때 직전 포커스 복원**
|
|
57
|
+
- [ ] `A11Y-DIALOG` `role="dialog"`(파괴적이면 `alertdialog`) + `aria-modal="true"` + `aria-labelledby`
|
|
58
|
+
- [ ] `A11Y-AUTOFOCUS` 열릴 때 `[autofocus]` → 첫 포커서블 → 컨테이너 순으로 포커스
|
|
59
|
+
- [ ] `A11Y-ESCAPE` Escape 로 닫힌다 (`close-on-escape` 로 끌 수 있게)
|
|
60
|
+
- [ ] `PERF-GLOBAL-STATE` 스크롤락은 `lockScroll()`/`unlockScroll()` (참조 카운트)
|
|
61
|
+
- [ ] 중첩 오버레이(모달 위 드로어)에서 닫기 순서가 올바르다
|
|
62
|
+
- [ ] `UX-DISMISS` 데이터 유실 위험이 있으면 바깥 클릭 닫기를 끈다
|
|
63
|
+
|
|
64
|
+
### 폼 컨트롤 (`db-input` `db-select` `db-checkbox` `db-toggle` `db-radio` `db-range-slider` …)
|
|
65
|
+
- [ ] `STD-FORM` 폼에 실제로 참여한다 — `name` 을 가진 네이티브 컨트롤을 렌더(1순위) 또는 `formAssociated` + `ElementInternals`(불가피할 때)
|
|
66
|
+
- [ ] `form.reset()` 후 값이 초기 상태로 돌아간다
|
|
67
|
+
- [ ] `form.checkValidity()` / `:invalid` 가 동작한다 (네이티브 컨트롤이면 자동, ElementInternals 면 `setValidity()`)
|
|
68
|
+
- [ ] `<label>` 연결이 동작한다 (`for` / 감싸기 둘 다)
|
|
69
|
+
- [ ] `aria-invalid` + `aria-describedby` 로 오류 메시지 연결
|
|
70
|
+
- [ ] `disabled` / `readonly` / `required` 가 ARIA 와 폼 양쪽에 반영
|
|
71
|
+
- [ ] `db-change` / `db-input` 이벤트가 네이티브와 같은 타이밍에 발생
|
|
72
|
+
|
|
73
|
+
### 복합 위젯 (`db-tabs` `db-select` `db-context-menu` `db-calendar` `db-stepper` …)
|
|
74
|
+
- [ ] `A11Y-APG` APG 패턴 준수 — 컨테이너 role + 항목 role + 관계 속성
|
|
75
|
+
- [ ] roving tabindex (선택된 항목만 `tabindex="0"`, 나머지 `-1`)
|
|
76
|
+
- [ ] 화살표 키 이동 + Home/End, 비활성 항목 건너뛰기
|
|
77
|
+
- [ ] 이동이 컨테이너 경계에서 순환하는지 / 멈추는지 명시적으로 결정
|
|
78
|
+
- [ ] 항목 id 가 `dbUid()` 기반이고 `aria-controls`/`aria-labelledby` 가 실제 존재하는 id 를 가리킨다
|
|
79
|
+
|
|
80
|
+
### 표시 전용 (`db-badge` `db-stat` `db-skeleton` `db-avatar` …)
|
|
81
|
+
- [ ] 불필요한 `tabindex` 가 없다
|
|
82
|
+
- [ ] 텍스트 대체가 있다 (아바타 이니셜, 스켈레톤 `aria-busy` 등)
|
|
83
|
+
- [ ] 의미 없는 role 을 붙이지 않았다
|
|
84
|
+
|
|
85
|
+
### 마케팅 섹션 (`db-section-*`)
|
|
86
|
+
- [ ] 제목 레벨(`h1`~`h3`)을 소비자가 지정할 수 있다 — 페이지 문서 개요를 깨지 않도록
|
|
87
|
+
- [ ] 배경 이미지/그래디언트가 텍스트 대비를 해치지 않는다
|
|
88
|
+
- [ ] 반응형 브레이크포인트에서 콘텐츠가 잘리지 않는다
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 마무리
|
|
93
|
+
|
|
94
|
+
- [ ] `npm run audit:quality` — 이 컴포넌트의 P0 위반 0건
|
|
95
|
+
- [ ] `npm run audit:axe` — critical/serious 위반 0건 (픽스처가 있는 경우)
|
|
96
|
+
- [ ] `npm run verify:docs` — 통과
|
|
97
|
+
- [ ] 라이트/다크 두 테마에서 시각 확인
|
|
98
|
+
- [ ] `docs/quality/SCORECARD.md` 점수가 개선됐다
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!-- Generated from docs/CONTEXT-MENU.md by scripts/sync-package-docs.mjs. -->
|
|
2
|
+
# Context Menu · nested actions
|
|
3
|
+
|
|
4
|
+
Canonical component: packages/ui-wc/src/components/db-context-menu.ts/scss. Items retain the existing shape: `{ id, label?, icon?, disabled?, divider?, variant?, subItems? }`. Add `subItems` to create a two-level flyout. This is composed with real MenuItem controls; the parent does not also render inline child items.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
menu.items = [
|
|
8
|
+
{ id: 'duplicate', label: 'Duplicate', icon: 'copy' },
|
|
9
|
+
{ id: 'easing', label: 'Easing', subItems: [
|
|
10
|
+
{ id: 'linear', label: 'Linear' },
|
|
11
|
+
{ id: 'ease-in', label: 'Ease in' },
|
|
12
|
+
] },
|
|
13
|
+
{ id: 'delete', label: 'Delete', icon: 'trash', variant: 'danger' },
|
|
14
|
+
];
|
|
15
|
+
menu.addEventListener('db-select', event => applyAction(event.detail.item.id));
|
|
16
|
+
// Optional imperative opening for editor surfaces:
|
|
17
|
+
menu.openAt(clientX, clientY);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Hover or click a branch to open it. Right Arrow opens/focuses the submenu, Left Arrow returns to its parent, Up/Down and Home/End move through enabled items, and Escape closes the current submenu or root. Disabled branches do not open. Flyouts flip and clamp to the viewport; on narrow screens they may overlap the parent so all child actions remain reachable. Theme scope follows the trigger.
|
|
21
|
+
|
|
22
|
+
The component catalog includes a Two-level menu example. Timeline effect keys use this same component for duplicate, delete, outgoing easing presets and undo, replacing the old inline easing bar. Native timeline history stores committed clip arrays; selection and marquee moves do not add history entries. See MEDIA-EDITORS.md for Alt-copy and undo/redo behavior.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# 크레딧과 AI 작업 UI
|
|
2
|
+
|
|
3
|
+
ui-wc 0.8.13 기반 개발 소스에 추가한 호환 기능이다. npm 배포 버전에는 아직 포함되지 않으며, 새 로컬 빌드 또는 다음 minor 릴리스가 필요하다. 기존 컴포넌트 API를 변경하지 않는다. 토큰·테마·ui-wc CSS 로딩 순서는 README를 따른다.
|
|
4
|
+
|
|
5
|
+
| 태그 / React export | 역할 | 주요 입력 | 이벤트 detail |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| `db-credit-balance` / `CreditBalance` | 가용 잔액·예약·누적 사용, 부족 안내, 충전 진입 | available, held, spent, low-threshold, recharge-label, compact, loading, error, disabled | `db-recharge` (없음) |
|
|
8
|
+
| `db-credit-recharge` / `CreditRecharge` | 충전 상품 선택과 결제 진입 | plans, selected-id, submit-label, description, loading, error, disabled | `db-plan-change`, `db-checkout`: `{planId}` |
|
|
9
|
+
| `db-ai-task-status` / `AiTaskStatus` | 대기·실행·완료·실패·취소·확인 대기 | task-id, state, progress, reserved, charged, cancel-label, retry-label, refresh-label, loading, disabled | `db-cancel`, `db-retry`, `db-refresh`: `{taskId}` |
|
|
10
|
+
|
|
11
|
+
모든 이벤트는 bubbles/composed이다. React에서는 `onDbRecharge`, `onDbPlanChange`, `onDbCheckout`, `onDbCancel`, `onDbRetry`, `onDbRefresh`로 받는다. 기존 wrapper의 camelCase→kebab-case 속성 변환을 사용한다. 개별 import는 `@designbasekorea/ui-wc/components/db-credit-balance` 등이며 기본 버튼·진행바·스피너 의존 등록을 포함한다.
|
|
12
|
+
|
|
13
|
+
## 표시 계약
|
|
14
|
+
|
|
15
|
+
- available은 서버가 계산한 **가용** 잔액이다. held를 UI에서 다시 빼지 않는다. 음수/소수/NaN/unsafe integer/누락은 0 대신 `—`로 표시한다. held/spent/reserved/charged도 비음수 정수다.
|
|
16
|
+
- low-threshold가 있을 때만 부족 안내를 표시한다. 단위는 unit으로 바꾼다. 충전 진입은 recharge-label이 있을 때 표시한다. 오류/로딩이면 잔액 값을 숨기고 충전을 막는다.
|
|
17
|
+
- plans는 `DbCreditPlan[]` property 또는 JSON 속성이다. `{id,label,credits,priceLabel,description?,badge?,disabled?}`. priceLabel은 앱에서 형식화한 표시 문자열이다. 잘못된 항목과 중복 id는 제외한다. 반환 배열은 복사본이다.
|
|
18
|
+
- 상품은 native radio의 Tab/Space/방향키로 선택한다. 선택 이벤트에서 소비자가 selected-id를 갱신하는 controlled 방식이다. 선택 거부 시 이전 선택을 유지한다. 빈 상품/유효하지 않거나 비활성인 선택은 결제를 막는다. loading 중 선택과 제출을 막고 error가 있어도 기존 선택은 보존한다. 오류 표시 자체는 재결제 허가가 아니므로 결제 확인 중에는 앱이 loading/disabled를 유지한다.
|
|
19
|
+
- state는 idle/queued/running/succeeded/failed/cancelled/needs_attention. 잘못된 값은 idle이다. 서버 진행률이 없으면 queued/running에 스피너를 표시한다. running의 유한 progress만 0~100으로 제한해 진행바에 표시한다. 시간 기반 가짜 진행률은 없다.
|
|
20
|
+
- 취소는 queued/running + cancel-label, 재시도는 failed + retry-label일 때 표시한다. needs_attention은 재생성·예약 해제를 유도하지 않고 refresh-label로 상태 확인만 제공한다. 요청 이벤트 후 자체 성공/취소/환불 상태를 만들지 않는다. 로딩은 동작 요청 잠금이고 작업 실행 상태와 별개다.
|
|
21
|
+
- 사용자 제공 문자열은 textContent로 표시한다. 기본 문구는 `dbSetLocale()`의 `credits.*`, `aiTask.*` 키로 교체한다. label/status-label/description/각 동작 라벨은 개별 지정할 수 있다.
|
|
22
|
+
|
|
23
|
+
## React 소비 예시
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
import { CreditBalance, CreditRecharge, AiTaskStatus, type DbCreditPlan } from '@designbasekorea/ui-wc/react';
|
|
28
|
+
|
|
29
|
+
function CreditSettings({ plans, available, held, task, openCheckout, openRecharge, refresh, cancel }) {
|
|
30
|
+
const [selectedId, setSelectedId] = useState('');
|
|
31
|
+
return <>
|
|
32
|
+
<CreditBalance available={available} held={held} rechargeLabel="충전하기"
|
|
33
|
+
onDbRecharge={openRecharge} />
|
|
34
|
+
<CreditRecharge plans={plans} selectedId={selectedId}
|
|
35
|
+
onDbPlanChange={event => setSelectedId(event.detail.planId)}
|
|
36
|
+
onDbCheckout={event => openCheckout(event.detail.planId)} />
|
|
37
|
+
<AiTaskStatus taskId={task.id} state={task.state} reserved={task.reserved_credits}
|
|
38
|
+
charged={task.charged_credits} cancelLabel="취소 요청" refreshLabel="상태 확인"
|
|
39
|
+
onDbCancel={event => cancel(event.detail.taskId)} onDbRefresh={refresh} />
|
|
40
|
+
</>;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
위 조립 예시는 제품의 타입·비동기 busy/error·초기 snapshot 흐름을 생략한 API 설명이다. 앱은 이벤트 처리 중 loading/disabled를 설정하고 새 서버 snapshot을 입력으로 전달한다. 실제 결제는 서버가 상품 id와 가격을 조회하고 결제 검증/멱등 정산 후에만 잔액에 반영한다. 컴포넌트가 표시하는 크레딧·상품 id·task id는 인가 근거가 아니다.
|
|
45
|
+
|
|
46
|
+
스타터의 기존 AiTextPanel(snapshot.available/held/spent, job.state/reserved_credits/charged_credits)에 그대로 매핑할 수 있다. 스타터의 고정 tarball/lockfile을 이번 작업에서 교체하지 않았으므로 소스 추가만으로 기존 앱에 새 export가 생기지는 않는다. 실제 SDK 연결 및 청구 흐름은 앱 Recipe/서버가 담당한다.
|
|
47
|
+
|
|
48
|
+
## 확인 경로
|
|
49
|
+
|
|
50
|
+
Storybook: Web Components/CreditBalance, CreditRecharge, AiTaskStatus.
|
|
51
|
+
독립 브라우저 specimen: `packages/ui-wc/test/fixtures/db-credit-ai.html` (라이브러리 루트를 정적 서버로 제공).
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npm run build --prefix packages/ui-wc
|
|
55
|
+
npm run test:credit-ai --prefix packages/ui-wc
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
당시 실행 근거와 제한은 [2026-10-18 검증 기록](./quality/CREDIT-AI-RELEASE-2026-10-18.md)을 따른다. 결제/외부 AI/실제 Supabase 시험을 포함하지 않는다. 현재 런타임 검증 범위는 [접근성 리포트](./quality/AXE-REPORT.md)와 [지원 범위](./SUPPORT.md)를 함께 확인한다.
|
|
59
|
+
|
|
60
|
+
## 헤더 드롭다운·충전 모달
|
|
61
|
+
|
|
62
|
+
`CreditDropdown` (`db-credit-dropdown`)과 `CreditRechargeModal` (`db-credit-recharge-modal`)은 각각 독립적으로 사용할 수 있다. example의 CreditDropdown/CreditRechargeModal 페이지에는 같은 실제 앱 헤더→드롭다운→모달 조립 예제가 있다.
|
|
63
|
+
|
|
64
|
+
- Dropdown: available/held/spent/low-threshold/unit/label/loading/error/disabled와 description/recharge-label/history-label/open. `db-open-change:{open}`, `db-recharge`, `db-history` 이벤트. native Popover로 기존 노드를 top layer에 유지하며 외부 클릭/Escape로 닫고, ArrowDown으로 열 수 있다. db-popover의 rich slot 복제 제약 때문에 별도 업무용 disclosure로 구현했다.
|
|
65
|
+
- Modal: plans/selected-id/available/unit/title/description/footnote/submit-label/open/loading/error/disabled. `db-plan-change:{planId}`, `db-checkout:{planId}`, `db-close`. 기존 db-modal의 포털/focus trap/scroll lock과 CreditRecharge를 조합한다. body와 footer를 먼저 만든 뒤 연다. loading 동안 중복 결제·닫기·Escape를 막으며 앱이 응답을 전달하면 해제한다. error만 있으면 선택을 보존하고 다시 시도할 수 있다. 확인이 끝나지 않은 결제는 앱이 loading을 유지해야 한다.
|
|
66
|
+
- 기존 CreditRecharge에 `selection-only`를 추가해 모달에서 중복 결제 버튼 없이 선택 부분만 조합한다. 기본 단독 표시/API는 유지한다.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const [open, setOpen] = useState(false);
|
|
70
|
+
const [selectedId, setSelectedId] = useState('light');
|
|
71
|
+
<>
|
|
72
|
+
<CreditDropdown available={snapshot.available} held={snapshot.held} spent={snapshot.spent}
|
|
73
|
+
rechargeLabel="크레딧 충전" onDbRecharge={() => setOpen(true)} />
|
|
74
|
+
<CreditRechargeModal open={open} plans={plans} selectedId={selectedId} available={snapshot.available}
|
|
75
|
+
onDbClose={() => setOpen(false)}
|
|
76
|
+
onDbPlanChange={(event: CustomEvent<{planId:string}>) => setSelectedId(event.detail.planId)}
|
|
77
|
+
onDbCheckout={(event: CustomEvent<{planId:string}>) => checkout(event.detail.planId)} />
|
|
78
|
+
</>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
스크린샷은 예제 데이터이며 크레딧 지급을 증명하지 않는다. 서버가 아직 연결되지 않은 갤러리/스타터 AI 시험의 결제 버튼은 안내 오류를 표시하고 잔액을 변경하지 않는다. 원본 starter의 실제 소비 반영은 별도 r2 스냅샷/manifest/lockfile과 ADR-035를 따른다. 최신 근거: `docs/evidence/CREDIT_OVERLAYS_2026-10-18.md`.
|
|
82
|
+
|
|
83
|
+
## 헤더 지갑·충전 전용 example
|
|
84
|
+
|
|
85
|
+
`examples/ui-wc-react/credit-wallet.html`은 CreditDropdown → CreditRechargeModal을 조립한 독립 React 예제다. 갤러리 서버(5180)의 `/credit-wallet.html`에서 확인한다. 잔액·상품·체크아웃 콜백은 소비 앱에서 주며 예제는 React 메모리 상태만 변경한다. 실제 결제/지갑 지급/AI 요청은 없다. `credit-balance`와 `credit-recharge` 갤러리에도 동일 조합이 등록돼 있다.
|
|
86
|
+
|
|
87
|
+
- 소스: `examples/ui-wc-react/src/demos/wc/credit-wallet-example.tsx`.
|
|
88
|
+
- 타입: `npx tsc -p examples/ui-wc-react/tsconfig.wallet.json --noEmit`.
|
|
89
|
+
- 독립 빌드: `npx vite build --config examples/ui-wc-react/vite.wallet.config.ts`.
|
|
90
|
+
- 실제 브라우저: 갤러리 서버 실행 후 `node scripts/test-credit-wallet.mjs`.
|
|
91
|
+
|
|
92
|
+
헤더 클릭/ArrowDown/Escape, 충전 모달 포커스 복귀, 예제 충전 완료와 실패 시 선택·잔액 보존, 390px/1280px를 검사한다. 실제 결제 승인이나 서버 크레딧 정산 검증과 구분한다. 스크린샷·기록은 `docs/evidence/credit-wallet-2026-10-18/`에 있다. 전체 갤러리의 기존 React/Lottie 타입 오류는 전용 example의 검사와 별개다.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## PlanOverview / CreditHistory — 개발 소스 추가 (2026-10-18)
|
|
96
|
+
|
|
97
|
+
`PlanOverview`는 현재 플랜 이름·표시 가격·이번 기간 사용량/한도와 `features: {id,label,allowed}[]`를 표시한다. `onDbManage`는 제품의 플랜 관리 화면을 여는 의도이며 구독 변경·권한 발급을 하지 않는다. 한도 미제공은 `—`이며 무제한/0으로 추정하지 않는다. 한도 초과는 숫자 그대로, 막대만 최대값으로 제한한다. loading/error 중에는 권한 목록을 숨기며 error에서 `onDbRetry`를 제공한다.
|
|
98
|
+
|
|
99
|
+
`CreditHistory`는 `entries: {id,label,dateLabel,kind,amount}[]`를 받는다. kind는 grant/charge/hold/release이며 amount는 음수가 아닌 안전 정수 또는 null이다. null은 확인 중으로 표시하고 예약을 차감으로 표시하지 않는다. 부분 내역을 합산해 잔액을 계산하지 않는다. label/dateLabel은 텍스트이며 HTML을 실행하지 않는다. 중복 id·잘못된 금액은 표시에서 제외한다. loading/error/empty와 `onDbRetry`를 제공한다. 날짜는 소비 앱에서 명시적 시간대·언어로 포맷한다.
|
|
100
|
+
|
|
101
|
+
HTML 소비는 배열을 `.features`/`.entries` 프로퍼티로 전달한다. React는 같은 배열 props와 onDbManage/onDbRetry를 사용한다. 기본 문자열은 dbSetLocale로 덮어쓸 수 있다. 새로운 runtime dependency 없음. 기존 API 삭제 없음. 아직 스타터의 고정 r2 snapshot에는 포함되지 않으며, 소스 example·빌드 출력으로 검증한다. 공개 npm 발행 없음.
|
|
102
|
+
|
|
103
|
+
`http://127.0.0.1:5180/credit-wallet.html`에서 플랜 전환(로컬 예제), 헤더→내역, 전체/사용/예약 필터, 상태 체험, 모의 충전 기록을 확인한다. 기존 `SectionPricing`은 마케팅 CTA/가격 카드로 유지하며, 현재 계정의 상태·한도·권한 표시를 그곳에 결합하지 않는다.
|
|
104
|
+
|
|
105
|
+
검증: `node scripts/test-plan-history.mjs`, `node scripts/test-credit-wallet.mjs`, `npx tsc -p examples/ui-wc-react/tsconfig.wallet.json --noEmit`, `npx vite build --config examples/ui-wc-react/vite.wallet.config.ts`.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## AiModelSelect / AiExecutionQuote — 개발 소스 추가 (2026-10-18)
|
|
109
|
+
|
|
110
|
+
`AiModelSelect`는 실제 DbSelect를 조합한다. `models: {id,label,description,capabilities,usageLabel,disabled?,disabledReason?}[]`와 `selectedId`를 받고 `onDbModelChange({detail:{modelId}})` 의도만 발행한다. 제품의 서버 allowlist에서 받은 목록을 넘기며 모델 이름으로 권한을 추정하지 않는다. 잘못된/중복 id는 제외하고 선택된 항목이 없으면 placeholder를 표시한다. loading/error/empty를 지원하며 기존 Select의 키보드 동작을 유지한다.
|
|
111
|
+
|
|
112
|
+
`AiExecutionQuote`는 `quote: {id,modelId,reservedCredits,pricingVersion,expiresAt,expiresLabel?,providerCostLabel?} | null`과 `available`을 받는다. 만료는 expiresAt을 기준으로 자동 갱신하며 expiresLabel은 제품의 명시적 시간대 표시다(미제공 시 ISO UTC). providerCostLabel이 없으면 비용 미확정이며 무료로 간주하지 않는다. 예약 최대값과 제공사 비용을 구분한다. 부족량은 표시 계산일 뿐 서버 인가가 아니다.
|
|
113
|
+
|
|
114
|
+
`onDbRefresh`로 견적을 요청하고 `onDbExecute`의 `{quoteId,modelId}`로 서버 실행 의도를 전달한다. 입력/모델/가격 설정 변경 시 부모는 반드시 `stale`을 지정하거나 quote를 비워야 한다. 새 견적 전에는 실행을 막는다. loading/executing/disabled/error/만료/잔액 미확인·부족에서도 실행 의도를 내보내지 않는다. UI 시간과 잔액은 인가가 아니며 서버가 견적 hash/기간/가격/모델/소유권/잔액과 idempotency를 다시 검사해야 한다.
|
|
115
|
+
|
|
116
|
+
기존 starter AiQuote 매핑은 id→id, model_id→modelId, reserved_credits→reservedCredits, pricing_version→pricingVersion, expires_at→expiresAt이다. estimated_cost_micros가 null이면 providerCostLabel을 생략한다. 서버 단가·모델 목록·원가 계산을 라이브러리로 옮기지 않는다. React props 또는 HTML 프로퍼티 `.models`/`.quote`로 배열/객체를 전달한다. 기본 문자열은 dbSetLocale로 대체한다.
|
|
117
|
+
|
|
118
|
+
독립 example의 `AiLaunchExample`는 입력→모의 견적→모의 실행→예제 지갑/내역 콜백을 연결한다. 늦게 도착한 입력 변경 전 견적은 버린다. 샘플 모델/50·120 예약/30·80 정산은 UI fixture이며 실제 가격/제품 기본값이 아니다. 원격 호출·SQL 예약이 없고 진행 중 예약량은 작업 카드에만 표시된다. 실제 backend 통합은 별도다. starter r2 snapshot에는 새 API가 없으며 공개 npm 배포는 하지 않았다.
|
|
119
|
+
|
|
120
|
+
이 흐름에서 발견한 DbTextarea의 error 속성 갱신 시 입력 유실은 input 시 value 반영으로 수정했다. DbSelect의 비활성 option에 aria-disabled도 추가했다. API 삭제/새 runtime dependency 없음. 변경된 generic control은 기존 소비자 회귀 후 새 snapshot으로 전달해야 한다.
|
|
121
|
+
|
|
122
|
+
검사: `node scripts/test-ai-launch.mjs`. `credit-wallet.html#ai-launch`의 개발용 버튼으로 잔액 부족/견적 오류/2초 만료를 체험한다.
|
|
123
|
+
|
|
124
|
+
### 충전 모달의 카드 선택 — 2026-10-18 수정
|
|
125
|
+
|
|
126
|
+
모달은 `CreditRecharge`의 `card-selection` (`cardSelection`) 모드로 상품을 표시한다. 라디오 input 없이 native button 카드 전체를 Enter/Space 또는 클릭으로 선택하며 `aria-pressed`로 현재 선택을 제공한다. `db-plan-change`와 controlled selected-id 계약은 유지한다. 추천 배지는 카드 헤더 안에 배치하고 모달 하단 왼쪽 결제 금액 요약은 제거했다. 상품 금액은 각 카드에서 확인하며 푸터에는 결제 버튼만 둔다. 기본 단독 CreditRecharge는 기존 라디오 동작을 유지한다.
|
|
127
|
+
|
|
128
|
+
example 상품명은 스타터/플러스/프로다. 상품 id와 표시 이름은 분리되며 서버 가격·결제 정책은 변경하지 않는다. 이 수정은 라이브러리 개발 소스와 example에 반영했다. 이미 설치된 스타터 r2 스냅샷을 자동 교체하지 않았다.
|
|
129
|
+
|
|
130
|
+
### 선택 중 모달 유지·현재 잔액 푸터
|
|
131
|
+
|
|
132
|
+
모달의 열린 상태와 동일한 속성을 반복 설정하지 않는다. 공통 db-modal도 이미 entering/entered 상태인 내용 갱신에서 입장 애니메이션을 다시 시작하지 않는다. 선택/로딩 갱신 중 포털 노드와 focus trap은 유지하고 실제 닫기→열기 때만 전환한다.
|
|
133
|
+
|
|
134
|
+
크레딧 수량과 가격은 작은 간격으로 묶고 추천에는 실제 `db-badge variant="primary"`를 사용한다. 푸터 왼쪽은 선택 상품의 결제 금액이 아니라 `available`(현재 남은 크레딧)을 표시한다. 모달 본문의 중복 잔액은 제거했다. available이 없거나 잘못된 금액이면 기존 금액 계약대로 —를 표시한다. 사용 예제에서는 실제 현재 잔액 상태를 available에 연결한다.
|
|
135
|
+
|
|
136
|
+
세부 간격 변수·모바일·포커스 보완은 [최근 모듈 품질 계약](./RECENT-COMPONENTS-QUALITY.md)을 따른다.
|