@openconsole/shadcn 0.0.0 → 0.2.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.
- package/README.md +380 -0
- package/accordion.tsx +66 -66
- package/alert-dialog.tsx +196 -196
- package/alert.tsx +66 -66
- package/aspect-ratio.tsx +11 -11
- package/avatar.tsx +53 -53
- package/badge.tsx +46 -46
- package/breadcrumb.tsx +109 -109
- package/button-group.tsx +83 -83
- package/button.tsx +60 -60
- package/calendar.tsx +219 -219
- package/card.tsx +92 -92
- package/carousel.tsx +241 -241
- package/chart.tsx +374 -374
- package/checkbox.tsx +32 -32
- package/collapsible.tsx +33 -33
- package/command.tsx +184 -184
- package/context-menu.tsx +252 -252
- package/dialog.tsx +143 -143
- package/direction.tsx +22 -22
- package/drawer.tsx +135 -135
- package/dropdown-menu.tsx +257 -257
- package/empty.tsx +104 -104
- package/field.tsx +248 -248
- package/form.tsx +167 -167
- package/hooks/index.ts +1 -1
- package/hooks/use-mobile.ts +19 -19
- package/hover-card.tsx +44 -44
- package/icon.tsx +21 -21
- package/index.ts +59 -59
- package/input-group.tsx +170 -170
- package/input-otp.tsx +77 -77
- package/input.tsx +21 -21
- package/item.tsx +193 -193
- package/kbd.tsx +28 -28
- package/label.tsx +24 -24
- package/lib/index.ts +1 -1
- package/lib/utils.ts +6 -6
- package/menubar.tsx +276 -276
- package/native-select.tsx +62 -62
- package/navigation-menu.tsx +168 -168
- package/package.json +10 -2
- package/pagination.tsx +127 -127
- package/popover.tsx +89 -89
- package/progress.tsx +31 -31
- package/radio-group.tsx +45 -45
- package/resizable.tsx +53 -53
- package/scroll-area.tsx +58 -58
- package/select.tsx +187 -187
- package/separator.tsx +28 -28
- package/sheet.tsx +139 -139
- package/sidebar.tsx +724 -724
- package/skeleton.tsx +13 -13
- package/slider.tsx +63 -63
- package/sonner.tsx +40 -40
- package/spinner.tsx +16 -16
- package/styles.css +122 -0
- package/switch.tsx +35 -35
- package/table.tsx +116 -116
- package/tabs.tsx +66 -66
- package/textarea.tsx +18 -18
- package/toggle-group.tsx +83 -83
- package/toggle.tsx +47 -47
- package/tooltip.tsx +61 -61
- package/tsconfig.json +12 -12
- package/tsconfig.tsbuildinfo +1 -1
- package/skill/SKILL.md +0 -599
- package/skill/customization.md +0 -263
- package/skill/rules/base-vs-radix.md +0 -167
- package/skill/rules/composition.md +0 -240
- package/skill/rules/forms.md +0 -271
- package/skill/rules/icons.md +0 -136
- package/skill/rules/styling.md +0 -180
package/skill/rules/forms.md
DELETED
|
@@ -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>` 内部 —— 否则抛错。
|
package/skill/rules/icons.md
DELETED
|
@@ -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。
|
package/skill/rules/styling.md
DELETED
|
@@ -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` 之间的堆叠顺序。
|