@openconsole/shadcn 0.0.0 → 0.0.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 (71) hide show
  1. package/accordion.tsx +66 -66
  2. package/alert-dialog.tsx +196 -196
  3. package/alert.tsx +66 -66
  4. package/aspect-ratio.tsx +11 -11
  5. package/avatar.tsx +53 -53
  6. package/badge.tsx +46 -46
  7. package/breadcrumb.tsx +109 -109
  8. package/button-group.tsx +83 -83
  9. package/button.tsx +60 -60
  10. package/calendar.tsx +219 -219
  11. package/card.tsx +92 -92
  12. package/carousel.tsx +241 -241
  13. package/chart.tsx +374 -374
  14. package/checkbox.tsx +32 -32
  15. package/collapsible.tsx +33 -33
  16. package/command.tsx +184 -184
  17. package/context-menu.tsx +252 -252
  18. package/dialog.tsx +143 -143
  19. package/direction.tsx +22 -22
  20. package/drawer.tsx +135 -135
  21. package/dropdown-menu.tsx +257 -257
  22. package/empty.tsx +104 -104
  23. package/field.tsx +248 -248
  24. package/form.tsx +167 -167
  25. package/hooks/index.ts +1 -1
  26. package/hooks/use-mobile.ts +19 -19
  27. package/hover-card.tsx +44 -44
  28. package/icon.tsx +21 -21
  29. package/index.ts +59 -59
  30. package/input-group.tsx +170 -170
  31. package/input-otp.tsx +77 -77
  32. package/input.tsx +21 -21
  33. package/item.tsx +193 -193
  34. package/kbd.tsx +28 -28
  35. package/label.tsx +24 -24
  36. package/lib/index.ts +1 -1
  37. package/lib/utils.ts +6 -6
  38. package/menubar.tsx +276 -276
  39. package/native-select.tsx +62 -62
  40. package/navigation-menu.tsx +168 -168
  41. package/package.json +10 -2
  42. package/pagination.tsx +127 -127
  43. package/popover.tsx +89 -89
  44. package/progress.tsx +31 -31
  45. package/radio-group.tsx +45 -45
  46. package/resizable.tsx +53 -53
  47. package/scroll-area.tsx +58 -58
  48. package/select.tsx +187 -187
  49. package/separator.tsx +28 -28
  50. package/sheet.tsx +139 -139
  51. package/sidebar.tsx +724 -724
  52. package/skeleton.tsx +13 -13
  53. package/skill/SKILL.md +620 -599
  54. package/skill/customization.md +301 -263
  55. package/skill/rules/base-vs-radix.md +167 -167
  56. package/skill/rules/composition.md +240 -240
  57. package/skill/rules/forms.md +271 -271
  58. package/skill/rules/icons.md +136 -136
  59. package/skill/rules/styling.md +180 -180
  60. package/slider.tsx +63 -63
  61. package/sonner.tsx +40 -40
  62. package/spinner.tsx +16 -16
  63. package/styles.css +122 -0
  64. package/switch.tsx +35 -35
  65. package/table.tsx +116 -116
  66. package/tabs.tsx +66 -66
  67. package/textarea.tsx +18 -18
  68. package/toggle-group.tsx +83 -83
  69. package/toggle.tsx +47 -47
  70. package/tooltip.tsx +61 -61
  71. package/tsconfig.json +12 -12
@@ -1,271 +1,271 @@
1
- # 表单 & 输入
2
-
3
- ## 目录
4
-
5
- - 表单用 `FieldGroup` + `Field`
6
- - `InputGroup` 要求 `InputGroupInput` / `InputGroupTextarea`
7
- - 输入框里的按钮用 `InputGroup` + `InputGroupAddon`
8
- - 2–7 个选项用 `ToggleGroup`
9
- - `FieldSet` + `FieldLegend` 给相关字段分组
10
- - 校验和禁用状态
11
- - react-hook-form 整合
12
-
13
- ---
14
-
15
- ## 表单用 `FieldGroup` + `Field`
16
-
17
- 总是用 `FieldGroup` + `Field` —— **从不**用 `<div className="space-y-*">`:
18
-
19
- ```tsx
20
- import {
21
- FieldGroup, Field, FieldLabel, Input,
22
- } from "@openconsole/shadcn";
23
-
24
- <FieldGroup>
25
- <Field>
26
- <FieldLabel htmlFor="email">Email</FieldLabel>
27
- <Input id="email" type="email" />
28
- </Field>
29
- <Field>
30
- <FieldLabel htmlFor="password">Password</FieldLabel>
31
- <Input id="password" type="password" />
32
- </Field>
33
- </FieldGroup>
34
- ```
35
-
36
- 设置页用 `Field orientation="horizontal"`。视觉上隐藏 label 用
37
- `FieldLabel className="sr-only"`。
38
-
39
- **怎么选表单控件:**
40
-
41
- | 场景 | 用什么 |
42
- |---|---|
43
- | 简单文本输入 | `Input` |
44
- | 预定义选项下拉 | `Select` |
45
- | 可搜索下拉 | `Popover` + `Command`(`CommandInput` + `CommandList` + `CommandGroup` + `CommandItem`) |
46
- | 原生 HTML select(无 JS) | `NativeSelect` |
47
- | 布尔切换 | `Switch`(设置)或 `Checkbox`(表单) |
48
- | 几个选项里单选 | `RadioGroup` |
49
- | 2–5 个选项切换 | `ToggleGroup` + `ToggleGroupItem` |
50
- | OTP / 验证码 | `InputOTP` |
51
- | 多行文本 | `Textarea` |
52
-
53
- ---
54
-
55
- ## `InputGroup` 要求 `InputGroupInput` / `InputGroupTextarea`
56
-
57
- **永远不要**在 `InputGroup` 里塞裸 `Input` 或 `Textarea`。
58
-
59
- **Incorrect:**
60
-
61
- ```tsx
62
- <InputGroup>
63
- <Input placeholder="Search..." />
64
- </InputGroup>
65
- ```
66
-
67
- **Correct:**
68
-
69
- ```tsx
70
- import { InputGroup, InputGroupInput } from "@openconsole/shadcn";
71
-
72
- <InputGroup>
73
- <InputGroupInput placeholder="Search..." />
74
- </InputGroup>
75
- ```
76
-
77
- ---
78
-
79
- ## 输入框里的按钮用 `InputGroup` + `InputGroupAddon`
80
-
81
- **永远不要**用 `position: relative` + `position: absolute` 把按钮
82
- 塞到 `Input` 上。
83
-
84
- **Incorrect:**
85
-
86
- ```tsx
87
- <div className="relative">
88
- <Input placeholder="Search..." className="pr-10" />
89
- <Button className="absolute right-0 top-0" size="icon">
90
- <SearchIcon />
91
- </Button>
92
- </div>
93
- ```
94
-
95
- **Correct:**
96
-
97
- ```tsx
98
- import {
99
- InputGroup, InputGroupInput, InputGroupAddon, Button,
100
- } from "@openconsole/shadcn";
101
-
102
- <InputGroup>
103
- <InputGroupInput placeholder="Search..." />
104
- <InputGroupAddon>
105
- <Button size="icon">
106
- <SearchIcon data-icon="inline-start" />
107
- </Button>
108
- </InputGroupAddon>
109
- </InputGroup>
110
- ```
111
-
112
- ---
113
-
114
- ## 2–7 个选项用 `ToggleGroup`
115
-
116
- 不要循环 `Button` 然后自己管 active 状态。
117
-
118
- **Incorrect:**
119
-
120
- ```tsx
121
- const [selected, setSelected] = useState("daily")
122
-
123
- <div className="flex gap-2">
124
- {["daily", "weekly", "monthly"].map((option) => (
125
- <Button
126
- key={option}
127
- variant={selected === option ? "default" : "outline"}
128
- onClick={() => setSelected(option)}
129
- >
130
- {option}
131
- </Button>
132
- ))}
133
- </div>
134
- ```
135
-
136
- **Correct:**
137
-
138
- ```tsx
139
- import { ToggleGroup, ToggleGroupItem } from "@openconsole/shadcn";
140
-
141
- <ToggleGroup type="single" defaultValue="daily" spacing={2}>
142
- <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
143
- <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
144
- <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
145
- </ToggleGroup>
146
- ```
147
-
148
- > 本包用 **radix** 风格,所以是 `type="single"` / `type="multiple"`,
149
- > `defaultValue` 是字符串。如果你从 ui.shadcn.com 复制了 base 风格的
150
- > `<ToggleGroup multiple defaultValue={["daily"]}>`,需要改写 ——
151
- > 见 [base-vs-radix.md —— ToggleGroup](./base-vs-radix.md#togglegroup)。
152
-
153
- 带 label 的 toggle group:
154
-
155
- ```tsx
156
- <Field orientation="horizontal">
157
- <FieldTitle id="theme-label">Theme</FieldTitle>
158
- <ToggleGroup type="single" aria-labelledby="theme-label" spacing={2}>
159
- <ToggleGroupItem value="light">Light</ToggleGroupItem>
160
- <ToggleGroupItem value="dark">Dark</ToggleGroupItem>
161
- <ToggleGroupItem value="system">System</ToggleGroupItem>
162
- </ToggleGroup>
163
- </Field>
164
- ```
165
-
166
- ---
167
-
168
- ## `FieldSet` + `FieldLegend` 给相关字段分组
169
-
170
- 相关的 checkbox / radio / switch 用 `FieldSet` + `FieldLegend` —— 不是
171
- `div` 加标题:
172
-
173
- ```tsx
174
- <FieldSet>
175
- <FieldLegend variant="label">Preferences</FieldLegend>
176
- <FieldDescription>Select all that apply.</FieldDescription>
177
- <FieldGroup className="gap-3">
178
- <Field orientation="horizontal">
179
- <Checkbox id="dark" />
180
- <FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
181
- </Field>
182
- </FieldGroup>
183
- </FieldSet>
184
- ```
185
-
186
- ---
187
-
188
- ## 校验和禁用状态
189
-
190
- 两套属性都要标。`data-invalid` / `data-disabled` 控制 `Field` 周围
191
- (label、description)的样式;`aria-invalid` / `disabled` 控制 control
192
- 本身的样式。
193
-
194
- ```tsx
195
- // 无效
196
- <Field data-invalid>
197
- <FieldLabel htmlFor="email">Email</FieldLabel>
198
- <Input id="email" aria-invalid />
199
- <FieldDescription>Invalid email address.</FieldDescription>
200
- </Field>
201
-
202
- // 禁用
203
- <Field data-disabled>
204
- <FieldLabel htmlFor="email">Email</FieldLabel>
205
- <Input id="email" disabled />
206
- </Field>
207
- ```
208
-
209
- 适用于所有 control: `Input`、`Textarea`、`Select`、`Checkbox`、
210
- `RadioGroupItem`、`Switch`、`Slider`、`NativeSelect`、`InputOTP`。
211
-
212
- ---
213
-
214
- ## react-hook-form 整合
215
-
216
- 本包提供了一套薄包装把 react-hook-form 跟 `Field` 体系串起来:
217
-
218
- ```tsx
219
- "use client";
220
-
221
- import { zodResolver } from "@hookform/resolvers/zod";
222
- import { useForm } from "react-hook-form";
223
- import { z } from "zod";
224
-
225
- import {
226
- Form, FormField, FormItem, FormLabel, FormControl,
227
- FormDescription, FormMessage,
228
- Input, Button,
229
- } from "@openconsole/shadcn";
230
-
231
- const schema = z.object({
232
- email: z.string().email(),
233
- });
234
-
235
- export function EmailForm() {
236
- const form = useForm<z.infer<typeof schema>>({
237
- resolver: zodResolver(schema),
238
- defaultValues: { email: "" },
239
- });
240
-
241
- return (
242
- <Form {...form}>
243
- <form onSubmit={form.handleSubmit((v) => console.log(v))}>
244
- <FormField
245
- control={form.control}
246
- name="email"
247
- render={({ field }) => (
248
- <FormItem>
249
- <FormLabel>Email</FormLabel>
250
- <FormControl>
251
- <Input {...field} />
252
- </FormControl>
253
- <FormDescription>We'll never share your email.</FormDescription>
254
- <FormMessage />
255
- </FormItem>
256
- )}
257
- />
258
- <Button type="submit">Submit</Button>
259
- </form>
260
- </Form>
261
- );
262
- }
263
- ```
264
-
265
- 要在自定义控件里读 form 状态用 `useFormField()`:
266
-
267
- ```tsx
268
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
269
- ```
270
-
271
- > `useFormField()` 必须用在 `<FormItem>` 内部 —— 否则抛错。
1
+ # 表单 & 输入
2
+
3
+ ## 目录
4
+
5
+ - 表单用 `FieldGroup` + `Field`
6
+ - `InputGroup` 要求 `InputGroupInput` / `InputGroupTextarea`
7
+ - 输入框里的按钮用 `InputGroup` + `InputGroupAddon`
8
+ - 2–7 个选项用 `ToggleGroup`
9
+ - `FieldSet` + `FieldLegend` 给相关字段分组
10
+ - 校验和禁用状态
11
+ - react-hook-form 整合
12
+
13
+ ---
14
+
15
+ ## 表单用 `FieldGroup` + `Field`
16
+
17
+ 总是用 `FieldGroup` + `Field` —— **从不**用 `<div className="space-y-*">`:
18
+
19
+ ```tsx
20
+ import {
21
+ FieldGroup, Field, FieldLabel, Input,
22
+ } from "@openconsole/shadcn";
23
+
24
+ <FieldGroup>
25
+ <Field>
26
+ <FieldLabel htmlFor="email">Email</FieldLabel>
27
+ <Input id="email" type="email" />
28
+ </Field>
29
+ <Field>
30
+ <FieldLabel htmlFor="password">Password</FieldLabel>
31
+ <Input id="password" type="password" />
32
+ </Field>
33
+ </FieldGroup>
34
+ ```
35
+
36
+ 设置页用 `Field orientation="horizontal"`。视觉上隐藏 label 用
37
+ `FieldLabel className="sr-only"`。
38
+
39
+ **怎么选表单控件:**
40
+
41
+ | 场景 | 用什么 |
42
+ |---|---|
43
+ | 简单文本输入 | `Input` |
44
+ | 预定义选项下拉 | `Select` |
45
+ | 可搜索下拉 | `Popover` + `Command`(`CommandInput` + `CommandList` + `CommandGroup` + `CommandItem`) |
46
+ | 原生 HTML select(无 JS) | `NativeSelect` |
47
+ | 布尔切换 | `Switch`(设置)或 `Checkbox`(表单) |
48
+ | 几个选项里单选 | `RadioGroup` |
49
+ | 2–5 个选项切换 | `ToggleGroup` + `ToggleGroupItem` |
50
+ | OTP / 验证码 | `InputOTP` |
51
+ | 多行文本 | `Textarea` |
52
+
53
+ ---
54
+
55
+ ## `InputGroup` 要求 `InputGroupInput` / `InputGroupTextarea`
56
+
57
+ **永远不要**在 `InputGroup` 里塞裸 `Input` 或 `Textarea`。
58
+
59
+ **Incorrect:**
60
+
61
+ ```tsx
62
+ <InputGroup>
63
+ <Input placeholder="Search..." />
64
+ </InputGroup>
65
+ ```
66
+
67
+ **Correct:**
68
+
69
+ ```tsx
70
+ import { InputGroup, InputGroupInput } from "@openconsole/shadcn";
71
+
72
+ <InputGroup>
73
+ <InputGroupInput placeholder="Search..." />
74
+ </InputGroup>
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 输入框里的按钮用 `InputGroup` + `InputGroupAddon`
80
+
81
+ **永远不要**用 `position: relative` + `position: absolute` 把按钮
82
+ 塞到 `Input` 上。
83
+
84
+ **Incorrect:**
85
+
86
+ ```tsx
87
+ <div className="relative">
88
+ <Input placeholder="Search..." className="pr-10" />
89
+ <Button className="absolute right-0 top-0" size="icon">
90
+ <SearchIcon />
91
+ </Button>
92
+ </div>
93
+ ```
94
+
95
+ **Correct:**
96
+
97
+ ```tsx
98
+ import {
99
+ InputGroup, InputGroupInput, InputGroupAddon, Button,
100
+ } from "@openconsole/shadcn";
101
+
102
+ <InputGroup>
103
+ <InputGroupInput placeholder="Search..." />
104
+ <InputGroupAddon>
105
+ <Button size="icon">
106
+ <SearchIcon data-icon="inline-start" />
107
+ </Button>
108
+ </InputGroupAddon>
109
+ </InputGroup>
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 2–7 个选项用 `ToggleGroup`
115
+
116
+ 不要循环 `Button` 然后自己管 active 状态。
117
+
118
+ **Incorrect:**
119
+
120
+ ```tsx
121
+ const [selected, setSelected] = useState("daily")
122
+
123
+ <div className="flex gap-2">
124
+ {["daily", "weekly", "monthly"].map((option) => (
125
+ <Button
126
+ key={option}
127
+ variant={selected === option ? "default" : "outline"}
128
+ onClick={() => setSelected(option)}
129
+ >
130
+ {option}
131
+ </Button>
132
+ ))}
133
+ </div>
134
+ ```
135
+
136
+ **Correct:**
137
+
138
+ ```tsx
139
+ import { ToggleGroup, ToggleGroupItem } from "@openconsole/shadcn";
140
+
141
+ <ToggleGroup type="single" defaultValue="daily" spacing={2}>
142
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
143
+ <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
144
+ <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
145
+ </ToggleGroup>
146
+ ```
147
+
148
+ > 本包用 **radix** 风格,所以是 `type="single"` / `type="multiple"`,
149
+ > `defaultValue` 是字符串。如果你从 ui.shadcn.com 复制了 base 风格的
150
+ > `<ToggleGroup multiple defaultValue={["daily"]}>`,需要改写 ——
151
+ > 见 [base-vs-radix.md —— ToggleGroup](./base-vs-radix.md#togglegroup)。
152
+
153
+ 带 label 的 toggle group:
154
+
155
+ ```tsx
156
+ <Field orientation="horizontal">
157
+ <FieldTitle id="theme-label">Theme</FieldTitle>
158
+ <ToggleGroup type="single" aria-labelledby="theme-label" spacing={2}>
159
+ <ToggleGroupItem value="light">Light</ToggleGroupItem>
160
+ <ToggleGroupItem value="dark">Dark</ToggleGroupItem>
161
+ <ToggleGroupItem value="system">System</ToggleGroupItem>
162
+ </ToggleGroup>
163
+ </Field>
164
+ ```
165
+
166
+ ---
167
+
168
+ ## `FieldSet` + `FieldLegend` 给相关字段分组
169
+
170
+ 相关的 checkbox / radio / switch 用 `FieldSet` + `FieldLegend` —— 不是
171
+ `div` 加标题:
172
+
173
+ ```tsx
174
+ <FieldSet>
175
+ <FieldLegend variant="label">Preferences</FieldLegend>
176
+ <FieldDescription>Select all that apply.</FieldDescription>
177
+ <FieldGroup className="gap-3">
178
+ <Field orientation="horizontal">
179
+ <Checkbox id="dark" />
180
+ <FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
181
+ </Field>
182
+ </FieldGroup>
183
+ </FieldSet>
184
+ ```
185
+
186
+ ---
187
+
188
+ ## 校验和禁用状态
189
+
190
+ 两套属性都要标。`data-invalid` / `data-disabled` 控制 `Field` 周围
191
+ (label、description)的样式;`aria-invalid` / `disabled` 控制 control
192
+ 本身的样式。
193
+
194
+ ```tsx
195
+ // 无效
196
+ <Field data-invalid>
197
+ <FieldLabel htmlFor="email">Email</FieldLabel>
198
+ <Input id="email" aria-invalid />
199
+ <FieldDescription>Invalid email address.</FieldDescription>
200
+ </Field>
201
+
202
+ // 禁用
203
+ <Field data-disabled>
204
+ <FieldLabel htmlFor="email">Email</FieldLabel>
205
+ <Input id="email" disabled />
206
+ </Field>
207
+ ```
208
+
209
+ 适用于所有 control: `Input`、`Textarea`、`Select`、`Checkbox`、
210
+ `RadioGroupItem`、`Switch`、`Slider`、`NativeSelect`、`InputOTP`。
211
+
212
+ ---
213
+
214
+ ## react-hook-form 整合
215
+
216
+ 本包提供了一套薄包装把 react-hook-form 跟 `Field` 体系串起来:
217
+
218
+ ```tsx
219
+ "use client";
220
+
221
+ import { zodResolver } from "@hookform/resolvers/zod";
222
+ import { useForm } from "react-hook-form";
223
+ import { z } from "zod";
224
+
225
+ import {
226
+ Form, FormField, FormItem, FormLabel, FormControl,
227
+ FormDescription, FormMessage,
228
+ Input, Button,
229
+ } from "@openconsole/shadcn";
230
+
231
+ const schema = z.object({
232
+ email: z.string().email(),
233
+ });
234
+
235
+ export function EmailForm() {
236
+ const form = useForm<z.infer<typeof schema>>({
237
+ resolver: zodResolver(schema),
238
+ defaultValues: { email: "" },
239
+ });
240
+
241
+ return (
242
+ <Form {...form}>
243
+ <form onSubmit={form.handleSubmit((v) => console.log(v))}>
244
+ <FormField
245
+ control={form.control}
246
+ name="email"
247
+ render={({ field }) => (
248
+ <FormItem>
249
+ <FormLabel>Email</FormLabel>
250
+ <FormControl>
251
+ <Input {...field} />
252
+ </FormControl>
253
+ <FormDescription>We'll never share your email.</FormDescription>
254
+ <FormMessage />
255
+ </FormItem>
256
+ )}
257
+ />
258
+ <Button type="submit">Submit</Button>
259
+ </form>
260
+ </Form>
261
+ );
262
+ }
263
+ ```
264
+
265
+ 要在自定义控件里读 form 状态用 `useFormField()`:
266
+
267
+ ```tsx
268
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
269
+ ```
270
+
271
+ > `useFormField()` 必须用在 `<FormItem>` 内部 —— 否则抛错。