@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.
Files changed (87) hide show
  1. package/COMPONENT_CATALOG.md +83 -38
  2. package/README.md +28 -7
  3. package/dist/app.css +146 -0
  4. package/dist/core/index.cjs +164 -324
  5. package/dist/core/index.d.cts +3 -6
  6. package/dist/core/index.d.ts +3 -6
  7. package/dist/core/index.js +3 -323
  8. package/dist/docs/01-getting-started.md +25 -5
  9. package/dist/docs/02-components/01-core.md +36 -28
  10. package/dist/docs/02-components/02-visuals.md +1 -1
  11. package/dist/docs/02-components/03-shells.md +40 -12
  12. package/dist/docs/02-components/04-surfaces.md +1 -1
  13. package/dist/docs/02-components/index.md +1 -1
  14. package/dist/docs/03-recipes/03-layouts.md +16 -12
  15. package/dist/docs/05-migration/01-breaking-changes.md +28 -0
  16. package/dist/id-registry.cjs +1899 -1872
  17. package/dist/id-registry.d.cts +13 -10
  18. package/dist/id-registry.d.cts.map +1 -0
  19. package/dist/id-registry.d.ts +13 -10
  20. package/dist/id-registry.d.ts.map +1 -0
  21. package/dist/id-registry.js +1897 -1871
  22. package/dist/id-registry.js.map +1 -0
  23. package/dist/id-registry.json +28 -28
  24. package/dist/index.cjs +8294 -9790
  25. package/dist/index.d.cts +1486 -1442
  26. package/dist/index.d.cts.map +1 -0
  27. package/dist/index.d.ts +1486 -1442
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +7933 -9777
  30. package/dist/index.js.map +1 -0
  31. package/dist/meta.cjs +7461 -6875
  32. package/dist/meta.d.cts.map +1 -0
  33. package/dist/meta.d.ts.map +1 -0
  34. package/dist/meta.js +7460 -6846
  35. package/dist/meta.js.map +1 -0
  36. package/dist/pagination-By7Ozv3U.js +3788 -0
  37. package/dist/pagination-By7Ozv3U.js.map +1 -0
  38. package/dist/pagination-Uv3CnGJz.cjs +4677 -0
  39. package/dist/shader-surface-BK1GMt5_.d.cts +1788 -0
  40. package/dist/shader-surface-BK1GMt5_.d.cts.map +1 -0
  41. package/dist/shader-surface-BWhO2xkk.js +3715 -0
  42. package/dist/shader-surface-BWhO2xkk.js.map +1 -0
  43. package/dist/shader-surface-DIVWtpFl.d.ts +1788 -0
  44. package/dist/shader-surface-DIVWtpFl.d.ts.map +1 -0
  45. package/dist/shader-surface-DxLLfrE6.cjs +4108 -0
  46. package/dist/shells/index.cjs +51 -65
  47. package/dist/shells/index.d.cts +4 -5
  48. package/dist/shells/index.d.ts +4 -5
  49. package/dist/shells/index.js +3 -64
  50. package/dist/tailwind.css +49 -0
  51. package/dist/theme/server.cjs +257 -228
  52. package/dist/theme/server.d.cts +48 -60
  53. package/dist/theme/server.d.cts.map +1 -0
  54. package/dist/theme/server.d.ts +48 -60
  55. package/dist/theme/server.d.ts.map +1 -0
  56. package/dist/theme/server.js +234 -189
  57. package/dist/theme/server.js.map +1 -0
  58. package/dist/types-D0FlcYnM.d.cts +301 -0
  59. package/dist/types-D0FlcYnM.d.cts.map +1 -0
  60. package/dist/types-D0FlcYnM.d.ts +301 -0
  61. package/dist/types-D0FlcYnM.d.ts.map +1 -0
  62. package/dist/visuals/index.cjs +10 -4
  63. package/dist/visuals/index.d.cts +2 -1
  64. package/dist/visuals/index.d.ts +2 -1
  65. package/dist/visuals/index.js +1 -2
  66. package/dist/workflow-canvas-3Jeevkgy.cjs +2599 -0
  67. package/dist/workflow-canvas-BGBAcjf-.js +2403 -0
  68. package/dist/workflow-canvas-BGBAcjf-.js.map +1 -0
  69. package/dist/workflow-canvas-Bar_O2SQ.d.ts +494 -0
  70. package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +1 -0
  71. package/dist/workflow-canvas-DghDRtHn.d.cts +494 -0
  72. package/dist/workflow-canvas-DghDRtHn.d.cts.map +1 -0
  73. package/package.json +11 -6
  74. package/dist/chunk-3GWWZKX7.js +0 -38
  75. package/dist/chunk-3QFYBBL6.cjs +0 -4759
  76. package/dist/chunk-3XD4HIFL.js +0 -3241
  77. package/dist/chunk-AFF2HPE5.cjs +0 -5008
  78. package/dist/chunk-NXZJIHEZ.cjs +0 -3241
  79. package/dist/chunk-ONE3C5RV.cjs +0 -38
  80. package/dist/chunk-RBM2RNC2.js +0 -5008
  81. package/dist/chunk-VBGPEW45.js +0 -4759
  82. package/dist/index-BZ_lBlO1.d.ts +0 -474
  83. package/dist/index-BuvxoWHf.d.cts +0 -474
  84. package/dist/index-DWyqDIkH.d.cts +0 -1689
  85. package/dist/index-Uyijm14M.d.ts +0 -1689
  86. package/dist/types-D4-0lwaE.d.cts +0 -298
  87. package/dist/types-D4-0lwaE.d.ts +0 -298
@@ -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.4.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 루트 레이아웃. flex + 기본 section spacing 적용, loading prop으로 LoadingOverlay 자동 래핑. | `BlockLayout` |
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 | Type | Required | Default | Description |
377
- | ---------- | ----------- | -------- | ------- | -------------------- |
378
- | `children` | `ReactNode` | Yes | | 표시할 단축키 텍스트 |
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
- | `defaultSelectedId` | `string` | | | 초기 선택된ID |
548
- | `selectedId` | `string \| null` | | | 제어 모드: 선택된 탭 ID |
549
- | `setSelectedId` | `(id: string \| null) => void` | | | 제어 모드: 탭 변경 핸들러 |
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
- | `className` | `string` | | | 기본 스타일 오버라이드 |
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 | Type | Required | Default | Description |
561
- | ----------- | ----------- | -------- | ------- | ------------------------------------ |
562
- | `id` | `string` | Yes | | 탭 고유 ID (TabPanel의 tabId와 매칭) |
563
- | `children` | `ReactNode` | Yes | | 탭 레이블 |
564
- | `className` | `string` | | | 기본 스타일 오버라이드 |
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
- | `className` | `string` | | | 기본 스타일 오버라이드 |
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 | Description |
2609
- | ----------------- | ------------------------------------------------ | -------- | -------------------------- | ----------------------------------------- |
2610
- | `accept` | `string` | | "image/\*,.pdf,.doc,.docx" | 허용 파일 타입 |
2611
- | `showPreview` | `boolean` | | true | 이미지 미리보기 표시 |
2612
- | `showFileInfo` | `boolean` | | true | 파일 정보 표시 |
2613
- | `showDescription` | `boolean` | | true | 설명 텍스트영역 표시 |
2614
- | `maxFileSize` | `number` | | | 최대 파일 크기 (bytes) |
2615
- | `onSubmit` | `(values: { title, description, file }) => void` | | | 제출 핸들러 |
2616
- | `labels` | `FileUploadFormLabels` | | | i18n용 라벨 오버라이드 |
2617
- | `onPreview` | `(file: File) => void` | | | 파일 선택 후 프리뷰 콜백 |
2618
- | `columnMapping` | `ReactNode` | | | CSV 컬럼 매핑 UI 슬롯 (파일 선택 후 표시) |
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` | 1rem (16px) |
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
- Surface 루트는 반드시 `SurfaceLayout`을 사용하여 일관된 스페이싱을 유지합니다.
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
- - **SurfaceLayout 강제**: block template은 반드시 `SurfaceLayout` 또는 `createSurface`를 사용하여 구조적 일관성 보장
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
+ }