@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.
- package/CHANGELOG.md +40 -0
- package/COMPONENT_CATALOG.md +3000 -0
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/chunk-3GWWZKX7.js +38 -0
- package/dist/chunk-AFF2HPE5.cjs +5008 -0
- package/dist/chunk-ELWICXYY.js +4745 -0
- package/dist/chunk-N4NDU5ET.cjs +4745 -0
- package/dist/chunk-ONE3C5RV.cjs +38 -0
- package/dist/chunk-QWBHD54V.js +3218 -0
- package/dist/chunk-RBM2RNC2.js +5008 -0
- package/dist/chunk-X5WCXJAF.cjs +3218 -0
- package/dist/core/index.cjs +324 -0
- package/dist/core/index.d.cts +5 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +324 -0
- package/dist/docs/01-getting-started.md +129 -0
- package/dist/docs/02-components/01-core.md +1841 -0
- package/dist/docs/02-components/02-visuals.md +11 -0
- package/dist/docs/02-components/03-shells.md +1361 -0
- package/dist/docs/02-components/04-surfaces.md +11 -0
- package/dist/docs/02-components/index.md +106 -0
- package/dist/docs/03-recipes/01-forms.md +471 -0
- package/dist/docs/03-recipes/02-dashboards.md +397 -0
- package/dist/docs/03-recipes/03-layouts.md +424 -0
- package/dist/docs/04-theming.md +232 -0
- package/dist/docs/05-migration/01-breaking-changes.md +117 -0
- package/dist/docs/05-migration/02-formstore.md +336 -0
- package/dist/docs/06-troubleshooting.md +119 -0
- package/dist/docs/index.md +71 -0
- package/dist/id-registry.cjs +1875 -0
- package/dist/id-registry.d.cts +27 -0
- package/dist/id-registry.d.ts +27 -0
- package/dist/id-registry.js +1875 -0
- package/dist/id-registry.json +3799 -0
- package/dist/index-BZ_lBlO1.d.ts +474 -0
- package/dist/index-BuvxoWHf.d.cts +474 -0
- package/dist/index-DlAcuvQp.d.cts +1686 -0
- package/dist/index-Slu5hOj1.d.ts +1686 -0
- package/dist/index.cjs +9959 -0
- package/dist/index.d.cts +2351 -0
- package/dist/index.d.ts +2351 -0
- package/dist/index.js +9959 -0
- package/dist/meta.cjs +6898 -0
- package/dist/meta.d.cts +36 -0
- package/dist/meta.d.ts +36 -0
- package/dist/meta.js +6869 -0
- package/dist/shells/index.cjs +65 -0
- package/dist/shells/index.d.cts +5 -0
- package/dist/shells/index.d.ts +5 -0
- package/dist/shells/index.js +65 -0
- package/dist/tailwind.css +401 -0
- package/dist/theme/presets/default.css +355 -0
- package/dist/theme/presets/minimal.css +354 -0
- package/dist/theme/presets/mono-dark.css +354 -0
- package/dist/theme/presets/natural.css +181 -0
- package/dist/theme/presets/pro.css +354 -0
- package/dist/types-D4-0lwaE.d.cts +298 -0
- package/dist/types-D4-0lwaE.d.ts +298 -0
- package/dist/visuals/index.cjs +4 -0
- package/dist/visuals/index.d.cts +1 -0
- package/dist/visuals/index.d.ts +1 -0
- package/dist/visuals/index.js +4 -0
- 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` (차트/데이터 시각화)
|