@openconsole/shadcn 0.0.1 → 0.2.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.
@@ -1,271 +0,0 @@
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,136 +0,0 @@
1
- # 图标
2
-
3
- 本包的图标库**固定为 `lucide-react`**。所有图标从那里导入,或者通过
4
- 本包导出的 `Icon` 包装按名字动态渲染。
5
-
6
- ---
7
-
8
- ## `Button` 里的图标用 `data-icon` 属性
9
-
10
- 加 `data-icon="inline-start"`(前缀)或 `data-icon="inline-end"`(后缀)
11
- 到图标上。**图标上不要加尺寸 class**。
12
-
13
- **Incorrect:**
14
-
15
- ```tsx
16
- <Button>
17
- <SearchIcon className="mr-2 size-4" />
18
- Search
19
- </Button>
20
- ```
21
-
22
- **Correct:**
23
-
24
- ```tsx
25
- <Button>
26
- <SearchIcon data-icon="inline-start"/>
27
- Search
28
- </Button>
29
-
30
- <Button>
31
- Next
32
- <ArrowRightIcon data-icon="inline-end"/>
33
- </Button>
34
- ```
35
-
36
- ---
37
-
38
- ## 组件内部图标不要加尺寸 class
39
-
40
- `Button`、`DropdownMenuItem`、`Alert`、`Sidebar*` 等组件通过 CSS 自己管
41
- 图标尺寸。**不要**手动加 `size-4` / `w-4 h-4`。除非用户明确要求自定义
42
- 图标尺寸。
43
-
44
- **Incorrect:**
45
-
46
- ```tsx
47
- <Button>
48
- <SearchIcon className="size-4" data-icon="inline-start" />
49
- Search
50
- </Button>
51
-
52
- <DropdownMenuItem>
53
- <SettingsIcon className="mr-2 size-4" />
54
- Settings
55
- </DropdownMenuItem>
56
- ```
57
-
58
- **Correct:**
59
-
60
- ```tsx
61
- <Button>
62
- <SearchIcon data-icon="inline-start" />
63
- Search
64
- </Button>
65
-
66
- <DropdownMenuItem>
67
- <SettingsIcon />
68
- Settings
69
- </DropdownMenuItem>
70
- ```
71
-
72
- ---
73
-
74
- ## 图标当组件对象传,不要当字符串 key
75
-
76
- 用 `icon={CheckIcon}`,不要用字符串 key 去查表。
77
-
78
- **Incorrect:**
79
-
80
- ```tsx
81
- const iconMap = {
82
- check: CheckIcon,
83
- alert: AlertIcon,
84
- };
85
-
86
- function StatusBadge({ icon }: { icon: string }) {
87
- const Icon = iconMap[icon];
88
- return <Icon />;
89
- }
90
-
91
- <StatusBadge icon="check" />
92
- ```
93
-
94
- **Correct:**
95
-
96
- ```tsx
97
- import { CheckIcon } from "lucide-react";
98
-
99
- function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
100
- return <Icon />;
101
- }
102
-
103
- <StatusBadge icon={CheckIcon} />
104
- ```
105
-
106
- ---
107
-
108
- ## 按名字动态渲染用 `Icon`
109
-
110
- 当图标名字来自数据(菜单配置、主题预设、CMS 内容)而不是代码里写死的
111
- import 时,用本包导出的 `Icon`:
112
-
113
- ```tsx
114
- import { Icon } from "@openconsole/shadcn";
115
-
116
- // 数据
117
- const menu = [
118
- { label: "Dashboard", icon: "LayoutDashboard", href: "/" },
119
- { label: "Settings", icon: "Settings", href: "/settings" },
120
- ];
121
-
122
- // 渲染
123
- {menu.map((item) => (
124
- <a key={item.href} href={item.href}>
125
- <Icon name={item.icon} />
126
- {item.label}
127
- </a>
128
- ))}
129
- ```
130
-
131
- `Icon name="…"` 接受 `lucide-react` 里**任何**图标的 PascalCase 名字
132
- (`LayoutDashboard`、`ChevronRight`、`Settings` 等)。找不到名字时
133
- 渲染为 `null`(不抛错)。
134
-
135
- > 编译期能确定的图标用普通 import —— `<SearchIcon />` 在 tree-shaking
136
- > 下比 `<Icon name="Search" />` 更省 bundle。
@@ -1,180 +0,0 @@
1
- # 样式 & 定制
2
-
3
- 主题 / CSS 变量 / 自定义色见 [customization.md](../customization.md)。
4
- 这份文件全是 Incorrect / Correct 对照,方便快速识别和修复违例。
5
-
6
- ## 目录
7
-
8
- - 语义色
9
- - 状态色不要裸值
10
- - 内置 variant 优先
11
- - `className` 只管布局
12
- - 不要 `space-x-*` / `space-y-*`
13
- - 宽高相等用 `size-*`
14
- - `truncate` 简写
15
- - 不要手写 `dark:` 颜色覆盖
16
- - `cn()` 用于条件 class
17
- - overlay 不要手写 z-index
18
-
19
- ---
20
-
21
- ## 语义色
22
-
23
- **Incorrect:**
24
-
25
- ```tsx
26
- <div className="bg-blue-500 text-white">
27
- <p className="text-gray-600">Secondary text</p>
28
- </div>
29
- ```
30
-
31
- **Correct:**
32
-
33
- ```tsx
34
- <div className="bg-primary text-primary-foreground">
35
- <p className="text-muted-foreground">Secondary text</p>
36
- </div>
37
- ```
38
-
39
- ---
40
-
41
- ## 状态色不要裸值
42
-
43
- 正面、负面、状态指示要么用 `Badge` variant,要么用语义 token
44
- (`text-destructive` 等),要么定义自定义 CSS 变量 —— **不要**裸用
45
- Tailwind 调色板。
46
-
47
- **Incorrect:**
48
-
49
- ```tsx
50
- <span className="text-emerald-600">+20.1%</span>
51
- <span className="text-green-500">Active</span>
52
- <span className="text-red-600">-3.2%</span>
53
- ```
54
-
55
- **Correct:**
56
-
57
- ```tsx
58
- <Badge variant="secondary">+20.1%</Badge>
59
- <Badge>Active</Badge>
60
- <span className="text-destructive">-3.2%</span>
61
- ```
62
-
63
- 需要 success / positive 色但没有对应语义 token? 用 `Badge` variant,
64
- 或者按 [customization.md —— 新增自定义色](../customization.md#新增自定义色)
65
- 加一个 `--success` 变量。
66
-
67
- ---
68
-
69
- ## 内置 variant 优先
70
-
71
- **Incorrect:**
72
-
73
- ```tsx
74
- <Button className="border border-input bg-transparent hover:bg-accent">
75
- Click me
76
- </Button>
77
- ```
78
-
79
- **Correct:**
80
-
81
- ```tsx
82
- <Button variant="outline">Click me</Button>
83
- ```
84
-
85
- `Button` variant: `default` / `secondary` / `outline` / `ghost` /
86
- `destructive` / `link`。`Badge` variant: `default` / `secondary` /
87
- `destructive` / `outline`。其它原语自行查 source。
88
-
89
- ---
90
-
91
- ## `className` 只管布局
92
-
93
- `className` 用来加布局类(`max-w-md`、`mx-auto`、`mt-4`),**不要**
94
- 覆盖组件颜色或排版。改色用语义 token、内置 variant、或 CSS 变量。
95
-
96
- **Incorrect:**
97
-
98
- ```tsx
99
- <Card className="bg-blue-100 text-blue-900 font-bold">
100
- <CardContent>Dashboard</CardContent>
101
- </Card>
102
- ```
103
-
104
- **Correct:**
105
-
106
- ```tsx
107
- <Card className="max-w-md mx-auto">
108
- <CardContent>Dashboard</CardContent>
109
- </Card>
110
- ```
111
-
112
- 定制顺序:
113
- 1. **内置 variant** —— `variant="outline"`、`variant="destructive"`…
114
- 2. **语义 token** —— `bg-primary`、`text-muted-foreground`。
115
- 3. **CSS 变量** —— 加到全局 CSS(见
116
- [customization.md](../customization.md))。
117
-
118
- ---
119
-
120
- ## 不要 `space-x-*` / `space-y-*`
121
-
122
- 改用 `gap-*`。`space-y-4` → `flex flex-col gap-4`。`space-x-2` → `flex gap-2`。
123
-
124
- ```tsx
125
- <div className="flex flex-col gap-4">
126
- <Input />
127
- <Input />
128
- <Button>Submit</Button>
129
- </div>
130
- ```
131
-
132
- ---
133
-
134
- ## 宽高相等用 `size-*`
135
-
136
- `size-10` 不是 `w-10 h-10`。适用于图标、头像、骨架屏、按钮等。
137
-
138
- ---
139
-
140
- ## `truncate` 简写
141
-
142
- `truncate` 不是 `overflow-hidden text-ellipsis whitespace-nowrap`。
143
-
144
- ---
145
-
146
- ## 不要手写 `dark:` 颜色覆盖
147
-
148
- 语义 token 通过 CSS 变量自动处理亮 / 暗 —— `bg-background text-foreground`
149
- 不是 `bg-white dark:bg-gray-950`。
150
-
151
- ---
152
-
153
- ## `cn()` 用于条件 class
154
-
155
- 从 `@openconsole/shadcn` 导入 `cn()` 来拼条件或合并的 class,**不要**在
156
- `className` 字符串里手写模板字面量的三元。
157
-
158
- **Incorrect:**
159
-
160
- ```tsx
161
- <div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
162
- ```
163
-
164
- **Correct:**
165
-
166
- ```tsx
167
- import { cn } from "@openconsole/shadcn";
168
-
169
- <div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
170
- ```
171
-
172
- > 注意导入路径是 `@openconsole/shadcn`,不是 `@/lib/utils`。
173
-
174
- ---
175
-
176
- ## overlay 不要手写 z-index
177
-
178
- `Dialog`、`Sheet`、`Drawer`、`AlertDialog`、`DropdownMenu`、`Popover`、
179
- `Tooltip`、`HoverCard` **自己管堆叠**。绝对不要加 `z-50` 或 `z-[999]` ——
180
- 这样会破坏它们跟 `Toaster` 之间的堆叠顺序。