@reopt-ai/opt-ui 1.5.0 → 1.6.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/COMPONENT_CATALOG.md +83 -38
- package/README.md +28 -7
- package/dist/app.css +146 -0
- package/dist/core/index.cjs +164 -324
- package/dist/core/index.d.cts +3 -6
- package/dist/core/index.d.ts +3 -6
- package/dist/core/index.js +3 -323
- package/dist/docs/01-getting-started.md +25 -5
- package/dist/docs/02-components/01-core.md +36 -28
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +40 -12
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +1 -1
- package/dist/docs/03-recipes/03-layouts.md +16 -12
- package/dist/docs/05-migration/01-breaking-changes.md +28 -0
- package/dist/id-registry.cjs +1899 -1872
- package/dist/id-registry.d.cts +13 -10
- package/dist/id-registry.d.cts.map +1 -0
- package/dist/id-registry.d.ts +13 -10
- package/dist/id-registry.d.ts.map +1 -0
- package/dist/id-registry.js +1897 -1871
- package/dist/id-registry.js.map +1 -0
- package/dist/id-registry.json +28 -28
- package/dist/index.cjs +8294 -9790
- package/dist/index.d.cts +1486 -1442
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1486 -1442
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7933 -9777
- package/dist/index.js.map +1 -0
- package/dist/meta.cjs +7461 -6875
- package/dist/meta.d.cts.map +1 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +7460 -6846
- package/dist/meta.js.map +1 -0
- package/dist/pagination-By7Ozv3U.js +3788 -0
- package/dist/pagination-By7Ozv3U.js.map +1 -0
- package/dist/pagination-Uv3CnGJz.cjs +4677 -0
- package/dist/shader-surface-BK1GMt5_.d.cts +1788 -0
- package/dist/shader-surface-BK1GMt5_.d.cts.map +1 -0
- package/dist/shader-surface-BWhO2xkk.js +3715 -0
- package/dist/shader-surface-BWhO2xkk.js.map +1 -0
- package/dist/shader-surface-DIVWtpFl.d.ts +1788 -0
- package/dist/shader-surface-DIVWtpFl.d.ts.map +1 -0
- package/dist/shader-surface-DxLLfrE6.cjs +4108 -0
- package/dist/shells/index.cjs +51 -65
- package/dist/shells/index.d.cts +4 -5
- package/dist/shells/index.d.ts +4 -5
- package/dist/shells/index.js +3 -64
- package/dist/tailwind.css +49 -0
- package/dist/theme/server.cjs +257 -228
- package/dist/theme/server.d.cts +48 -60
- package/dist/theme/server.d.cts.map +1 -0
- package/dist/theme/server.d.ts +48 -60
- package/dist/theme/server.d.ts.map +1 -0
- package/dist/theme/server.js +234 -189
- package/dist/theme/server.js.map +1 -0
- package/dist/types-D0FlcYnM.d.cts +301 -0
- package/dist/types-D0FlcYnM.d.cts.map +1 -0
- package/dist/types-D0FlcYnM.d.ts +301 -0
- package/dist/types-D0FlcYnM.d.ts.map +1 -0
- package/dist/visuals/index.cjs +10 -4
- package/dist/visuals/index.d.cts +2 -1
- package/dist/visuals/index.d.ts +2 -1
- package/dist/visuals/index.js +1 -2
- package/dist/workflow-canvas-3Jeevkgy.cjs +2599 -0
- package/dist/workflow-canvas-BGBAcjf-.js +2403 -0
- package/dist/workflow-canvas-BGBAcjf-.js.map +1 -0
- package/dist/workflow-canvas-Bar_O2SQ.d.ts +494 -0
- package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +1 -0
- package/dist/workflow-canvas-DghDRtHn.d.cts +494 -0
- package/dist/workflow-canvas-DghDRtHn.d.cts.map +1 -0
- package/package.json +11 -6
- package/dist/chunk-3GWWZKX7.js +0 -38
- package/dist/chunk-3QFYBBL6.cjs +0 -4759
- package/dist/chunk-3XD4HIFL.js +0 -3241
- package/dist/chunk-AFF2HPE5.cjs +0 -5008
- package/dist/chunk-NXZJIHEZ.cjs +0 -3241
- package/dist/chunk-ONE3C5RV.cjs +0 -38
- package/dist/chunk-RBM2RNC2.js +0 -5008
- package/dist/chunk-VBGPEW45.js +0 -4759
- package/dist/index-BZ_lBlO1.d.ts +0 -474
- package/dist/index-BuvxoWHf.d.cts +0 -474
- package/dist/index-DWyqDIkH.d.cts +0 -1689
- package/dist/index-Uyijm14M.d.ts +0 -1689
- package/dist/types-D4-0lwaE.d.cts +0 -298
- package/dist/types-D4-0lwaE.d.ts +0 -298
package/COMPONENT_CATALOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- DEPRECATED: Prefer dist/docs/02-components/ for per-layer reference. -->
|
|
4
4
|
<!-- Auto-generated from ComponentMeta. Do not edit. -->
|
|
5
|
-
<!-- Version: 1.
|
|
5
|
+
<!-- Version: 1.6.0 -->
|
|
6
6
|
|
|
7
7
|
## Quick Stats
|
|
8
8
|
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
| Toggle | 눌림(pressed) 두 상태 버튼. 단독 사용하거나 ToggleGroup으로 묶어 단일/다중 선택을 만든다. | `Toggle`, `ToggleGroup` |
|
|
185
185
|
| Accordion | 수직으로 쌓인 펼침/접힘 패널. single(한 개)/multiple(여러 개) 모드와 collapsible 지원. | `AccordionRoot`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` |
|
|
186
186
|
| Meter | 범위 내 스칼라 측정값 게이지 (디스크 사용량·점수·용량 등). role=meter로 작업 진행률(Progress)과 구분된다. | `Meter` |
|
|
187
|
-
| BlockLayout | Block 루트 레이아웃.
|
|
187
|
+
| BlockLayout | Block 루트 레이아웃. 자식 간 section spacing과 선택적 시맨틱 inset을 제공하고 loading prop으로 LoadingOverlay를 자동 래핑합니다. | `BlockLayout` |
|
|
188
188
|
| createBlock | Block 팩토리 함수. BlockLayout 래핑을 구조적으로 강제하여 loading/className 처리를 자동화합니다. | `createBlock` |
|
|
189
189
|
| StarRating | 1~N점 별점을 선택하는 인터랙티브 레이팅 입력. 읽기 전용 표시도 지원. | `StarRating` |
|
|
190
190
|
| ErrorBoundary | React 에러 바운더리. 자식 컴포넌트 렌더 에러를 잡아 fallback UI를 표시합니다. | `ErrorBoundary` |
|
|
@@ -373,9 +373,11 @@
|
|
|
373
373
|
|
|
374
374
|
**Import:** `import { Kbd } from "@reopt-ai/opt-ui"`
|
|
375
375
|
|
|
376
|
-
| Prop
|
|
377
|
-
|
|
|
378
|
-
| `children`
|
|
376
|
+
| Prop | Type | Required | Default | Description |
|
|
377
|
+
| ----------- | ----------- | -------- | ------- | ------------------------------------------------------------------------------- |
|
|
378
|
+
| `children` | `ReactNode` | Yes | | 표시할 단축키 텍스트 |
|
|
379
|
+
| `hint` | `boolean` | | | 인라인 힌트 배지로 표시. `data-shortcut-hints="hidden"`일 때 숨겨진다 (app.css) |
|
|
380
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
379
381
|
|
|
380
382
|
#### Separator
|
|
381
383
|
|
|
@@ -542,26 +544,31 @@
|
|
|
542
544
|
|
|
543
545
|
**TabsRoot:**
|
|
544
546
|
|
|
545
|
-
| Prop | Type | Required | Default | Description
|
|
546
|
-
| ------------------- | ------------------------------ | -------- | ------- |
|
|
547
|
-
| `
|
|
548
|
-
| `
|
|
549
|
-
| `
|
|
547
|
+
| Prop | Type | Required | Default | Description |
|
|
548
|
+
| ------------------- | ------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------- |
|
|
549
|
+
| `variant` | `"underline" \| "workspace"` | | | 탭 프레임 형태. workspace는 편집기 탭(문서/쿼리처럼 열고 닫히며 상태를 갖는 작업 단위) |
|
|
550
|
+
| `defaultSelectedId` | `string` | | | 초기 선택된 탭 ID |
|
|
551
|
+
| `selectedId` | `string \| null` | | | 제어 모드: 선택된 탭 ID |
|
|
552
|
+
| `setSelectedId` | `(id: string \| null) => void` | | | 제어 모드: 탭 변경 핸들러 |
|
|
550
553
|
|
|
551
554
|
**TabList:**
|
|
552
555
|
|
|
553
|
-
| Prop | Type | Required | Default | Description
|
|
554
|
-
| ----------- | ----------- | -------- | ------- |
|
|
555
|
-
| `children` | `ReactNode` | Yes | | Tab 요소들
|
|
556
|
-
| `
|
|
556
|
+
| Prop | Type | Required | Default | Description |
|
|
557
|
+
| ----------- | ----------- | -------- | ------- | -------------------------------------------------------------------------------------- |
|
|
558
|
+
| `children` | `ReactNode` | Yes | | Tab 요소들 |
|
|
559
|
+
| `actions` | `ReactNode` | | | 탭 줄 오른쪽 액션 영역 (새 탭, 닫기 등). tablist 바깥에 렌더돼 탭 순회에 끼어들지 않음 |
|
|
560
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
557
561
|
|
|
558
562
|
**Tab:**
|
|
559
563
|
|
|
560
|
-
| Prop
|
|
561
|
-
|
|
|
562
|
-
| `id`
|
|
563
|
-
| `children`
|
|
564
|
-
| `
|
|
564
|
+
| Prop | Type | Required | Default | Description |
|
|
565
|
+
| ------------- | ---------------------- | -------- | ------- | ------------------------------------------------- |
|
|
566
|
+
| `id` | `string` | Yes | | 탭 고유 ID (TabPanel의 tabId와 매칭) |
|
|
567
|
+
| `children` | `ReactNode` | Yes | | 탭 레이블 |
|
|
568
|
+
| `icon` | `ReactNode` | | | 탭 이름 앞 아이콘. 활성 탭에서 accent 색으로 전환 |
|
|
569
|
+
| `status` | `"dirty" \| "running"` | | | 저장되지 않은 변경 / 실행 중 인디케이터 |
|
|
570
|
+
| `statusLabel` | `string` | | | status 인디케이터의 접근성 레이블 |
|
|
571
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
565
572
|
|
|
566
573
|
**TabPanel:**
|
|
567
574
|
|
|
@@ -1877,13 +1884,14 @@ import {
|
|
|
1877
1884
|
|
|
1878
1885
|
**Import:** `import { BlockLayout } from "@reopt-ai/opt-ui"`
|
|
1879
1886
|
|
|
1880
|
-
| Prop | Type | Required | Default | Description
|
|
1881
|
-
| -------------- | --------------------------------------------- | -------- | --------- |
|
|
1882
|
-
| `children` | `ReactNode` | Yes | | Block 콘텐츠
|
|
1883
|
-
| `loading` | `boolean` | | | 로딩 상태. 설정 시 LoadingOverlay로 자동 래핑
|
|
1884
|
-
| `loadingLabel` | `string` | | | LoadingOverlay 접근성 레이블
|
|
1885
|
-
| `spacing` | `"section" \| "group" \| "element" \| "none"` | | "section" | Block 최상위 자식 사이 간격. 데이터 밀도가 높은 Block는 group/element로 낮출 수 있음
|
|
1886
|
-
| `
|
|
1887
|
+
| Prop | Type | Required | Default | Description |
|
|
1888
|
+
| -------------- | --------------------------------------------- | -------- | --------- | ---------------------------------------------------------------------------------------------------- |
|
|
1889
|
+
| `children` | `ReactNode` | Yes | | Block 콘텐츠 |
|
|
1890
|
+
| `loading` | `boolean` | | | 로딩 상태. 설정 시 LoadingOverlay로 자동 래핑 |
|
|
1891
|
+
| `loadingLabel` | `string` | | | LoadingOverlay 접근성 레이블 |
|
|
1892
|
+
| `spacing` | `"section" \| "group" \| "element" \| "none"` | | "section" | Block 최상위 자식 사이 간격. 데이터 밀도가 높은 Block는 group/element로 낮출 수 있음 |
|
|
1893
|
+
| `inset` | `"section" \| "group" \| "element" \| "none"` | | | Block 안쪽 여백. 페이지 콘텐츠 경계에는 section을 사용하고, 이미 여백을 제공하는 Shell 안에서는 생략 |
|
|
1894
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
1887
1895
|
|
|
1888
1896
|
**Examples:**
|
|
1889
1897
|
|
|
@@ -1892,7 +1900,7 @@ _Block 루트:_
|
|
|
1892
1900
|
```tsx
|
|
1893
1901
|
import { BlockLayout, PageHeader } from "@reopt-ai/opt-ui";
|
|
1894
1902
|
|
|
1895
|
-
<BlockLayout loading={isLoading} spacing="section">
|
|
1903
|
+
<BlockLayout loading={isLoading} spacing="section" inset="section">
|
|
1896
1904
|
<PageHeader title="대시보드" description="프로젝트 현황" />
|
|
1897
1905
|
<section>콘텐츠 영역</section>
|
|
1898
1906
|
</BlockLayout>;
|
|
@@ -2605,17 +2613,17 @@ const filters = [
|
|
|
2605
2613
|
**Import:** `import { FileUploadForm } from "@reopt-ai/opt-ui"`
|
|
2606
2614
|
**Dependencies:** form (core)
|
|
2607
2615
|
|
|
2608
|
-
| Prop | Type | Required | Default
|
|
2609
|
-
| ----------------- | ------------------------------------------------ | -------- |
|
|
2610
|
-
| `accept` | `string` | | "image
|
|
2611
|
-
| `showPreview` | `boolean` | | true
|
|
2612
|
-
| `showFileInfo` | `boolean` | | true
|
|
2613
|
-
| `showDescription` | `boolean` | | true
|
|
2614
|
-
| `maxFileSize` | `number` | |
|
|
2615
|
-
| `onSubmit` | `(values: { title, description, file }) => void` | |
|
|
2616
|
-
| `labels` | `FileUploadFormLabels` | |
|
|
2617
|
-
| `onPreview` | `(file: File) => void` | |
|
|
2618
|
-
| `columnMapping` | `ReactNode` | |
|
|
2616
|
+
| Prop | Type | Required | Default | Description |
|
|
2617
|
+
| ----------------- | ------------------------------------------------ | -------- | ------------------------- | ----------------------------------------- |
|
|
2618
|
+
| `accept` | `string` | | "image/*,.pdf,.doc,.docx" | 허용 파일 타입 |
|
|
2619
|
+
| `showPreview` | `boolean` | | true | 이미지 미리보기 표시 |
|
|
2620
|
+
| `showFileInfo` | `boolean` | | true | 파일 정보 표시 |
|
|
2621
|
+
| `showDescription` | `boolean` | | true | 설명 텍스트영역 표시 |
|
|
2622
|
+
| `maxFileSize` | `number` | | | 최대 파일 크기 (bytes) |
|
|
2623
|
+
| `onSubmit` | `(values: { title, description, file }) => void` | | | 제출 핸들러 |
|
|
2624
|
+
| `labels` | `FileUploadFormLabels` | | | i18n용 라벨 오버라이드 |
|
|
2625
|
+
| `onPreview` | `(file: File) => void` | | | 파일 선택 후 프리뷰 콜백 |
|
|
2626
|
+
| `columnMapping` | `ReactNode` | | | CSV 컬럼 매핑 UI 슬롯 (파일 선택 후 표시) |
|
|
2619
2627
|
|
|
2620
2628
|
#### EventIcon
|
|
2621
2629
|
|
|
@@ -2627,6 +2635,19 @@ const filters = [
|
|
|
2627
2635
|
| `color` | `string` | | theme accent | 사용자 지정 아이콘 색상. 없으면 theme accent 토큰을 사용하고, 전달하면 전경색과 tint 배경에 적용됩니다. |
|
|
2628
2636
|
| `size` | `"sm" \| "md" \| "lg"` | | "md" | 아이콘 크기 (sm=20px, md=28px, lg=36px) |
|
|
2629
2637
|
|
|
2638
|
+
#### ConnectionIndicator
|
|
2639
|
+
|
|
2640
|
+
**Import:** `import { ConnectionIndicator } from "@reopt-ai/opt-ui"`
|
|
2641
|
+
|
|
2642
|
+
| Prop | Type | Required | Default | Description |
|
|
2643
|
+
| ----------- | ----------------------------------------------------------- | -------- | ------- | --------------------------------------------------------------------- |
|
|
2644
|
+
| `status` | `"connected" \| "connecting" \| "disconnected" \| "error"` | | | 연결 상태 프리셋. tone과 pulse의 기본값을 정함 |
|
|
2645
|
+
| `tone` | `"neutral" \| "success" \| "warning" \| "danger" \| "info"` | | | status로 표현되지 않는 도메인 상태의 색을 직접 지정 (status보다 우선) |
|
|
2646
|
+
| `label` | `string` | | | 상태 문구 |
|
|
2647
|
+
| `showLabel` | `boolean` | | | 문구 표시 여부 |
|
|
2648
|
+
| `pulse` | `boolean` | | | 점 애니메이션. 생략하면 status 프리셋을 따름 |
|
|
2649
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
2650
|
+
|
|
2630
2651
|
#### TemplatePicker
|
|
2631
2652
|
|
|
2632
2653
|
**Import:** `import { TemplatePicker } from "@reopt-ai/opt-ui"`
|
|
@@ -2728,6 +2749,30 @@ const filters = [
|
|
|
2728
2749
|
| `error` | `string` | | | assertive 오류 상태 |
|
|
2729
2750
|
| `labels` | `QueryResultsTableLabels` | | | 로딩/빈 상태/행 수 문구 |
|
|
2730
2751
|
|
|
2752
|
+
#### SqlEditor
|
|
2753
|
+
|
|
2754
|
+
**Import:** `import { SqlEditor } from "@reopt-ai/opt-ui"`
|
|
2755
|
+
**Dependencies:** button (core)
|
|
2756
|
+
|
|
2757
|
+
| Prop | Type | Required | Default | Description |
|
|
2758
|
+
| ------------------- | ----------------------------- | -------- | ------- | -------------------------------------------------------------------------------- |
|
|
2759
|
+
| `value` | `string` | Yes | | 편집 중인 SQL |
|
|
2760
|
+
| `onChange` | `(sql: string) => void` | Yes | | 본문 변경 핸들러 |
|
|
2761
|
+
| `onRun` | `(sql: string) => void` | | | Mod+Enter 실행 핸들러 |
|
|
2762
|
+
| `onRunAndAdvance` | `(sql: string) => void` | | | Shift+Enter — 실행 후 다음 셀/탭으로 이동 |
|
|
2763
|
+
| `schema` | `SqlEditorSchema` | | | 테이블/컬럼 자동완성 스키마. 변경 시 에디터를 다시 만들지 않고 자동완성만 재구성 |
|
|
2764
|
+
| `completionSources` | `SqlEditorCompletionSource[]` | | | 스키마 자동완성에 더할 완성 소스 (함수 시그니처, 스니펫) |
|
|
2765
|
+
| `keymap` | `SqlEditorKeyBinding[]` | | | 추가 키 바인딩. 내장 실행 바인딩보다 먼저 평가 |
|
|
2766
|
+
| `extensions` | `SqlEditorExtension[]` | | | 임의의 CodeMirror 확장. 내장 확장 뒤에 붙어 우선권을 가짐 |
|
|
2767
|
+
| `height` | `number \| "fill"` | | | px 고정 높이. fill이면 부모가 정한 높이를 채움 |
|
|
2768
|
+
| `placeholder` | `string` | | | 빈 편집기 안내 문구 |
|
|
2769
|
+
| `readOnly` | `boolean` | | | 편집 잠금 |
|
|
2770
|
+
| `ariaLabel` | `string` | | | 편집 영역 접근성 레이블 |
|
|
2771
|
+
| `autoFocus` | `boolean` | | | 마운트 시 포커스 |
|
|
2772
|
+
| `showFooter` | `boolean` | | | 실행 힌트/버튼 표시. 기본값은 onRun이 있을 때 표시 |
|
|
2773
|
+
| `labels` | `SqlEditorLabels` | | | 문구 오버라이드 |
|
|
2774
|
+
| `className` | `string` | | | 기본 스타일 오버라이드 |
|
|
2775
|
+
|
|
2731
2776
|
#### ReportBuilder
|
|
2732
2777
|
|
|
2733
2778
|
**Import:** `import { ReportBuilder } from "@reopt-ai/opt-ui"`
|
package/README.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
접근성 우선 UI 컴포넌트 라이브러리. opt-ui-primitives 기반 Core, 비즈니스 Shells, Surface/block layout primitives를 제공합니다. 페이지 템플릿 Surface는 `@reopt-ai/opt-cli` registry로 설치하며, 차트와 데이터 시각화 컴포넌트는 `@reopt-ai/opt-charts`로 분리되었습니다.
|
|
4
4
|
|
|
5
|
+
## 에이전트 스킬로 설정하기 (권장)
|
|
6
|
+
|
|
7
|
+
소비자 프로젝트 루트에서 전용 스킬을 설치합니다.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx skills add reopt-ai/reopt-skills/opt-ui-install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
설치 후 에이전트에게
|
|
14
|
+
`opt-ui-install 스킬로 이 프로젝트에 @reopt-ai/opt-ui를 설정하고 검증해줘`라고
|
|
15
|
+
요청하세요. 스킬은 신규 설치와 업그레이드를 구분하고, `AGENTS.md`(없으면
|
|
16
|
+
`CLAUDE.md`)의 reopt marker 블록, Tailwind v4, `OptThemeProvider`, `opt
|
|
17
|
+
doctor`, Surface 흐름을 멱등하게 설정합니다. 스킬 소스는
|
|
18
|
+
[`reopt-ai/reopt-skills`](https://github.com/reopt-ai/reopt-skills/tree/main/skills/opt-ui-install)가
|
|
19
|
+
단일 기준입니다.
|
|
20
|
+
|
|
21
|
+
아래 설치 명령은 스킬을 사용할 수 없거나 모든 단계를 직접 통제할 때의 수동
|
|
22
|
+
대안입니다.
|
|
23
|
+
|
|
5
24
|
## 설치
|
|
6
25
|
|
|
7
26
|
```bash
|
|
@@ -170,13 +189,15 @@ CSS 변수 기반 시맨틱 토큰 — `dark:` 프리픽스 없이 자동 전환
|
|
|
170
189
|
|
|
171
190
|
### 시맨틱 스페이싱
|
|
172
191
|
|
|
173
|
-
| CSS 변수 | Tailwind
|
|
174
|
-
| --------------------- |
|
|
175
|
-
| `--opt-space-section` | `gap-section` | 1.5rem (24px) |
|
|
176
|
-
| `--opt-space-group` | `gap-group`
|
|
177
|
-
| `--opt-space-element` | `gap-element` | 0.5rem (8px) |
|
|
192
|
+
| CSS 변수 | Tailwind | 값 |
|
|
193
|
+
| --------------------- | -------------------------- | ------------- |
|
|
194
|
+
| `--opt-space-section` | `gap-section`, `p-section` | 1.5rem (24px) |
|
|
195
|
+
| `--opt-space-group` | `gap-group`, `p-group` | 1rem (16px) |
|
|
196
|
+
| `--opt-space-element` | `gap-element`, `p-element` | 0.5rem (8px) |
|
|
178
197
|
|
|
179
|
-
|
|
198
|
+
Block 루트는 반드시 `BlockLayout`을 사용합니다. 페이지 콘텐츠 경계를 Block이 직접 맡을 때는
|
|
199
|
+
`inset="section"`을 지정하고, 카드나 Shell이 이미 여백을 제공하는 embedded Block에서는 inset을
|
|
200
|
+
생략합니다. `PageHeader`는 재사용 가능한 콘텐츠 헤더이므로 바깥 여백을 소유하지 않습니다.
|
|
180
201
|
|
|
181
202
|
## 접근성
|
|
182
203
|
|
|
@@ -229,7 +250,7 @@ bun run test:coverage
|
|
|
229
250
|
## Design Decisions
|
|
230
251
|
|
|
231
252
|
- **Layer metaphor**: Core → Shells는 opt-ui가 맡고, 페이지 템플릿 Surface는 opt-cli block registry로 설치
|
|
232
|
-
- **
|
|
253
|
+
- **BlockLayout 강제**: block template은 반드시 `BlockLayout` 또는 `createBlock`을 사용하고, 페이지 경계는 `inset="section"`으로 명시
|
|
233
254
|
- **Subpath exports**: `./core`, `./visuals`, `./shells` — 필요한 계층만 import 가능
|
|
234
255
|
- **Dual format**: ESM + CJS — 모든 번들러/런타임 호환
|
|
235
256
|
- **Source maps**: 디버깅을 위한 소스맵 포함
|
package/dist/app.css
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @reopt-ai/opt-ui — Application base layer (optional)
|
|
3
|
+
*
|
|
4
|
+
* `tailwind.css`가 토큰과 유틸리티를 준다면, 이 파일은 **앱 셸이 매번 다시 쓰는
|
|
5
|
+
* 규칙**을 준다. 실제 소비 프로젝트를 감사했을 때 아래 다섯 가지가 앱마다
|
|
6
|
+
* 손으로 재작성돼 있었고, 매번 조금씩 달랐다 — 커서 정책, opt-ui 밖 요소의
|
|
7
|
+
* 포커스 링, 사용자 모션 설정, 글자 크기 설정, 스킵 링크.
|
|
8
|
+
*
|
|
9
|
+
* Usage (consumer globals.css):
|
|
10
|
+
*
|
|
11
|
+
* @import "tailwindcss";
|
|
12
|
+
* @import "@reopt-ai/opt-ui/tailwind.css";
|
|
13
|
+
* @import "@reopt-ai/opt-ui/app.css";
|
|
14
|
+
*
|
|
15
|
+
* 선택 사항이다. 앱이 자기 규칙을 갖고 있다면 안 써도 되고, 여기 규칙은 전부
|
|
16
|
+
* `:where()`로 특이도 0이라 유틸리티 클래스가 항상 이긴다.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
@layer base {
|
|
20
|
+
/* ── Cursor ──
|
|
21
|
+
*
|
|
22
|
+
* 브라우저 기본값은 button에 `default`를 준다. 디자인 시스템을 쓰는 앱은
|
|
23
|
+
* 예외 없이 이걸 pointer로 되돌리는데, 그 목록을 손으로 관리하면 role 기반
|
|
24
|
+
* 요소(`[role="option"]`, `[role="treeitem"]`)가 빠지기 쉽다.
|
|
25
|
+
*
|
|
26
|
+
* opt-ui 컴포넌트는 자체 클래스로 커서를 지정하므로 여기 규칙과 무관하다.
|
|
27
|
+
* 이건 앱이 직접 쓰는 raw 요소를 위한 것이다. */
|
|
28
|
+
:where(
|
|
29
|
+
button:not(:disabled),
|
|
30
|
+
summary,
|
|
31
|
+
label[for],
|
|
32
|
+
select:not(:disabled),
|
|
33
|
+
a[href],
|
|
34
|
+
input:is(
|
|
35
|
+
[type="button"],
|
|
36
|
+
[type="submit"],
|
|
37
|
+
[type="reset"],
|
|
38
|
+
[type="checkbox"],
|
|
39
|
+
[type="radio"],
|
|
40
|
+
[type="file"],
|
|
41
|
+
[type="range"]
|
|
42
|
+
):not(:disabled),
|
|
43
|
+
[role="button"]:not([aria-disabled="true"]),
|
|
44
|
+
[role="tab"]:not([aria-disabled="true"]),
|
|
45
|
+
[role="menuitem"]:not([aria-disabled="true"]),
|
|
46
|
+
[role="menuitemcheckbox"]:not([aria-disabled="true"]),
|
|
47
|
+
[role="menuitemradio"]:not([aria-disabled="true"]),
|
|
48
|
+
[role="option"]:not([aria-disabled="true"]),
|
|
49
|
+
[role="switch"]:not([aria-disabled="true"]),
|
|
50
|
+
[role="checkbox"]:not([aria-disabled="true"]),
|
|
51
|
+
[role="radio"]:not([aria-disabled="true"]),
|
|
52
|
+
[role="treeitem"]:not([aria-disabled="true"])
|
|
53
|
+
) {
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:where(
|
|
58
|
+
button:disabled,
|
|
59
|
+
input:disabled,
|
|
60
|
+
select:disabled,
|
|
61
|
+
textarea:disabled,
|
|
62
|
+
[aria-disabled="true"]
|
|
63
|
+
) {
|
|
64
|
+
cursor: not-allowed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ── Focus ──
|
|
68
|
+
*
|
|
69
|
+
* opt-ui 컴포넌트는 `data-opt-id`를 달고 자체 포커스 링을 갖는다. 앱이 직접
|
|
70
|
+
* 쓰는 raw button/link/summary에는 아무것도 없어서, 키보드 사용자가 앱 고유
|
|
71
|
+
* 영역에 들어가는 순간 포커스가 사라지곤 한다. 같은 링을 기본값으로 준다. */
|
|
72
|
+
:where(
|
|
73
|
+
button:not([data-opt-id]),
|
|
74
|
+
a[href]:not([data-opt-id]),
|
|
75
|
+
summary:not([data-opt-id]),
|
|
76
|
+
[tabindex]:not([tabindex="-1"]):not([data-opt-id])
|
|
77
|
+
):focus-visible {
|
|
78
|
+
outline: 2px solid var(--opt-ring);
|
|
79
|
+
outline-offset: 2px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ── User preference: text scale ──
|
|
83
|
+
*
|
|
84
|
+
* `<html data-text-scale="small|large">`로 루트 글자 크기를 조절한다. rem
|
|
85
|
+
* 기반 토큰이 전부 따라 움직이므로 레이아웃이 함께 확대/축소된다. */
|
|
86
|
+
html[data-text-scale="small"] {
|
|
87
|
+
font-size: 93.75%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
html[data-text-scale="large"] {
|
|
91
|
+
font-size: 112.5%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── User preference: motion ──
|
|
95
|
+
*
|
|
96
|
+
* `tailwind.css`가 시스템 `prefers-reduced-motion`을 이미 존중한다. 이건 앱
|
|
97
|
+
* 설정에서 사용자가 **시스템과 무관하게** 끈 경우를 위한 명시적 스코프다.
|
|
98
|
+
* `<html data-motion="reduced">` 또는 앱 셸 루트에 붙인다. */
|
|
99
|
+
[data-motion="reduced"],
|
|
100
|
+
[data-motion="reduced"] :where(*, *::before, *::after) {
|
|
101
|
+
animation-duration: 0.01ms !important;
|
|
102
|
+
animation-iteration-count: 1 !important;
|
|
103
|
+
transition-duration: 0.01ms !important;
|
|
104
|
+
scroll-behavior: auto !important;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@layer components {
|
|
109
|
+
/* ── Shortcut hints ──
|
|
110
|
+
*
|
|
111
|
+
* `<Kbd hint>`가 붙이는 클래스. 메뉴 항목 옆에 붙는 "⌘K" 같은 광고성 배지를
|
|
112
|
+
* 가리키며, 단축키 도움말 자체는 대상이 아니다.
|
|
113
|
+
*
|
|
114
|
+
* `<html data-shortcut-hints="hidden">`이면 사라진다 — 설정 값을 모든 호출
|
|
115
|
+
* 지점에 내려보내지 않고 "키보드 힌트 숨기기"를 구현하는 경로다.
|
|
116
|
+
* opt-shell의 `shortcutHints` 정책이 그 속성을 쓴다. */
|
|
117
|
+
[data-shortcut-hints="hidden"] .opt-shortcut-hint {
|
|
118
|
+
display: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ── Skip link ──
|
|
122
|
+
*
|
|
123
|
+
* 키보드 사용자가 사이드바 전체를 Tab으로 통과하지 않고 본문으로 건너뛰는
|
|
124
|
+
* 링크. 포커스 전에는 화면 밖에 있다가 포커스되면 나타난다.
|
|
125
|
+
*
|
|
126
|
+
* <a class="opt-skip-link" href="#main">본문으로 건너뛰기</a>
|
|
127
|
+
*/
|
|
128
|
+
.opt-skip-link {
|
|
129
|
+
position: fixed;
|
|
130
|
+
z-index: 100;
|
|
131
|
+
top: var(--opt-space-element);
|
|
132
|
+
left: var(--opt-space-element);
|
|
133
|
+
padding: var(--opt-space-element) var(--opt-space-group);
|
|
134
|
+
border: 1px solid var(--opt-ring);
|
|
135
|
+
border-radius: var(--opt-radius-md);
|
|
136
|
+
color: var(--opt-text);
|
|
137
|
+
background: var(--opt-surface-overlay);
|
|
138
|
+
box-shadow: var(--opt-shadow-md);
|
|
139
|
+
text-decoration: none;
|
|
140
|
+
transform: translateY(calc(-100% - var(--opt-space-section)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.opt-skip-link:focus {
|
|
144
|
+
transform: translateY(0);
|
|
145
|
+
}
|
|
146
|
+
}
|