@teamix-evo/ui 0.1.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/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: label
|
|
3
|
+
name: Label
|
|
4
|
+
type: component
|
|
5
|
+
category: foundation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Label
|
|
11
|
+
|
|
12
|
+
表单字段标签 — 基于 [`@radix-ui/react-label`](https://www.radix-ui.com/primitives/docs/components/label) 包装。
|
|
13
|
+
shadcn 原版只继承 Radix 的 `peer-disabled:` 联动;本版本补一条 antd Form.Item 风格的 `required`(红色 `*` 标记)+ 显式 `disabled` prop,适合 label 与目标控件**不直接相邻**的场景。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 表单字段标题(配合 `htmlFor` 或 Radix Label 默认的 click-to-focus 关联)
|
|
18
|
+
- 任何需要"不可点击的强标题"的位置(如设置面板的开关组),提供清晰的可读层级
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 整段说明文字 → 用普通 `<p>` 或 design `Typography`
|
|
23
|
+
- 表单字段说明(辅助说明) → 用 `<p className="text-sm text-muted-foreground">` 而非 Label
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`label.tsx`](./label.tsx) 的 `LabelProps` interface JSDoc。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
28
|
+
|
|
29
|
+
<!-- auto:props:begin -->
|
|
30
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
31
|
+
| --- | --- | --- | --- | --- |
|
|
32
|
+
| `disabled` | `boolean` | `false` | – | 视觉禁用态。除原生 `peer-disabled:` 联动外,显式置 `true` 也会触发禁用样式; 适合 label 与目标控件不直接相邻、无法走 `peer` 约束的场景。 |
|
|
33
|
+
| `required` | `boolean` | `false` | – | 必填标记。`true` 时在 children 后追加红色 `*`(语义来自 design destructive token), 配合 `aria-required` 在 input 上一起使用以保证可访问性。 |
|
|
34
|
+
<!-- auto:props:end -->
|
|
35
|
+
|
|
36
|
+
## 依赖
|
|
37
|
+
|
|
38
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
39
|
+
|
|
40
|
+
<!-- auto:deps:begin -->
|
|
41
|
+
### 同库依赖
|
|
42
|
+
|
|
43
|
+
> `teamix-evo ui add label` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
44
|
+
|
|
45
|
+
| Entry | 类型 | 描述 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
48
|
+
|
|
49
|
+
### npm 依赖
|
|
50
|
+
|
|
51
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add @radix-ui/react-label@^2.1.0 class-variance-authority@^0.7.0
|
|
55
|
+
```
|
|
56
|
+
<!-- auto:deps:end -->
|
|
57
|
+
|
|
58
|
+
> 除上述 props 外,Label 透传所有 `<label>` 原生属性(`htmlFor` / `id` / `aria-*` / `onClick` / ...)。
|
|
59
|
+
|
|
60
|
+
## AI 生成纪律
|
|
61
|
+
|
|
62
|
+
- **`htmlFor` 必填**:Label 必须通过 `htmlFor` 关联到目标控件的 `id`,或把控件包裹在 Label 内。**不要**只渲染孤立 Label。
|
|
63
|
+
- **`required` 配 `aria-required`**:仅视觉的 `*` 不够,目标 input 也要 `aria-required="true"`。
|
|
64
|
+
- **`disabled` 不替代 input 的 disabled**:Label 的 `disabled` 仅控制视觉,目标控件的 `disabled` 必须独立设置。
|
|
65
|
+
- **不内嵌交互元素**:Label 内不要放 button / link / 复杂控件 — Radix Label 的事件冒泡可能误触发关联控件。
|
|
66
|
+
- **文案克制**:Label 文字 ≤ 8 个汉字 / 16 字符,过长用 Tooltip 补充。
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Label } from '@/components/ui/label';
|
|
72
|
+
import { Input } from '@/components/ui/input';
|
|
73
|
+
|
|
74
|
+
// 基本
|
|
75
|
+
<Label htmlFor="email">邮箱</Label>
|
|
76
|
+
<Input id="email" type="email" />
|
|
77
|
+
|
|
78
|
+
// 必填(视觉 + 语义)
|
|
79
|
+
<Label htmlFor="username" required>用户名</Label>
|
|
80
|
+
<Input id="username" required aria-required="true" />
|
|
81
|
+
|
|
82
|
+
// 禁用
|
|
83
|
+
<Label htmlFor="readonly" disabled>只读字段</Label>
|
|
84
|
+
<Input id="readonly" disabled />
|
|
85
|
+
|
|
86
|
+
// 与控件相邻 — 用 peer 自动联动
|
|
87
|
+
<div>
|
|
88
|
+
<Input id="auto" disabled className="peer" />
|
|
89
|
+
<Label htmlFor="auto">自动联动 disabled 视觉</Label>
|
|
90
|
+
</div>
|
|
91
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Label } from './label';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Label> = {
|
|
5
|
+
title: '基础原语 · Foundation/Label',
|
|
6
|
+
component: Label,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'表单字段标签 — Radix Label 包装,shadcn 工程基线 + antd `required` 红 `*` 与显式 `disabled` 视觉。',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
disabled: {
|
|
18
|
+
description: '视觉禁用态(脱离 peer 也工作)',
|
|
19
|
+
control: 'boolean',
|
|
20
|
+
},
|
|
21
|
+
required: {
|
|
22
|
+
description: '必填标记,在文本后追加红色 `*`',
|
|
23
|
+
control: 'boolean',
|
|
24
|
+
},
|
|
25
|
+
children: {
|
|
26
|
+
description: '标签文本',
|
|
27
|
+
control: 'text',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
children: '邮箱',
|
|
32
|
+
htmlFor: 'demo-input',
|
|
33
|
+
disabled: false,
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof Label>;
|
|
40
|
+
|
|
41
|
+
/** 默认 Label,可在 Controls 调 `required` / `disabled` 等 props。 */
|
|
42
|
+
export const Playground: Story = {};
|
|
43
|
+
|
|
44
|
+
/** 必填态:文本后追加红色 `*`(语义来自 design destructive token,不要手写颜色)。 */
|
|
45
|
+
export const Required: Story = {
|
|
46
|
+
args: { children: '用户名', required: true },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** 禁用态:文字 70% 透明 + cursor-not-allowed。 */
|
|
50
|
+
export const Disabled: Story = {
|
|
51
|
+
args: { children: '只读字段', disabled: true },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** 与 input 配合(实际项目中最常用形态)。 */
|
|
55
|
+
export const WithInput: Story = {
|
|
56
|
+
parameters: { controls: { disable: true } },
|
|
57
|
+
render: () => (
|
|
58
|
+
<div className="flex flex-col gap-2">
|
|
59
|
+
<Label htmlFor="email-with-input" required>
|
|
60
|
+
邮箱
|
|
61
|
+
</Label>
|
|
62
|
+
<input
|
|
63
|
+
id="email-with-input"
|
|
64
|
+
type="email"
|
|
65
|
+
aria-required="true"
|
|
66
|
+
placeholder="you@example.com"
|
|
67
|
+
className="rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Peer 联动:input 禁用时 Label 自动响应(无需显式 `disabled` prop)。 */
|
|
74
|
+
export const PeerDisabled: Story = {
|
|
75
|
+
parameters: { controls: { disable: true } },
|
|
76
|
+
render: () => (
|
|
77
|
+
<div className="flex flex-col gap-2">
|
|
78
|
+
<input
|
|
79
|
+
id="peer-input"
|
|
80
|
+
disabled
|
|
81
|
+
placeholder="disabled input"
|
|
82
|
+
className="peer rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm"
|
|
83
|
+
/>
|
|
84
|
+
<Label htmlFor="peer-input">peer-disabled 自动响应</Label>
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
|
|
7
|
+
const labelVariants = cva(
|
|
8
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
disabled: {
|
|
12
|
+
true: 'cursor-not-allowed opacity-70',
|
|
13
|
+
false: '',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
disabled: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export interface LabelProps
|
|
23
|
+
extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>,
|
|
24
|
+
VariantProps<typeof labelVariants> {
|
|
25
|
+
/**
|
|
26
|
+
* 视觉禁用态。除原生 `peer-disabled:` 联动外,显式置 `true` 也会触发禁用样式;
|
|
27
|
+
* 适合 label 与目标控件不直接相邻、无法走 `peer` 约束的场景。
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* 必填标记。`true` 时在 children 后追加红色 `*`(语义来自 design destructive token),
|
|
33
|
+
* 配合 `aria-required` 在 input 上一起使用以保证可访问性。
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
required?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Label = React.forwardRef<
|
|
40
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
41
|
+
LabelProps
|
|
42
|
+
>(
|
|
43
|
+
(
|
|
44
|
+
{ className, disabled = false, required = false, children, ...props },
|
|
45
|
+
ref,
|
|
46
|
+
) => (
|
|
47
|
+
<LabelPrimitive.Root
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(labelVariants({ disabled }), className)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
{required ? (
|
|
54
|
+
<span
|
|
55
|
+
className="ml-0.5 text-destructive"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
>
|
|
58
|
+
*
|
|
59
|
+
</span>
|
|
60
|
+
) : null}
|
|
61
|
+
</LabelPrimitive.Root>
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
Label.displayName = 'Label';
|
|
65
|
+
|
|
66
|
+
export { Label, labelVariants };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: masonry
|
|
3
|
+
name: Masonry
|
|
4
|
+
type: component
|
|
5
|
+
category: layout
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Masonry
|
|
11
|
+
|
|
12
|
+
瀑布流 — antd 独有补足。**等价 antd `Masonry`**(v6.0 新)。基于 CSS `column-count` + `break-inside: avoid` 的纯 CSS 实现 — **0 JS、SSR 友好、自动按 children 高度填充列**。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 图片墙 / Pinterest 风格的相册
|
|
17
|
+
- 博客 / 资讯卡片流(每张卡高度不一)
|
|
18
|
+
- Dashboard 卡片墙(不同卡片高度差异大)
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 严格等高网格 → `Grid (Row/Col)` / Tailwind `grid grid-cols-N`
|
|
23
|
+
- 严格视觉对齐(按"哪列最矮就填哪列")→ 用 JS-driven masonry(react-masonry-css)
|
|
24
|
+
- 等宽等高数据流 → `List grid={{column:N}}` / `Item` + Tailwind grid
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
| `columns` | `number` | `3` | – | 列数(antd `columns` 并集) — 固定值或响应式断点配置 `{ default, sm, md, lg, xl }`。 **响应式注意**:本组件用 CSS `column-count`,断点匹配靠 Tailwind `@container` 不便, 简化为 fixed 列数。响应式建议直接在外层包 className(如 `md:columns-3 columns-2`)+ `columns={undefined}`。 |
|
|
30
|
+
| `gap` | `number` | `16` | – | 列间距(像素)。 |
|
|
31
|
+
<!-- auto:props:end -->
|
|
32
|
+
|
|
33
|
+
<!-- auto:deps:begin -->
|
|
34
|
+
### 同库依赖
|
|
35
|
+
|
|
36
|
+
> `teamix-evo ui add masonry` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
37
|
+
|
|
38
|
+
| Entry | 类型 | 描述 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
41
|
+
|
|
42
|
+
### npm 依赖
|
|
43
|
+
|
|
44
|
+
_无 — 本组件不依赖任何 npm 包。_
|
|
45
|
+
<!-- auto:deps:end -->
|
|
46
|
+
|
|
47
|
+
## AI 生成纪律
|
|
48
|
+
|
|
49
|
+
- **子项填充顺序**:CSS columns 是**自上而下、自左而右一列填完再到下一列**(不像 JS masonry 选最矮列)— 对内容流 OK,对严格视觉对齐不适用
|
|
50
|
+
- **`columns` 是固定值**:响应式建议外层包 Tailwind `md:columns-3 columns-2` + 本组件传 `columns={undefined}` 让 CSS class 接管
|
|
51
|
+
- **每个子项最好独立卡片化**(rounded-md / border / shadow),不要嵌套裸文本 — column-break 在裸文本上会断行不优雅
|
|
52
|
+
- **不要给子项设 fixed height**:Masonry 的核心价值就是按自然高度填充;如需等高直接用 Grid
|
|
53
|
+
- **图片项务必设 `width=100%`**:否则会按原图宽度撑破列宽
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Masonry } from '@/components/ui/masonry';
|
|
59
|
+
|
|
60
|
+
// 基础
|
|
61
|
+
<Masonry columns={3} gap={16}>
|
|
62
|
+
{photos.map((p) => (
|
|
63
|
+
<img key={p.id} src={p.url} alt={p.alt} className="w-full rounded-md" />
|
|
64
|
+
))}
|
|
65
|
+
</Masonry>
|
|
66
|
+
|
|
67
|
+
// 卡片墙
|
|
68
|
+
<Masonry columns={4} gap={12}>
|
|
69
|
+
{cards.map((c) => (
|
|
70
|
+
<Card key={c.id}>
|
|
71
|
+
<CardHeader><CardTitle>{c.title}</CardTitle></CardHeader>
|
|
72
|
+
<CardContent>{c.body}</CardContent>
|
|
73
|
+
</Card>
|
|
74
|
+
))}
|
|
75
|
+
</Masonry>
|
|
76
|
+
|
|
77
|
+
// 响应式(外层 className 控制列数,组件不强加)
|
|
78
|
+
<div className="columns-2 md:columns-3 lg:columns-4">
|
|
79
|
+
{items.map((it) => (
|
|
80
|
+
<div key={it.id} className="mb-4 break-inside-avoid">
|
|
81
|
+
<Card>{it.content}</Card>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Masonry } from './masonry';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Masonry> = {
|
|
5
|
+
title: '布局与容器 · Layout/Masonry',
|
|
6
|
+
component: Masonry,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'瀑布流 — CSS column-count + break-inside: avoid 的 0 JS 实现(图片墙 / 资讯卡片流 / Dashboard 卡片墙)。等价 antd `Masonry`(v6.0 新)。响应式建议外层 className 控制(`md:columns-3`)。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
columns: { control: { type: 'number', min: 1, max: 6 } },
|
|
18
|
+
gap: { control: { type: 'number', min: 0, max: 48 } },
|
|
19
|
+
},
|
|
20
|
+
args: { columns: 3, gap: 16 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof Masonry>;
|
|
25
|
+
|
|
26
|
+
const cards = Array.from({ length: 12 }).map((_, i) => ({
|
|
27
|
+
id: i,
|
|
28
|
+
h: 80 + ((i * 37) % 180),
|
|
29
|
+
text: `卡片 ${i + 1}`,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
export const Playground: Story = {
|
|
33
|
+
render: (args) => (
|
|
34
|
+
<Masonry {...args} className="w-full">
|
|
35
|
+
{cards.map((c) => (
|
|
36
|
+
<div
|
|
37
|
+
key={c.id}
|
|
38
|
+
className="flex items-center justify-center rounded-md border bg-card text-sm"
|
|
39
|
+
style={{ height: c.h }}
|
|
40
|
+
>
|
|
41
|
+
{c.text}({c.h}px)
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</Masonry>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const ColorfulPhotos: Story = {
|
|
49
|
+
parameters: { controls: { disable: true } },
|
|
50
|
+
render: () => (
|
|
51
|
+
<Masonry columns={4} gap={12} className="w-full">
|
|
52
|
+
{cards.map((c) => (
|
|
53
|
+
<div
|
|
54
|
+
key={c.id}
|
|
55
|
+
className="rounded-md bg-muted text-center text-xs text-muted-foreground"
|
|
56
|
+
style={{
|
|
57
|
+
height: c.h,
|
|
58
|
+
background: `linear-gradient(135deg, hsl(${(c.id * 30) % 360}, 60%, 70%), hsl(${(c.id * 30 + 60) % 360}, 60%, 50%))`,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<span className="block py-2 text-white/90">photo {c.id + 1}</span>
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</Masonry>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
|
|
5
|
+
export interface MasonryProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/**
|
|
7
|
+
* 列数(antd `columns` 并集) — 固定值或响应式断点配置 `{ default, sm, md, lg, xl }`。
|
|
8
|
+
*
|
|
9
|
+
* **响应式注意**:本组件用 CSS `column-count`,断点匹配靠 Tailwind `@container` 不便,
|
|
10
|
+
* 简化为 fixed 列数。响应式建议直接在外层包 className(如 `md:columns-3 columns-2`)+ `columns={undefined}`。
|
|
11
|
+
* @default 3
|
|
12
|
+
*/
|
|
13
|
+
columns?: number;
|
|
14
|
+
/**
|
|
15
|
+
* 列间距(像素)。
|
|
16
|
+
* @default 16
|
|
17
|
+
*/
|
|
18
|
+
gap?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 瀑布流 — antd 独有补足。**等价 antd `Masonry`**(v6.0 新)。基于 CSS `column-count`
|
|
23
|
+
* + `break-inside: avoid` 的纯 CSS 实现 — 0 JS、SSR 友好、自动按 children 高度填充列。
|
|
24
|
+
*
|
|
25
|
+
* **局限**(对齐 CSS columns 的固有限制):
|
|
26
|
+
* - 子项按 **DOM 顺序**填充列(自上而下、自左而右一列填完再到下一列)
|
|
27
|
+
* - 不像 JS-driven masonry(react-masonry-css / Pinterest 风格)那样按"哪列最矮就填哪列"
|
|
28
|
+
* - 适合内容流(博客、相册),不适合严格视觉对齐的场景
|
|
29
|
+
*/
|
|
30
|
+
const Masonry = React.forwardRef<HTMLDivElement, MasonryProps>(
|
|
31
|
+
({ columns = 3, gap = 16, className, children, style, ...props }, ref) => {
|
|
32
|
+
const items = React.Children.toArray(children).filter(Boolean);
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn('block', className)}
|
|
37
|
+
style={{
|
|
38
|
+
columnCount: columns,
|
|
39
|
+
columnGap: `${gap}px`,
|
|
40
|
+
...style,
|
|
41
|
+
}}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{items.map((child, i) => (
|
|
45
|
+
<div
|
|
46
|
+
key={i}
|
|
47
|
+
className="mb-[var(--masonry-gap)] break-inside-avoid"
|
|
48
|
+
style={{ ['--masonry-gap' as string]: `${gap}px` }}
|
|
49
|
+
>
|
|
50
|
+
{child}
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
Masonry.displayName = 'Masonry';
|
|
58
|
+
|
|
59
|
+
export { Masonry };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: mentions
|
|
3
|
+
name: Mentions
|
|
4
|
+
type: component
|
|
5
|
+
category: form
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Mentions
|
|
11
|
+
|
|
12
|
+
@提及 — antd 独有补足。在 Textarea 中输入 `@`(或自定义 `prefix`)即弹出候选下拉,选中后插入 `@name `。键盘 ↑↓ 切换、Enter / Tab 选中、Esc 关闭。**异步候选**用 `onSearch(query)` 由消费方刷新 `options`。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 评论 / 工单输入框需要 @ 同事、# 话题
|
|
17
|
+
- 任意需要"输入触发字符 → 弹出候选 → 选中插入"的场景
|
|
18
|
+
|
|
19
|
+
## When NOT to use
|
|
20
|
+
|
|
21
|
+
- 全字段候选(下拉选单)→ `Select` / `Combobox`
|
|
22
|
+
- 仅是搜索输入 → `Input` + 业务侧自渲染建议列表
|
|
23
|
+
|
|
24
|
+
<!-- auto:props:begin -->
|
|
25
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
26
|
+
| --- | --- | --- | --- | --- |
|
|
27
|
+
| `options` | `MentionsOption[]` | – | ✓ | 候选项(antd `options` 并集)。 |
|
|
28
|
+
| `value` | `string` | – | – | 受控值。 |
|
|
29
|
+
| `defaultValue` | `string` | – | – | uncontrolled 初值。 |
|
|
30
|
+
| `onChange` | `(value: string) => void` | – | – | 文本变化回调。 |
|
|
31
|
+
| `prefix` | `string` | `"@"` | – | 触发符(antd `prefix` 并集) — 通常是 `@` 或 `#`。 |
|
|
32
|
+
| `split` | `string` | `" "` | – | 选中候选项后插入文本的尾随分隔(对齐 antd `split` 默认空格)。 |
|
|
33
|
+
| `onSearch` | `(query: string, prefix: string) => void` | – | – | 用户在 prefix 后输入时的搜索回调,用于业务侧异步刷新 `options`。 |
|
|
34
|
+
| `onSelect` | `(option: MentionsOption, prefix: string) => void` | – | – | 选中候选项时回调。 |
|
|
35
|
+
| `rows` | `number` | `3` | – | 文本框尺寸传透传 Textarea(行数)。 |
|
|
36
|
+
| `disabled` | `boolean` | – | – | 禁用。 |
|
|
37
|
+
<!-- auto:props:end -->
|
|
38
|
+
|
|
39
|
+
<!-- auto:deps:begin -->
|
|
40
|
+
### 同库依赖
|
|
41
|
+
|
|
42
|
+
> `teamix-evo ui add mentions` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
43
|
+
|
|
44
|
+
| Entry | 类型 | 描述 |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
47
|
+
| `textarea` | component | 多行文本输入 — shadcn 基底 + antd autoSize / showCount |
|
|
48
|
+
|
|
49
|
+
### npm 依赖
|
|
50
|
+
|
|
51
|
+
_无 — 本组件不依赖任何 npm 包。_
|
|
52
|
+
<!-- auto:deps:end -->
|
|
53
|
+
|
|
54
|
+
## AI 生成纪律
|
|
55
|
+
|
|
56
|
+
- **`onSearch` 是异步刷新点**:`options` 由消费方根据 `query` 拉接口刷新,组件本身不内置网络层
|
|
57
|
+
- **`prefix` 必须是单字符**(`@` / `#` / `/` 等)— 多字符触发会与正文混淆
|
|
58
|
+
- **不要在 `onSelect` 里改 `value`** — 组件已经替消费方插入并触发了 `onChange`
|
|
59
|
+
- **email 不会误触**:`a@b` 中 `@` 前是字母,组件不会把它当作触发符;触发符前必须是空白或行首
|
|
60
|
+
- **键盘冲突**:Enter 在候选打开时被消费(选中候选),关闭时回归原生换行 — 这是预期
|
|
61
|
+
- **大候选列表**(> 100 项)请在 `onSearch` 中做服务端筛选,组件不内置虚拟化
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Mentions } from '@/components/ui/mentions';
|
|
67
|
+
import * as React from 'react';
|
|
68
|
+
|
|
69
|
+
const users = [
|
|
70
|
+
{ value: 'alice', label: 'Alice (前端)' },
|
|
71
|
+
{ value: 'bob', label: 'Bob (后端)' },
|
|
72
|
+
{ value: 'carol', label: 'Carol (设计)' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// 静态候选
|
|
76
|
+
<Mentions options={users} placeholder="输入 @ 提及同事..." />
|
|
77
|
+
|
|
78
|
+
// 异步候选
|
|
79
|
+
const [opts, setOpts] = React.useState(users);
|
|
80
|
+
<Mentions
|
|
81
|
+
options={opts}
|
|
82
|
+
onSearch={async (q) => {
|
|
83
|
+
const list = await fetchUsers(q);
|
|
84
|
+
setOpts(list);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
// 双触发符:@ + # — 当前组件支持单 prefix,如需多触发请实例化两个 Mentions
|
|
89
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Mentions } from './mentions';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Mentions> = {
|
|
6
|
+
title: '表单与输入 · Form/Mentions',
|
|
7
|
+
component: Mentions,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'@提及输入 — Textarea + @ 触发候选下拉(评论 @ 同事、# 话题)。↑↓ 切换、Enter / Tab 选中、Esc 关闭。异步候选用 onSearch 由消费方刷新 options。视觉走 OpenTrek tokens,等价 antd `Mentions`,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
prefix: { control: 'text' },
|
|
19
|
+
placeholder: { control: 'text' },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof Mentions>;
|
|
25
|
+
|
|
26
|
+
const users = [
|
|
27
|
+
{ value: 'alice', label: 'Alice (前端)' },
|
|
28
|
+
{ value: 'bob', label: 'Bob (后端)' },
|
|
29
|
+
{ value: 'carol', label: 'Carol (设计)' },
|
|
30
|
+
{ value: 'david', label: 'David (产品)' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const Playground: Story = {
|
|
34
|
+
args: { placeholder: '输入 @ 提及同事...', options: users },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const HashtagPrefix: Story = {
|
|
38
|
+
parameters: { controls: { disable: true } },
|
|
39
|
+
render: () => (
|
|
40
|
+
<Mentions
|
|
41
|
+
options={[
|
|
42
|
+
{ value: 'frontend', label: '#frontend' },
|
|
43
|
+
{ value: 'backend', label: '#backend' },
|
|
44
|
+
{ value: 'design', label: '#design' },
|
|
45
|
+
]}
|
|
46
|
+
prefix="#"
|
|
47
|
+
placeholder="输入 # 添加话题..."
|
|
48
|
+
/>
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const AsyncSearch: Story = {
|
|
53
|
+
parameters: { controls: { disable: true } },
|
|
54
|
+
render: () => {
|
|
55
|
+
const [opts, setOpts] = React.useState(users);
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex flex-col gap-2">
|
|
58
|
+
<Mentions
|
|
59
|
+
options={opts}
|
|
60
|
+
placeholder="输入 @ 触发模拟异步搜索..."
|
|
61
|
+
onSearch={(q) => {
|
|
62
|
+
const next = users.filter((u) =>
|
|
63
|
+
u.value.toLowerCase().includes(q.toLowerCase()),
|
|
64
|
+
);
|
|
65
|
+
// simulate latency
|
|
66
|
+
window.setTimeout(() => setOpts(next), 80);
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
<div className="text-xs text-muted-foreground">
|
|
70
|
+
当前候选数: {opts.length}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
};
|