@reopt-ai/opt-ui 1.4.1

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 (64) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/COMPONENT_CATALOG.md +3000 -0
  3. package/LICENSE +21 -0
  4. package/README.md +244 -0
  5. package/dist/chunk-3GWWZKX7.js +38 -0
  6. package/dist/chunk-AFF2HPE5.cjs +5008 -0
  7. package/dist/chunk-ELWICXYY.js +4745 -0
  8. package/dist/chunk-N4NDU5ET.cjs +4745 -0
  9. package/dist/chunk-ONE3C5RV.cjs +38 -0
  10. package/dist/chunk-QWBHD54V.js +3218 -0
  11. package/dist/chunk-RBM2RNC2.js +5008 -0
  12. package/dist/chunk-X5WCXJAF.cjs +3218 -0
  13. package/dist/core/index.cjs +324 -0
  14. package/dist/core/index.d.cts +5 -0
  15. package/dist/core/index.d.ts +5 -0
  16. package/dist/core/index.js +324 -0
  17. package/dist/docs/01-getting-started.md +129 -0
  18. package/dist/docs/02-components/01-core.md +1841 -0
  19. package/dist/docs/02-components/02-visuals.md +11 -0
  20. package/dist/docs/02-components/03-shells.md +1361 -0
  21. package/dist/docs/02-components/04-surfaces.md +11 -0
  22. package/dist/docs/02-components/index.md +106 -0
  23. package/dist/docs/03-recipes/01-forms.md +471 -0
  24. package/dist/docs/03-recipes/02-dashboards.md +397 -0
  25. package/dist/docs/03-recipes/03-layouts.md +424 -0
  26. package/dist/docs/04-theming.md +232 -0
  27. package/dist/docs/05-migration/01-breaking-changes.md +117 -0
  28. package/dist/docs/05-migration/02-formstore.md +336 -0
  29. package/dist/docs/06-troubleshooting.md +119 -0
  30. package/dist/docs/index.md +71 -0
  31. package/dist/id-registry.cjs +1875 -0
  32. package/dist/id-registry.d.cts +27 -0
  33. package/dist/id-registry.d.ts +27 -0
  34. package/dist/id-registry.js +1875 -0
  35. package/dist/id-registry.json +3799 -0
  36. package/dist/index-BZ_lBlO1.d.ts +474 -0
  37. package/dist/index-BuvxoWHf.d.cts +474 -0
  38. package/dist/index-DlAcuvQp.d.cts +1686 -0
  39. package/dist/index-Slu5hOj1.d.ts +1686 -0
  40. package/dist/index.cjs +9959 -0
  41. package/dist/index.d.cts +2351 -0
  42. package/dist/index.d.ts +2351 -0
  43. package/dist/index.js +9959 -0
  44. package/dist/meta.cjs +6898 -0
  45. package/dist/meta.d.cts +36 -0
  46. package/dist/meta.d.ts +36 -0
  47. package/dist/meta.js +6869 -0
  48. package/dist/shells/index.cjs +65 -0
  49. package/dist/shells/index.d.cts +5 -0
  50. package/dist/shells/index.d.ts +5 -0
  51. package/dist/shells/index.js +65 -0
  52. package/dist/tailwind.css +401 -0
  53. package/dist/theme/presets/default.css +355 -0
  54. package/dist/theme/presets/minimal.css +354 -0
  55. package/dist/theme/presets/mono-dark.css +354 -0
  56. package/dist/theme/presets/natural.css +181 -0
  57. package/dist/theme/presets/pro.css +354 -0
  58. package/dist/types-D4-0lwaE.d.cts +298 -0
  59. package/dist/types-D4-0lwaE.d.ts +298 -0
  60. package/dist/visuals/index.cjs +4 -0
  61. package/dist/visuals/index.d.cts +1 -0
  62. package/dist/visuals/index.d.ts +1 -0
  63. package/dist/visuals/index.js +4 -0
  64. package/package.json +165 -0
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: "Breaking Changes"
3
+ description: "opt-ui 버전별 Breaking/Deprecated 변경사항 레지스트리"
4
+ ---
5
+
6
+ # Breaking Changes Registry
7
+
8
+ 버전별 Breaking/Deprecated 변경사항 레지스트리.
9
+
10
+ ## Registry Format
11
+
12
+ 각 변경은 다음 필드를 포함:
13
+
14
+ ```yaml
15
+ - version: "X.Y.Z" # 변경이 포함된 버전
16
+ level: B | D | A | F # 분류 (Breaking, Deprecated, Added, Fixed)
17
+ component: "Component" # 영향받는 컴포넌트
18
+ change: "변경 요약"
19
+ scan: "grep 패턴" # 영향 스캔용 패턴
20
+ fix:
21
+ type: rename | retype | restructure | manual
22
+ from: "기존 패턴"
23
+ to: "새 패턴"
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Version 1.2.0
29
+
30
+ ### Breaking
31
+
32
+ - version: "1.2.0"
33
+ level: B
34
+ component: "ToolbarRoot"
35
+ change: "focusLoop prop 제거"
36
+ detail: "ToolbarRoot에서 focusLoop prop이 제거됨. 자동 포커스 루프 동작으로 변경"
37
+ scan: "focusLoop"
38
+ fix:
39
+ type: rename
40
+ from: "<ToolbarRoot focusLoop>"
41
+ to: "<ToolbarRoot>"
42
+
43
+ - version: "1.2.0"
44
+ level: B
45
+ component: "DropdownContent"
46
+ change: "gutter prop 제거 (MenuPopoverProps 변경)"
47
+ detail: "DropdownContent(MenuPopover)에서 gutter prop이 제거됨"
48
+ scan: "gutter="
49
+ fix:
50
+ type: rename
51
+ from: '<DropdownContent gutter={4}>'
52
+ to: "<DropdownContent>"
53
+
54
+ - version: "1.2.0"
55
+ level: B
56
+ component: "FormStore"
57
+ change: "useValidate/useSubmit 메서드 제거 -> useFormStore 옵션으로 이동"
58
+ detail: "form.useValidate(callback)과 form.useSubmit(callback)이 제거됨. useFormStore({ validate, onSubmit, validateOn }) 옵션으로 전달. validateOn 기본값이 change로 변경됨 -- 기존 useValidate는 submit 시에만 실행되었으므로 반드시 validateOn: 'submit' 명시 필요."
59
+ scan: "useValidate\\|useSubmit"
60
+ fix:
61
+ type: restructure
62
+ from: "form.useValidate(() => { ... }); form.useSubmit(() => { ... })"
63
+ to: "useFormStore({ validateOn: 'submit', validate: (values) => errors, onSubmit: (values) => { ... } })"
64
+
65
+ - version: "1.2.0"
66
+ level: B
67
+ component: "TabsRoot"
68
+ change: "setSelectedId 콜백 타입 string|null -> string|undefined"
69
+ detail: "TabsRoot의 selectedId/setSelectedId가 null 대신 undefined 사용"
70
+ scan: "setSelectedId.\*null"
71
+ fix:
72
+ type: retype
73
+ from: "useState<string | null>"
74
+ to: "useState<string | undefined>"
75
+
76
+ ---
77
+
78
+ ## Version 1.1.1
79
+
80
+ 초기 릴리즈 이후 Breaking Change 없음 (Added만).
81
+
82
+ ### Added
83
+
84
+ - version: "1.1.1"
85
+ level: A
86
+ component: ChatSidebar
87
+ change: "새 Shell -- AI 채팅 사이드바"
88
+
89
+ - version: "1.1.1"
90
+ level: A
91
+ component: ChatInput
92
+ change: "새 Shell -- 채팅 입력 (자동 리사이즈, 모델/스타일 Select)"
93
+
94
+ - version: "1.1.1"
95
+ level: A
96
+ component: ChatMessageList
97
+ change: "새 Shell -- 채팅 메시지 목록 (말풍선, 타이핑 인디케이터)"
98
+
99
+ - version: "1.1.1"
100
+ level: A
101
+ component: ChatAssistant
102
+ change: "새 Surface -- AI 챗봇 어시스턴트 (ChatSidebar + ChatMessageList + ChatInput)"
103
+
104
+ ---
105
+
106
+ ## Scan Pattern Guide
107
+
108
+ 영향 분석에 사용할 grep 패턴 작성 규칙:
109
+
110
+ | 변경 유형 | 스캔 패턴 예시 |
111
+ | --------------- | ------------------------------------------------------------ |
112
+ | Prop rename | `propName.*=` 또는 `<Component[^>]*propName=` |
113
+ | Prop 값 변경 | `propName.*=.*["']oldValue["']` |
114
+ | 컴포넌트 rename | `from ["']@reopt-ai/opt-ui["']` 후 import 이름 확인 |
115
+ | 컴포넌트 삭제 | `import.*{[^}]*OldName[^}]*}.*from ["']@reopt-ai/opt-ui["']` |
116
+ | CSS 토큰 변경 | `className.*oldToken` |
117
+ | 타입 변경 | `import type.*{[^}]*OldType[^}]*}` |
@@ -0,0 +1,336 @@
1
+ ---
2
+ title: "FormStore Migration"
3
+ description: "opt-ui FormStore 마이그레이션 가이드 (1.1.x -> 1.2.0) — useValidate/useSubmit 제거, 제네릭 타입, 선언적 검증"
4
+ related:
5
+ links:
6
+ - ../03-recipes/01-forms.md
7
+ - 01-breaking-changes.md
8
+ ---
9
+
10
+ <!-- AI agent hint: useValidate(), useSubmit()를 발견하면 반드시 이 문서의 BEFORE/AFTER 패턴을 따라 useFormStore 옵션으로 변환할 것. as never 캐스트도 제거 대상. -->
11
+
12
+ # FormStore Migration Guide (1.1.x -> 1.2.0)
13
+
14
+ ## Overview
15
+
16
+ opt-ui 1.2.0 replaces the Ariakit-based form system with a self-contained `FormStore`.
17
+ The new store is built on `useSyncExternalStore` with zero external dependencies,
18
+ providing generic type inference, dirty tracking, async submit lifecycle,
19
+ and pluggable schema validation.
20
+
21
+ **Why the change:**
22
+
23
+ - Ariakit's `useFormStore` returned an opaque store requiring `as never` casts for typed access.
24
+ - `useValidate` / `useSubmit` were separate hooks that fragmented form logic across the component.
25
+ - No built-in dirty tracking, async submit state, or schema adapter.
26
+ - The new API consolidates everything into a single `useFormStore<V>(options)` call.
27
+
28
+ ---
29
+
30
+ ## Breaking Changes
31
+
32
+ ### 1. useValidate / useSubmit -> useFormStore options
33
+
34
+ The two standalone hooks are removed. Pass `validate` and `onSubmit` directly to `useFormStore`.
35
+
36
+ **BEFORE (1.1.x):**
37
+
38
+ ```tsx
39
+ import { useFormStore, useValidate, useSubmit } from "@reopt-ai/opt-ui";
40
+
41
+ function SettingsForm() {
42
+ const form = useFormStore({
43
+ defaultValues: { name: "", email: "" },
44
+ });
45
+
46
+ form.useValidate(() => {
47
+ if (!form.getValue("name")) {
48
+ form.setError("name", "Name is required");
49
+ }
50
+ });
51
+
52
+ form.useSubmit(() => {
53
+ console.log("Submitted:", form.getValues());
54
+ });
55
+
56
+ return <FormRoot store={form}>...</FormRoot>;
57
+ }
58
+ ```
59
+
60
+ **AFTER (1.2.0):**
61
+
62
+ ```tsx
63
+ import { useFormStore, type FormErrors } from "@reopt-ai/opt-ui";
64
+
65
+ function SettingsForm() {
66
+ const form = useFormStore({
67
+ defaultValues: { name: "", email: "" },
68
+ validate: (values) => {
69
+ const errors: FormErrors = {};
70
+ if (!values.name) errors.name = "Name is required";
71
+ return errors;
72
+ },
73
+ onSubmit: (values) => {
74
+ console.log("Submitted:", values);
75
+ },
76
+ });
77
+
78
+ return <FormRoot store={form}>...</FormRoot>;
79
+ }
80
+ ```
81
+
82
+ | Aspect | 1.1.x | 1.2.0 |
83
+ | ------------- | -------------------------------------------- | ---------------------------------------- |
84
+ | Validation | `form.useValidate(callback)` hook | `validate` option in `useFormStore` |
85
+ | Submit | `form.useSubmit(callback)` hook | `onSubmit` option in `useFormStore` |
86
+ | Error setting | Imperative `form.setError()` inside validate | Return `FormErrors` object from validate |
87
+ | Location | Scattered across component body | Colocated in store config |
88
+
89
+ ---
90
+
91
+ ### 2. Generic FormStore type
92
+
93
+ **BEFORE (1.1.x):**
94
+
95
+ ```tsx
96
+ const form = useFormStore({
97
+ defaultValues: { name: "", count: 0 },
98
+ });
99
+
100
+ const name = form.getValue(form.names.name) as string;
101
+ form.setValue(form.names.count, 5 as never);
102
+ ```
103
+
104
+ **AFTER (1.2.0):**
105
+
106
+ ```tsx
107
+ interface MyFormValues {
108
+ name: string;
109
+ count: number;
110
+ }
111
+
112
+ const form = useFormStore<MyFormValues>({
113
+ defaultValues: { name: "", count: 0 },
114
+ });
115
+
116
+ const name = form.getValue("name"); // type: string
117
+ form.setValue("count", 5); // type-checked
118
+ ```
119
+
120
+ The generic parameter `V` flows through `defaultValues`, `validate`, `onSubmit`, `getValue`, and `setValue`.
121
+
122
+ ---
123
+
124
+ ### 3. validate function signature
125
+
126
+ The validate function no longer mutates the store imperatively. Instead, it returns an errors object.
127
+
128
+ **BEFORE (1.1.x):**
129
+
130
+ ```tsx
131
+ form.useValidate(() => {
132
+ if (!form.getValue(form.names.email)) {
133
+ form.setError(form.names.email, "Required");
134
+ }
135
+ });
136
+ ```
137
+
138
+ **AFTER (1.2.0):**
139
+
140
+ ```tsx
141
+ const form = useFormStore({
142
+ defaultValues: { email: "", age: 0 },
143
+ validate: (values) => {
144
+ const errors: FormErrors = {};
145
+ if (!values.email) errors.email = "Required";
146
+ if (!values.age) errors.age = "Required";
147
+ return errors;
148
+ },
149
+ });
150
+ ```
151
+
152
+ Rules:
153
+
154
+ - Return `FormErrors` (`Partial<Record<string, string>>`) from `validate`.
155
+ - Return `{}` or `undefined` for no errors.
156
+ - Keys support dot-paths for nested fields: `"items.0.name"`.
157
+
158
+ ---
159
+
160
+ ### 4. async onSubmit
161
+
162
+ **BEFORE (1.1.x):**
163
+
164
+ ```tsx
165
+ const [loading, setLoading] = useState(false);
166
+ const [error, setError] = useState<Error | null>(null);
167
+
168
+ form.useSubmit(async () => {
169
+ setLoading(true);
170
+ setError(null);
171
+ try {
172
+ await api.save(form.getValues());
173
+ } catch (e) {
174
+ setError(e as Error);
175
+ } finally {
176
+ setLoading(false);
177
+ }
178
+ });
179
+ ```
180
+
181
+ **AFTER (1.2.0):**
182
+
183
+ ```tsx
184
+ const form = useFormStore({
185
+ defaultValues: { name: "" },
186
+ onSubmit: async (values) => {
187
+ await api.save(values); // throw on failure
188
+ },
189
+ });
190
+
191
+ form.isSubmitting(); // true while Promise is pending
192
+ form.getSubmitError(); // Error object if rejected
193
+ ```
194
+
195
+ No manual `useState` for loading or error. The store handles the full async lifecycle.
196
+
197
+ ---
198
+
199
+ ## New APIs (Additive)
200
+
201
+ These APIs are new in 1.2.0. Adopt when ready.
202
+
203
+ ### isDirty / isFieldDirty
204
+
205
+ ```tsx
206
+ form.isDirty(); // true if any field differs from defaultValues
207
+ form.isFieldDirty("email"); // true if "email" differs from its default
208
+ ```
209
+
210
+ ### resetField
211
+
212
+ ```tsx
213
+ form.resetField("email"); // Reset single field to default, clear its error
214
+ ```
215
+
216
+ ### validateOn option
217
+
218
+ ```tsx
219
+ const form = useFormStore({
220
+ defaultValues: { name: "" },
221
+ validateOn: "blur", // "change" (default) | "blur" | "submit"
222
+ validate: (values) => { ... },
223
+ });
224
+ ```
225
+
226
+ | Value | Behavior |
227
+ | ---------- | --------------------------------------------------------------- |
228
+ | `"change"` | Validate on every `setValue` call (default, immediate feedback) |
229
+ | `"blur"` | Validate when `setFieldTouched(name, true)` is called |
230
+ | `"submit"` | Validate only on `submit()` call (least intrusive) |
231
+
232
+ ### schemaValidator adapter
233
+
234
+ ```tsx
235
+ import { useFormStore, schemaValidator } from "@reopt-ai/opt-ui";
236
+ import { z } from "zod";
237
+
238
+ const schema = z.object({
239
+ email: z.string().email("Enter a valid email"),
240
+ age: z.number().min(1, "Age is required"),
241
+ });
242
+
243
+ const form = useFormStore({
244
+ defaultValues: { email: "", age: 0 },
245
+ validate: schemaValidator(schema),
246
+ });
247
+ ```
248
+
249
+ Works with any library exposing a `safeParse` method (Zod, Valibot, etc.).
250
+
251
+ ### useFieldValue hook
252
+
253
+ ```tsx
254
+ import { useFieldValue } from "@reopt-ai/opt-ui";
255
+
256
+ function EmailPreview({ form }) {
257
+ const email = useFieldValue<string>(form, "email");
258
+ return <p>Preview: {email}</p>;
259
+ }
260
+ ```
261
+
262
+ Re-renders only when that specific field changes.
263
+
264
+ ### FormProvider + useFormContext
265
+
266
+ ```tsx
267
+ import { FormProvider, useFormContext, useFormStore } from "@reopt-ai/opt-ui";
268
+
269
+ function MyForm() {
270
+ const form = useFormStore({ defaultValues: { name: "" } });
271
+ return (
272
+ <FormProvider store={form}>
273
+ <NameField />
274
+ </FormProvider>
275
+ );
276
+ }
277
+
278
+ function NameField() {
279
+ const form = useFormContext();
280
+ const name = useFieldValue<string>(form!, "name");
281
+ return (
282
+ <input
283
+ value={name}
284
+ onChange={(e) => form!.setValue("name", e.target.value)}
285
+ />
286
+ );
287
+ }
288
+ ```
289
+
290
+ `FormRoot` also wraps children with `FormProvider` automatically.
291
+
292
+ ---
293
+
294
+ ## Migration Checklist
295
+
296
+ ### Step 1: Identify affected files
297
+
298
+ ```bash
299
+ grep -rn "\.useValidate\|\.useSubmit" --include="*.tsx" --include="*.ts" .
300
+ grep -rn "as never" --include="*.tsx" --include="*.ts" . | grep -v node_modules
301
+ grep -rn "form\.names\." --include="*.tsx" --include="*.ts" . | grep -v node_modules
302
+ ```
303
+
304
+ ### Step 2: Replace useValidate
305
+
306
+ 1. Extract the validation logic from the callback body.
307
+ 2. Convert imperative `form.setError()` calls to a returned `FormErrors` object.
308
+ 3. Move the logic to the `validate` option in `useFormStore()`.
309
+ 4. Remove the `form.useValidate(...)` call.
310
+
311
+ ### Step 3: Replace useSubmit
312
+
313
+ 1. Move the callback to the `onSubmit` option in `useFormStore()`.
314
+ 2. Replace `form.getValues()` with the `values` parameter.
315
+ 3. If async, remove manual `loading`/`error` state -- use `form.isSubmitting()` and `form.getSubmitError()`.
316
+ 4. Remove the `form.useSubmit(...)` call.
317
+
318
+ ### Step 4: Remove `as never` casts
319
+
320
+ 1. Add a type parameter to `useFormStore<MyValues>(...)`.
321
+ 2. Replace `form.getValue(form.names.fieldName)` with `form.getValue("fieldName")`.
322
+ 3. Remove `as never` from `form.setValue(...)` calls.
323
+
324
+ ### Step 5: Update imports
325
+
326
+ ```diff
327
+ - import { useFormStore, useValidate, useSubmit } from "@reopt-ai/opt-ui";
328
+ + import { useFormStore, type FormErrors } from "@reopt-ai/opt-ui";
329
+ ```
330
+
331
+ ### Step 6: Verify
332
+
333
+ ```bash
334
+ npx tsc --noEmit
335
+ bun vitest run
336
+ ```
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: "Troubleshooting"
3
+ description: "opt-ui 설치, 스타일, 테마, 빌드, CLI 관련 일반적인 문제와 해결 방법"
4
+ related:
5
+ links:
6
+ - 01-getting-started.md
7
+ - 04-theming.md
8
+ ---
9
+
10
+ # Troubleshooting
11
+
12
+ ## 1. Install Fails With 404
13
+
14
+ 패키지 이름, 버전 또는 프로젝트의 registry override를 확인합니다.
15
+
16
+ 확인 사항:
17
+
18
+ 1. 패키지 이름이 `@reopt-ai/opt-ui`인지 확인
19
+ 2. 요청한 버전이 npm에 공개되었는지 확인
20
+ 3. `.npmrc`에서 `@reopt-ai` scope를 사설 registry로 덮어쓰지 않는지 확인
21
+
22
+ ```bash
23
+ npm view @reopt-ai/opt-ui version
24
+ ```
25
+
26
+ ## 2. Components Render Unstyled
27
+
28
+ 다음 중 하나가 누락된 경우입니다:
29
+
30
+ - `@import "@reopt-ai/opt-ui/tailwind.css";`
31
+ - 올바른 `@source` 경로
32
+ - Tailwind CSS 자체
33
+
34
+ Tailwind v4 패턴:
35
+
36
+ ```css
37
+ @import "tailwindcss";
38
+ @import "@reopt-ai/opt-ui/tailwind.css";
39
+ @source "../node_modules/@reopt-ai/opt-ui/dist";
40
+ ```
41
+
42
+ `@source` 경로는 CSS 파일 위치 기준 상대 경로입니다.
43
+
44
+ ## 3. Theme Does Not Switch Or `useOptTheme` Throws
45
+
46
+ `OptThemeProvider`가 누락된 경우입니다.
47
+
48
+ ```tsx
49
+ import { OptThemeProvider } from "@reopt-ai/opt-ui";
50
+
51
+ <OptThemeProvider defaultPreset="default">{children}</OptThemeProvider>;
52
+ ```
53
+
54
+ ## 4. Next.js Build Problems
55
+
56
+ Next.js에서 패키지 처리에 문제가 있으면 추가합니다:
57
+
58
+ ```ts
59
+ const nextConfig = {
60
+ transpilePackages: ["@reopt-ai/opt-ui"],
61
+ };
62
+
63
+ export default nextConfig;
64
+ ```
65
+
66
+ React 19과 최신 Next.js 버전도 확인합니다.
67
+
68
+ ## 5. Surface CLI Issues
69
+
70
+ 진단 시작:
71
+
72
+ ```bash
73
+ npx @reopt-ai/opt-cli surface info
74
+ npx @reopt-ai/opt-cli surface doctor
75
+ ```
76
+
77
+ 일반적인 원인:
78
+
79
+ - `opt-ui.json` 없음
80
+ - 잘못된 slug
81
+ - global CSS import 누락
82
+ - alias 불일치
83
+ - 설치 후 대규모 로컬 수정
84
+
85
+ 자동 수정 시도:
86
+
87
+ ```bash
88
+ npx @reopt-ai/opt-cli surface doctor --fix
89
+ ```
90
+
91
+ ## 6. Tailwind v3 Project
92
+
93
+ Tailwind v3에서는 `@source` 대신 `content` 설정을 사용합니다:
94
+
95
+ ```ts
96
+ // tailwind.config.ts
97
+ content: [
98
+ // ... your paths
99
+ "./node_modules/@reopt-ai/opt-ui/dist/**/*.{js,mjs}",
100
+ ];
101
+ ```
102
+
103
+ 가능하면 v4로 업그레이드하는 것을 권장합니다.
104
+
105
+ ## 7. Common CSS Variable Issues
106
+
107
+ 스타일이 부분적으로만 적용되는 경우:
108
+
109
+ - `[data-theme]` 속성이 `<html>` 또는 루트 요소에 설정되어 있는지 확인
110
+ - `OptThemeProvider`가 렌더 트리의 최상단에 있는지 확인
111
+ - 커스텀 프리셋 사용 시 모든 필수 CSS 변수가 정의되어 있는지 확인
112
+
113
+ ## 8. Dark Mode Not Working
114
+
115
+ 다크 모드는 `[data-theme="preset-dark"]` 셀렉터로 동작합니다:
116
+
117
+ - `OptThemeProvider`의 mode가 `"dark"`으로 설정되어 있는지 확인
118
+ - `@reopt-ai/opt-ui/tailwind.css`가 import되어 있는지 확인 (dark variant 정의 포함)
119
+ - Tailwind `dark:` 클래스가 자동으로 동작합니다 (`@custom-variant dark` 포함)
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: "@reopt-ai/opt-ui Documentation"
3
+ description: "opt-ui 디자인 시스템 패키지 문서 — Core/Shells/Surfaces 아키텍처, 설치, 레시피, 테마, 마이그레이션 가이드"
4
+ related:
5
+ title: Start here
6
+ links:
7
+ - 01-getting-started.md
8
+ - 02-components/index.md
9
+ - 03-recipes/01-forms.md
10
+ ---
11
+
12
+ <!-- AI agent hint: This is @reopt-ai/opt-ui documentation shipped with the package. Component API reference is in 02-components/ (auto-generated). For copy-paste recipes, see 03-recipes/. For version migration, see 05-migration/. For theming, see 04-theming.md. -->
13
+
14
+ # @reopt-ai/opt-ui
15
+
16
+ React 디자인 시스템 라이브러리. opt-ui-primitives 기반의 접근성 우선 컴포넌트, 비즈니스 로직 조합 Shell, 페이지 단위 Surface 템플릿을 제공합니다. 차트와 데이터 시각화는 `@reopt-ai/opt-charts` 패키지에서 제공합니다.
17
+
18
+ ## Architecture
19
+
20
+ opt-ui는 Core/Shells/Surfaces 레이어를 소유하고, 차트는 opt-charts가 소유합니다.
21
+
22
+ | Layer | Role | Count |
23
+ | ------------ | ------------------------------------- | ------- |
24
+ | **Core** | opt-ui-primitives 기반, 단일 책임 | 55 |
25
+ | **Visuals** | `@reopt-ai/opt-charts` 호환 re-export | 0 local |
26
+ | **Shells** | Core 조합 + 비즈니스 로직 | 79 |
27
+ | **Surfaces** | Shells 조합, 페이지 템플릿 (CLI) | 7 |
28
+
29
+ - **Core**: Button, Input, Form, Dialog, Tabs, Select, Checkbox, RadioGroup 등 기본 UI
30
+ - **Visuals**: LineChart, BarChart, PieChart, GaugeChart, SankeyChart 등 차트는 `@reopt-ai/opt-charts`에서 직접 import
31
+ - **Shells**: DataTable, FilterBar, SettingsForm, CommandPalette, KanbanBoard 등 비즈니스 UI
32
+ - **Surfaces**: 완성된 페이지 템플릿 (`npx @reopt-ai/opt-cli surface add <slug>`로 프로젝트에 복사)
33
+
34
+ ## Quick Install
35
+
36
+ ```bash
37
+ bun add @reopt-ai/opt-ui @reopt-ai/opt-ui-primitives
38
+ ```
39
+
40
+ 공개 npm registry에서 별도 인증 없이 설치할 수 있습니다. 상세 설치 가이드는 [01-getting-started.md](./01-getting-started.md)를 참고하세요.
41
+
42
+ ## Docs Structure
43
+
44
+ ```
45
+ docs/
46
+ index.md # 이 파일 — 개요 및 구조 안내
47
+ 01-getting-started.md # 설치, Next.js 설정, Tailwind 연동
48
+ 02-components/ # 컴포넌트 API 레퍼런스 (auto-generated)
49
+ 03-recipes/
50
+ 01-forms.md # FormStore 기반 폼 패턴 4종
51
+ 02-dashboards.md # 대시보드 조합 패턴 3종
52
+ 03-layouts.md # 레이아웃 조합 패턴 3종
53
+ 04-theming.md # 5개 프리셋, CSS 변수, 커스텀 테마
54
+ 05-migration/
55
+ 01-breaking-changes.md # 버전별 Breaking Change 레지스트리
56
+ 02-formstore.md # FormStore 마이그레이션 (1.1.x → 1.2.0)
57
+ 06-troubleshooting.md # 설치/스타일/테마/빌드 문제 해결
58
+ ```
59
+
60
+ ## Test Coverage Audit
61
+
62
+ `bun run test`는 Vitest 이후 `scripts/validate-test-coverage.ts`를 실행합니다.
63
+ 이 감사는 opt-ui ComponentMeta 항목이 모두 `testDescribe`를 갖고, 같은 이름의
64
+ `describe(...)` 블록이 테스트 파일에 존재하는지 확인합니다.
65
+
66
+ ## Key Dependencies
67
+
68
+ - React 19+
69
+ - Tailwind CSS v4
70
+ - `@reopt-ai/opt-ui-primitives` (WAI-ARIA 패턴)
71
+ - `@reopt-ai/opt-charts` (차트/데이터 시각화)