@teamix-evo/ui 0.7.0 → 0.7.2

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 (121) hide show
  1. package/manifest.json +16 -7
  2. package/package.json +4 -4
  3. package/src/_design-system/theme-tokens/stories.tsx +2 -2
  4. package/src/components/accordion/index.tsx +1 -1
  5. package/src/components/affix/meta.md +26 -0
  6. package/src/components/alert/index.tsx +2 -2
  7. package/src/components/alert-dialog/index.tsx +3 -3
  8. package/src/components/alert-dialog/meta.md +52 -0
  9. package/src/components/alert-dialog/stories.tsx +45 -48
  10. package/src/components/avatar/index.tsx +1 -1
  11. package/src/components/badge/index.tsx +2 -2
  12. package/src/components/badge/meta.md +48 -0
  13. package/src/components/button/index.tsx +2 -2
  14. package/src/components/button/meta.md +15 -0
  15. package/src/components/button/stories.tsx +1 -1
  16. package/src/components/calendar/index.tsx +2 -2
  17. package/src/components/card/index.tsx +1 -1
  18. package/src/components/carousel/index.tsx +2 -2
  19. package/src/components/carousel/meta.md +34 -2
  20. package/src/components/carousel/stories.tsx +2 -2
  21. package/src/components/cascader-select/index.tsx +2 -1
  22. package/src/components/cascader-select/meta.md +46 -0
  23. package/src/components/checkbox/meta.md +47 -0
  24. package/src/components/color-picker/index.tsx +3 -3
  25. package/src/components/color-picker/meta.md +80 -0
  26. package/src/components/combobox/index.tsx +2 -2
  27. package/src/components/combobox/meta.md +130 -0
  28. package/src/components/data-table/index.tsx +3 -3
  29. package/src/components/data-table/meta.md +419 -0
  30. package/src/components/data-table/stories.tsx +4 -4
  31. package/src/components/date-picker/meta.md +91 -0
  32. package/src/components/descriptions/index.tsx +1 -1
  33. package/src/components/descriptions/meta.md +245 -0
  34. package/src/components/dialog/index.tsx +4 -4
  35. package/src/components/dialog/meta.md +47 -1
  36. package/src/components/dialog/stories.tsx +38 -41
  37. package/src/components/dropdown-menu/index.tsx +5 -5
  38. package/src/components/empty/index.tsx +2 -2
  39. package/src/components/field/index.tsx +4 -4
  40. package/src/components/filter-bar/index.tsx +6 -6
  41. package/src/components/filter-bar/meta.md +323 -0
  42. package/src/components/float-button/index.tsx +2 -2
  43. package/src/components/form/index.tsx +1 -1
  44. package/src/components/form/meta.md +119 -0
  45. package/src/components/hover-card/index.tsx +1 -1
  46. package/src/components/hover-card/meta.md +21 -0
  47. package/src/components/input/meta.md +16 -0
  48. package/src/components/input-group/index.tsx +1 -1
  49. package/src/components/input-group/meta.md +118 -0
  50. package/src/components/input-group/stories.tsx +6 -6
  51. package/src/components/input-ip/index.tsx +2 -2
  52. package/src/components/input-ip/meta.md +30 -0
  53. package/src/components/input-ip/stories.tsx +2 -2
  54. package/src/components/input-number/index.tsx +3 -2
  55. package/src/components/input-number/meta.md +67 -0
  56. package/src/components/input-number/stories.tsx +2 -2
  57. package/src/components/item/index.tsx +4 -4
  58. package/src/components/label/meta.md +8 -0
  59. package/src/components/mentions/meta.md +15 -0
  60. package/src/components/menubar/index.tsx +4 -4
  61. package/src/components/navigation-menu/index.tsx +4 -4
  62. package/src/components/page-header/index.tsx +2 -2
  63. package/src/components/page-header/meta.md +145 -0
  64. package/src/components/page-shell/index.tsx +3 -3
  65. package/src/components/pagination/index.tsx +1 -1
  66. package/src/components/pagination/meta.md +203 -0
  67. package/src/components/popconfirm/meta.md +45 -0
  68. package/src/components/popover/index.tsx +2 -2
  69. package/src/components/popover/meta.md +47 -0
  70. package/src/components/progress/index.tsx +1 -1
  71. package/src/components/progress/meta.md +36 -0
  72. package/src/components/progress/stories.tsx +1 -1
  73. package/src/components/radio-group/meta.md +69 -0
  74. package/src/components/rate/index.tsx +1 -1
  75. package/src/components/rate/meta.md +50 -0
  76. package/src/components/resizable/index.tsx +1 -1
  77. package/src/components/select/index.tsx +2 -2
  78. package/src/components/select/meta.md +20 -0
  79. package/src/components/separator/index.tsx +1 -1
  80. package/src/components/sheet/index.tsx +13 -14
  81. package/src/components/sheet/meta.md +124 -0
  82. package/src/components/sheet/stories.tsx +110 -119
  83. package/src/components/sidebar/index.tsx +5 -5
  84. package/src/components/sidebar/meta.md +383 -0
  85. package/src/components/skeleton/meta.md +13 -0
  86. package/src/components/slider/index.tsx +2 -2
  87. package/src/components/sonner/meta.md +86 -0
  88. package/src/components/spinner/meta.md +46 -0
  89. package/src/components/spinner/stories.tsx +2 -2
  90. package/src/components/steps/meta.md +20 -0
  91. package/src/components/steps/stories.tsx +1 -1
  92. package/src/components/switch/index.tsx +2 -2
  93. package/src/components/switch/meta.md +33 -0
  94. package/src/components/table/index.tsx +4 -4
  95. package/src/components/table/meta.md +11 -0
  96. package/src/components/tabs/index.tsx +7 -7
  97. package/src/components/tabs/meta.md +52 -0
  98. package/src/components/tag/index.tsx +8 -8
  99. package/src/components/tag/meta.md +194 -0
  100. package/src/components/textarea/index.tsx +1 -1
  101. package/src/components/textarea/meta.md +27 -0
  102. package/src/components/textarea/stories.tsx +1 -1
  103. package/src/components/time-picker/index.tsx +3 -3
  104. package/src/components/time-picker/meta.md +76 -0
  105. package/src/components/timeline/index.tsx +1 -0
  106. package/src/components/toggle/index.tsx +1 -1
  107. package/src/components/toggle-group/index.tsx +1 -1
  108. package/src/components/tooltip/index.tsx +1 -1
  109. package/src/components/tooltip/meta.md +23 -0
  110. package/src/components/transfer/index.tsx +2 -2
  111. package/src/components/transfer/meta.md +97 -0
  112. package/src/components/tree/index.tsx +245 -15
  113. package/src/components/tree/meta.md +151 -0
  114. package/src/components/tree-select/index.tsx +16 -2
  115. package/src/components/tree-select/meta.md +150 -0
  116. package/src/components/typography/index.tsx +3 -3
  117. package/src/components/upload/index.tsx +3 -3
  118. package/src/components/upload/meta.md +82 -0
  119. package/src/components/tree/utils.ts +0 -269
  120. package/src/examples/built-in-assets/stories.tsx +0 -572
  121. package/src/examples/evaluators/stories.tsx +0 -502
@@ -55,3 +55,326 @@
55
55
  | `control` | `Control<TForm>` | – | – | 显式指定 RHF control(默认从 FilterBar 上下文取)。 |
56
56
  | `children` | `React.ReactElement` | – | – | 静态 JSX 子元素(首选,AI 友好)。内部按子元素身份自动桥接 RHF field 到 `value`/`checked` + 对应 onChange。 适配 Input / Select / Checkbox / Switch / RadioGroup / Slider / Textarea; 不在适配表中的自定义控件请改用 `render` prop。 |
57
57
  | `render` | `(props: { field: ControllerRenderProps<TForm, TName>; fieldState: ControllerFieldState; formState: UseFormStateReturn<TForm>; }) => React.ReactElement` | – | – | 渲染输入控件(逆出舱,适配表不覆盖时使用)。 |
58
+
59
+ ## 示例
60
+
61
+ ### Inline
62
+
63
+ ```tsx
64
+ <div>
65
+ <FilterBar
66
+ form={form}
67
+ autoFilter
68
+ onFilter={(values) => setFiltered(values)}
69
+ >
70
+ <FilterBarHeader>
71
+ <FilterBarField name="status" label="状态">
72
+ <Select>
73
+ <SelectTrigger className="w-full">
74
+ <SelectValue placeholder="全部" />
75
+ </SelectTrigger>
76
+ <SelectContent>
77
+ {STATUS_OPTIONS.map((o) => (
78
+ <SelectItem key={o.value} value={o.value}>
79
+ {o.label}
80
+ </SelectItem>
81
+ ))}
82
+ </SelectContent>
83
+ </Select>
84
+ </FilterBarField>
85
+ <FilterBarField name="region" label="地域">
86
+ <Select>
87
+ <SelectTrigger className="w-full">
88
+ <SelectValue placeholder="全部" />
89
+ </SelectTrigger>
90
+ <SelectContent>
91
+ {REGION_OPTIONS.map((o) => (
92
+ <SelectItem key={o.value} value={o.value}>
93
+ {o.label}
94
+ </SelectItem>
95
+ ))}
96
+ </SelectContent>
97
+ </Select>
98
+ </FilterBarField>
99
+ <FilterBarField name="keyword" label="关键词">
100
+ <Input placeholder="请输入实例名称" />
101
+ </FilterBarField>
102
+ </FilterBarHeader>
103
+ </FilterBar>
104
+ <FilteredPreview values={filtered} />
105
+ </div>
106
+ ```
107
+
108
+ ### WithAdvancedPanel
109
+
110
+ ```tsx
111
+ <div>
112
+ <FilterBar
113
+ form={form}
114
+ columns={3}
115
+ onFilter={(values) => setFiltered(values)}
116
+ >
117
+ <FilterBarHeader>
118
+ <FilterBarField name="status" label="状态">
119
+ <Select>
120
+ <SelectTrigger className="w-full">
121
+ <SelectValue placeholder="全部" />
122
+ </SelectTrigger>
123
+ <SelectContent>
124
+ {STATUS_OPTIONS.map((o) => (
125
+ <SelectItem key={o.value} value={o.value}>
126
+ {o.label}
127
+ </SelectItem>
128
+ ))}
129
+ </SelectContent>
130
+ </Select>
131
+ </FilterBarField>
132
+ <FilterBarField name="region" label="地域">
133
+ <Select>
134
+ <SelectTrigger className="w-full">
135
+ <SelectValue placeholder="全部" />
136
+ </SelectTrigger>
137
+ <SelectContent>
138
+ {REGION_OPTIONS.map((o) => (
139
+ <SelectItem key={o.value} value={o.value}>
140
+ {o.label}
141
+ </SelectItem>
142
+ ))}
143
+ </SelectContent>
144
+ </Select>
145
+ </FilterBarField>
146
+ <FilterBarField name="keyword" label="关键词">
147
+ <Input placeholder="请输入" />
148
+ </FilterBarField>
149
+ <FilterBarTrigger />
150
+ </FilterBarHeader>
151
+ <FilterBarContent>
152
+ <FilterBarField name="type" label="类型">
153
+ <Select>
154
+ <SelectTrigger className="w-full">
155
+ <SelectValue placeholder="全部" />
156
+ </SelectTrigger>
157
+ <SelectContent>
158
+ {TYPE_OPTIONS.map((o) => (
159
+ <SelectItem key={o.value} value={o.value}>
160
+ {o.label}
161
+ </SelectItem>
162
+ ))}
163
+ </SelectContent>
164
+ </Select>
165
+ </FilterBarField>
166
+ <FilterBarField name="owner" label="负责人">
167
+ <Input placeholder="请输入负责人" />
168
+ </FilterBarField>
169
+ <FilterBarField name="tag" label="标签">
170
+ <Input placeholder="请输入标签" />
171
+ </FilterBarField>
172
+ <FilterBarActions />
173
+ </FilterBarContent>
174
+ </FilterBar>
175
+ <FilteredPreview values={filtered} />
176
+ </div>
177
+ ```
178
+
179
+ ### PurePanel
180
+
181
+ ```tsx
182
+ <div>
183
+ <FilterBar
184
+ form={form}
185
+ defaultOpen
186
+ columns={{ base: 2, md: 4 }}
187
+ onFilter={(values) => setFiltered(values)}
188
+ >
189
+ <FilterBarContent>
190
+ <FilterBarField name="status" label="状态">
191
+ <Select>
192
+ <SelectTrigger className="w-full">
193
+ <SelectValue placeholder="全部" />
194
+ </SelectTrigger>
195
+ <SelectContent>
196
+ {STATUS_OPTIONS.map((o) => (
197
+ <SelectItem key={o.value} value={o.value}>
198
+ {o.label}
199
+ </SelectItem>
200
+ ))}
201
+ </SelectContent>
202
+ </Select>
203
+ </FilterBarField>
204
+ <FilterBarField name="region" label="地域">
205
+ <Select>
206
+ <SelectTrigger className="w-full">
207
+ <SelectValue placeholder="全部" />
208
+ </SelectTrigger>
209
+ <SelectContent>
210
+ {REGION_OPTIONS.map((o) => (
211
+ <SelectItem key={o.value} value={o.value}>
212
+ {o.label}
213
+ </SelectItem>
214
+ ))}
215
+ </SelectContent>
216
+ </Select>
217
+ </FilterBarField>
218
+ <FilterBarField name="type" label="类型">
219
+ <Select>
220
+ <SelectTrigger className="w-full">
221
+ <SelectValue placeholder="全部" />
222
+ </SelectTrigger>
223
+ <SelectContent>
224
+ {TYPE_OPTIONS.map((o) => (
225
+ <SelectItem key={o.value} value={o.value}>
226
+ {o.label}
227
+ </SelectItem>
228
+ ))}
229
+ </SelectContent>
230
+ </Select>
231
+ </FilterBarField>
232
+ <FilterBarField name="owner" label="负责人">
233
+ <Input placeholder="请输入" />
234
+ </FilterBarField>
235
+ <FilterBarActions />
236
+ </FilterBarContent>
237
+ </FilterBar>
238
+ <FilteredPreview values={filtered} />
239
+ </div>
240
+ ```
241
+
242
+ ### WithKeySearch
243
+
244
+ ```tsx
245
+ <div>
246
+ <FilterBar
247
+ form={form}
248
+ columns={3}
249
+ onFilter={(values) => setFiltered(values)}
250
+ addonBefore={
251
+ <Button>
252
+ <Plus />
253
+ 创建任务
254
+ </Button>
255
+ }
256
+ addonAfter={
257
+ <Button variant="outline" aria-label="设置">
258
+ <Settings2 />
259
+ </Button>
260
+ }
261
+ >
262
+ <FilterBarHeader>
263
+ <FilterBarSearch className="w-80">
264
+ <FilterBarSearchKey
265
+ options={[
266
+ { name: 'instanceId', label: '实例 ID' },
267
+ { name: 'instanceName', label: '实例名称' },
268
+ { name: 'ip', label: 'IP 地址' },
269
+ ]}
270
+ />
271
+ <FilterBarSearchValue />
272
+ <FilterBarSearchAction />
273
+ </FilterBarSearch>
274
+ <FilterBarTrigger />
275
+ </FilterBarHeader>
276
+ <FilterBarContent>
277
+ <FilterBarField name="status" label="状态">
278
+ <Select>
279
+ <SelectTrigger className="w-full">
280
+ <SelectValue placeholder="全部" />
281
+ </SelectTrigger>
282
+ <SelectContent>
283
+ {STATUS_OPTIONS.map((o) => (
284
+ <SelectItem key={o.value} value={o.value}>
285
+ {o.label}
286
+ </SelectItem>
287
+ ))}
288
+ </SelectContent>
289
+ </Select>
290
+ </FilterBarField>
291
+ <FilterBarField name="region" label="地域">
292
+ <Select>
293
+ <SelectTrigger className="w-full">
294
+ <SelectValue placeholder="全部" />
295
+ </SelectTrigger>
296
+ <SelectContent>
297
+ {REGION_OPTIONS.map((o) => (
298
+ <SelectItem key={o.value} value={o.value}>
299
+ {o.label}
300
+ </SelectItem>
301
+ ))}
302
+ </SelectContent>
303
+ </Select>
304
+ </FilterBarField>
305
+ <FilterBarField name="type" label="类型">
306
+ <Select>
307
+ <SelectTrigger className="w-full">
308
+ <SelectValue placeholder="全部" />
309
+ </SelectTrigger>
310
+ <SelectContent>
311
+ {TYPE_OPTIONS.map((o) => (
312
+ <SelectItem key={o.value} value={o.value}>
313
+ {o.label}
314
+ </SelectItem>
315
+ ))}
316
+ </SelectContent>
317
+ </Select>
318
+ </FilterBarField>
319
+ <FilterBarActions />
320
+ </FilterBarContent>
321
+ </FilterBar>
322
+ <FilteredPreview values={filtered} />
323
+ </div>
324
+ ```
325
+
326
+ ### CustomActions
327
+
328
+ ```tsx
329
+ <div>
330
+ <FilterBar
331
+ form={form}
332
+ onFilter={(values) =>
333
+ setSavedFilter(`筛选 → ${JSON.stringify(values)}`)
334
+ }
335
+ >
336
+ <FilterBarHeader>
337
+ <FilterBarField name="status" label="状态">
338
+ <Select>
339
+ <SelectTrigger className="w-full">
340
+ <SelectValue placeholder="全部" />
341
+ </SelectTrigger>
342
+ <SelectContent>
343
+ {STATUS_OPTIONS.map((o) => (
344
+ <SelectItem key={o.value} value={o.value}>
345
+ {o.label}
346
+ </SelectItem>
347
+ ))}
348
+ </SelectContent>
349
+ </Select>
350
+ </FilterBarField>
351
+ <FilterBarField name="keyword" label="关键词">
352
+ <Input placeholder="请输入" />
353
+ </FilterBarField>
354
+ <FilterBarActions>
355
+ <Button
356
+ type="button"
357
+ variant="outline"
358
+ onClick={() => form.reset()}
359
+ >
360
+ 清空
361
+ </Button>
362
+ <Button
363
+ type="button"
364
+ variant="outline"
365
+ onClick={() =>
366
+ setSavedFilter(`已保存 → ${JSON.stringify(form.getValues())}`)
367
+ }
368
+ >
369
+ 保存条件
370
+ </Button>
371
+ <Button type="submit">立即查询</Button>
372
+ </FilterBarActions>
373
+ </FilterBarHeader>
374
+ </FilterBar>
375
+ <div className="mt-3 rounded-md bg-muted/40 px-3 py-2 text-xs text-muted-foreground">
376
+ <span className="text-foreground">最近动作:</span>
377
+ {savedFilter ?? '— 无 —'}
378
+ </div>
379
+ </div>
380
+ ```
@@ -19,7 +19,7 @@ import { ArrowUpIcon } from 'lucide-react';
19
19
  import { cn } from '@/lib/utils';
20
20
 
21
21
  const floatButtonVariants = cva(
22
- 'relative inline-flex items-center justify-center transition-all shadow-lg hover:shadow-xl active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer disabled:cursor-not-allowed disabled:opacity-50',
22
+ 'relative inline-flex cursor-pointer items-center justify-center shadow-lg transition-all hover:shadow-xl focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none active:scale-95 disabled:cursor-not-allowed disabled:opacity-50',
23
23
  {
24
24
  variants: {
25
25
  variant: {
@@ -76,7 +76,7 @@ function FloatButton({
76
76
  {badge != null && (
77
77
  <span
78
78
  data-slot="float-button-badge"
79
- className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-xxs font-medium leading-none text-destructive-foreground"
79
+ className="absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-xxs leading-none font-medium text-destructive-foreground"
80
80
  >
81
81
  {badge}
82
82
  </span>
@@ -198,7 +198,7 @@ function FormMessage({
198
198
  id={formMessageId}
199
199
  role="alert"
200
200
  data-slot="field-error"
201
- className={cn('text-xs font-normal text-destructive -mt-1', className)}
201
+ className={cn('-mt-1 text-xs font-normal text-destructive', className)}
202
202
  {...props}
203
203
  >
204
204
  {body}
@@ -14,3 +14,122 @@ react-hook-form 极薄封装 — 将 RHF FormProvider / Controller 与 Field 系
14
14
  - 表单场景需要 RHF 表单级状态管理
15
15
  - 需要自动 aria-invalid / aria-describedby 注入
16
16
  - 需要字段级 error 联动(自动标红 + 错误信息展示)
17
+
18
+ ## 示例
19
+
20
+ ### Basic
21
+
22
+ 基础用法:用户名 + 邮箱字段,含校验规则与描述文案。
23
+
24
+ ```tsx
25
+ <Form {...form}>
26
+ <form
27
+ onSubmit={form.handleSubmit((data) => console.log(data))}
28
+ className="flex w-80 flex-col gap-4"
29
+ >
30
+ <FormField
31
+ name="username"
32
+ rules={{ required: '用户名不能为空' }}
33
+ render={({ field }) => (
34
+ <FormItem>
35
+ <FormLabel>用户名</FormLabel>
36
+ <FormControl>
37
+ <Input placeholder="请输入用户名" {...field} />
38
+ </FormControl>
39
+ <FormDescription>这是你的公开显示名称。</FormDescription>
40
+ <FormMessage />
41
+ </FormItem>
42
+ )}
43
+ />
44
+ <FormField
45
+ name="email"
46
+ rules={{
47
+ required: '邮箱不能为空',
48
+ pattern: { value: /^\S+@\S+$/i, message: '邮箱格式不正确' },
49
+ }}
50
+ render={({ field }) => (
51
+ <FormItem>
52
+ <FormLabel>邮箱</FormLabel>
53
+ <FormControl>
54
+ <Input
55
+ type="email"
56
+ placeholder="name@example.com"
57
+ {...field}
58
+ />
59
+ </FormControl>
60
+ <FormMessage />
61
+ </FormItem>
62
+ )}
63
+ />
64
+ <Button type="submit">提交</Button>
65
+ </form>
66
+ </Form>
67
+ ```
68
+
69
+ ### ErrorState
70
+
71
+ 错误状态:必填 + minLength 校验,提交时联动 `data-invalid` 与 FormMessage。
72
+
73
+ ```tsx
74
+ <Form {...form}>
75
+ <form
76
+ onSubmit={form.handleSubmit((data) => console.log(data))}
77
+ className="flex w-80 flex-col gap-4"
78
+ >
79
+ <FormField
80
+ name="name"
81
+ rules={{
82
+ required: '此字段必填',
83
+ minLength: { value: 2, message: '最少 2 个字符' },
84
+ }}
85
+ render={({ field }) => (
86
+ <FormItem>
87
+ <FormLabel>姓名</FormLabel>
88
+ <FormControl>
89
+ <Input placeholder="请输入姓名" {...field} />
90
+ </FormControl>
91
+ <FormMessage />
92
+ </FormItem>
93
+ )}
94
+ />
95
+ <Button type="submit">验证</Button>
96
+ </form>
97
+ </Form>
98
+ ```
99
+
100
+ ### Horizontal
101
+
102
+ 水平布局:FormItem `orientation="responsive"`,标签与控件并排。
103
+
104
+ ```tsx
105
+ <Form {...form}>
106
+ <form
107
+ onSubmit={form.handleSubmit((data) => console.log(data))}
108
+ className="flex w-96 flex-col gap-4"
109
+ >
110
+ <FormField
111
+ name="firstName"
112
+ render={({ field }) => (
113
+ <FormItem orientation="responsive">
114
+ <FormLabel>姓</FormLabel>
115
+ <FormControl>
116
+ <Input {...field} />
117
+ </FormControl>
118
+ </FormItem>
119
+ )}
120
+ />
121
+ <FormField
122
+ name="lastName"
123
+ render={({ field }) => (
124
+ <FormItem orientation="responsive">
125
+ <FormLabel>名</FormLabel>
126
+ <FormControl>
127
+ <Input {...field} />
128
+ </FormControl>
129
+ </FormItem>
130
+ )}
131
+ />
132
+ <Button type="submit">提交</Button>
133
+ </form>
134
+ </Form>
135
+ ```
@@ -70,7 +70,7 @@ function HoverCardContent({
70
70
  align={align}
71
71
  sideOffset={sideOffset}
72
72
  className={cn(
73
- 'group/hover-card-content relative z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md bg-popover p-2.5 text-xs text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
73
+ 'group/hover-card-content relative z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md bg-popover p-2.5 text-xs text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
74
74
  className,
75
75
  )}
76
76
  {...props}
@@ -27,6 +27,27 @@
27
27
 
28
28
  ## 示例
29
29
 
30
+ ### AllPositions
31
+
32
+ 12 方向矩阵:side × align 组合。
33
+
34
+ ```tsx
35
+ <div className="grid grid-cols-3 gap-4">
36
+ {sides.flatMap((side) =>
37
+ aligns.map((align) => (
38
+ <HoverCard key={`${side}-${align}`}>
39
+ <HoverCardTrigger asChild>
40
+ <Button>{`${side}-${align}`}</Button>
41
+ </HoverCardTrigger>
42
+ <HoverCardContent side={side} align={align}>
43
+ <p className="text-muted-foreground">{`${side} / ${align}`}</p>
44
+ </HoverCardContent>
45
+ </HoverCard>
46
+ )),
47
+ )}
48
+ </div>
49
+ ```
50
+
30
51
  ### CustomDelay
31
52
 
32
53
  自定义打开 / 关闭延时。
@@ -44,6 +44,14 @@
44
44
  </div>
45
45
  ```
46
46
 
47
+ ### Invalid
48
+
49
+ 通过 `aria-invalid` 属性标记错误状态,通常用于表单校验失败时。
50
+
51
+ ```tsx
52
+ <Input placeholder="请输入邮箱" aria-invalid="true" />
53
+ ```
54
+
47
55
  ### Widths
48
56
 
49
57
  Input 默认宽度为 100%,可通过 `className` 控制宽度。
@@ -55,3 +63,11 @@ Input 默认宽度为 100%,可通过 `className` 控制宽度。
55
63
  <Input className="w-full" placeholder="w-full (默认)" />
56
64
  </div>
57
65
  ```
66
+
67
+ ### ReadOnly
68
+
69
+ `readOnly` 属性让输入框只读,用户可复制但无法编辑。
70
+
71
+ ```tsx
72
+ <Input readOnly value="只读内容,可复制" />
73
+ ```
@@ -278,7 +278,7 @@ function InputGroupShowCount({
278
278
  data-slot="input-group-show-count"
279
279
  data-overflow={overflow || undefined}
280
280
  className={cn(
281
- 'flex items-center text-xs tabular-nums text-muted-foreground/60 select-none',
281
+ 'flex items-center text-xs text-muted-foreground/60 tabular-nums select-none',
282
282
  'data-[overflow]:text-destructive',
283
283
  className,
284
284
  )}
@@ -59,6 +59,87 @@
59
59
  </InputGroup>
60
60
  ```
61
61
 
62
+ ### Search
63
+
64
+ 搜索场景:左侧 SearchIcon + 右侧 InputGroupClear 受控清除按钮。 仅当 value 非空时显示清除按钮。
65
+
66
+ ```tsx
67
+ <InputGroup className="w-64">
68
+ <InputGroupAddon>
69
+ <InputGroupText>
70
+ <SearchIcon />
71
+ </InputGroupText>
72
+ </InputGroupAddon>
73
+ <InputGroupInput
74
+ placeholder="搜索..."
75
+ value={value}
76
+ onChange={(e) => setValue(e.target.value)}
77
+ />
78
+ <InputGroupAddon align="inline-end">
79
+ <InputGroupClear
80
+ visible={value.length > 0}
81
+ onClick={() => setValue('')}
82
+ />
83
+ </InputGroupAddon>
84
+ </InputGroup>
85
+ ```
86
+
87
+ ### Clear
88
+
89
+ 一键清除:使用 `InputGroupClear` helper 受控显示清除按钮, 仅当 `visible` 为 true 时渲染,点击由父级清空 `value`。
90
+
91
+ ```tsx
92
+ <InputGroup className="w-64">
93
+ <InputGroupInput
94
+ placeholder="输入后展示清除按钮"
95
+ value={value}
96
+ onChange={(e) => setValue(e.target.value)}
97
+ />
98
+ <InputGroupAddon align="inline-end">
99
+ <InputGroupClear
100
+ visible={value.length > 0}
101
+ onClick={() => setValue('')}
102
+ />
103
+ </InputGroupAddon>
104
+ </InputGroup>
105
+ ```
106
+
107
+ ### Password
108
+
109
+ 密码输入:使用 `InputGroupPasswordToggle` 受控版,由父级同时控制 `type` 与 `visible`。
110
+
111
+ ```tsx
112
+ <InputGroup className="w-64">
113
+ <InputGroupInput
114
+ type={visible ? 'text' : 'password'}
115
+ placeholder="请输入密码"
116
+ />
117
+ <InputGroupAddon align="inline-end">
118
+ <InputGroupPasswordToggle
119
+ visible={visible}
120
+ onVisibleChange={setVisible}
121
+ />
122
+ </InputGroupAddon>
123
+ </InputGroup>
124
+ ```
125
+
126
+ ### Currency
127
+
128
+ 金额输入:后置货币符号,展示带格式化的数值输入。
129
+
130
+ ```tsx
131
+ <InputGroup className="w-64">
132
+ <InputGroupInput
133
+ placeholder="请输入金额"
134
+ value={format(value)}
135
+ onChange={(e) => setValue(e.target.value.replace(/,/g, ''))}
136
+ />
137
+ <InputGroupAddon align="inline-end">
138
+ <InputGroupText>¥</InputGroupText>
139
+ </InputGroupAddon>
140
+ </InputGroup>
141
+ ```
142
+
62
143
  ### Email
63
144
 
64
145
  邮箱输入:左侧邮件图标 + 右侧域名后缀文本。
@@ -92,6 +173,43 @@
92
173
  </InputGroup>
93
174
  ```
94
175
 
176
+ ### WithCount
177
+
178
+ 单行字符计数:使用 `InputGroupShowCount` 显示 `current/max`。 超过 maxLength 时计数自动转 destructive 色。
179
+
180
+ ```tsx
181
+ <InputGroup className="w-80">
182
+ <InputGroupInput
183
+ placeholder="请输入标题"
184
+ value={value}
185
+ onChange={(e) => setValue(e.target.value)}
186
+ maxLength={max}
187
+ />
188
+ <InputGroupAddon align="inline-end">
189
+ <InputGroupShowCount current={value.length} max={max} />
190
+ </InputGroupAddon>
191
+ </InputGroup>
192
+ ```
193
+
194
+ ### MultilineWithCount
195
+
196
+ 多行 + 字符计数:textarea 配合 `block-end` 对齐的计数行, 对齐 cloud-design `hasLimitHint` 的视觉位置。
197
+
198
+ ```tsx
199
+ <InputGroup className="w-80">
200
+ <InputGroupTextarea
201
+ placeholder="请输入备注..."
202
+ value={value}
203
+ onChange={(e) => setValue(e.target.value)}
204
+ rows={4}
205
+ className="field-sizing-fixed"
206
+ />
207
+ <InputGroupAddon align="block-end" className="justify-end">
208
+ <InputGroupShowCount current={value.length} max={max} />
209
+ </InputGroupAddon>
210
+ </InputGroup>
211
+ ```
212
+
95
213
  ### Disabled
96
214
 
97
215
  禁用状态的输入框组合。