@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,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: native-select
|
|
3
|
+
name: NativeSelect
|
|
4
|
+
type: component
|
|
5
|
+
category: form
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# NativeSelect
|
|
11
|
+
|
|
12
|
+
原生下拉选择 — shadcn 2025-10 新增。**原生 `<select>` 元素**的样式封装,零 JS 开销,移动端弹起系统 picker,直接参与 form `submit`。与 `Select`(Radix)互为补充,分别面向"原生友好"与"可定制项 UI"两种诉求。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 移动端表单(系统原生 picker 更熟悉,触屏体验更好)
|
|
17
|
+
- 无 JS / SSR-first 场景
|
|
18
|
+
- 不需要自定义选项 UI 的简单下拉(国家、省份、单位等)
|
|
19
|
+
- 表单数据靠原生 `submit` 收集,不想引入受控状态
|
|
20
|
+
|
|
21
|
+
## When NOT to use
|
|
22
|
+
|
|
23
|
+
- 需要搜索 / 虚拟列表 / 自定义选项渲染 → `Select`(Radix)/ `Combobox`
|
|
24
|
+
- 多选 → `Select multiple`(原生 multi 体验差)→ 用 Radix 版
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
| `wrapperClassName` | `string` | – | – | 容器额外 className(给外层 wrapper,而非 `<select>` 本身)。 |
|
|
30
|
+
<!-- auto:props:end -->
|
|
31
|
+
|
|
32
|
+
<!-- auto:deps:begin -->
|
|
33
|
+
### 同库依赖
|
|
34
|
+
|
|
35
|
+
> `teamix-evo ui add native-select` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
36
|
+
|
|
37
|
+
| Entry | 类型 | 描述 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
40
|
+
|
|
41
|
+
### npm 依赖
|
|
42
|
+
|
|
43
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add lucide-react@^0.460.0
|
|
47
|
+
```
|
|
48
|
+
<!-- auto:deps:end -->
|
|
49
|
+
|
|
50
|
+
## AI 生成纪律
|
|
51
|
+
|
|
52
|
+
- **用 children `<option>`** 而非 `options` 数组 — 保留原生 `<optgroup>` / `disabled` / `selected` 表达力
|
|
53
|
+
- **`appearance-none`** 已禁掉浏览器默认箭头,我们用 lucide ChevronDown 替代 — **不要**自己再加箭头
|
|
54
|
+
- **不要做受控** — 原生 select 在受控时不流畅(光标位置 / 滚动会跳);需要受控时直接用 Radix `Select`
|
|
55
|
+
- **可访问性**:本组件无需额外 aria,原生 `<select>` 是最 accessible 的下拉之一
|
|
56
|
+
- **移动端**:不要给 `<select>` 加自定义 `onTouchStart` / `onClick`,会破坏系统 picker
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { NativeSelect } from '@/components/ui/native-select';
|
|
62
|
+
|
|
63
|
+
// 基础
|
|
64
|
+
<NativeSelect defaultValue="apple">
|
|
65
|
+
<option value="apple">苹果</option>
|
|
66
|
+
<option value="banana">香蕉</option>
|
|
67
|
+
<option value="cherry">樱桃</option>
|
|
68
|
+
</NativeSelect>
|
|
69
|
+
|
|
70
|
+
// 分组
|
|
71
|
+
<NativeSelect>
|
|
72
|
+
<optgroup label="水果">
|
|
73
|
+
<option value="apple">苹果</option>
|
|
74
|
+
<option value="banana">香蕉</option>
|
|
75
|
+
</optgroup>
|
|
76
|
+
<optgroup label="蔬菜">
|
|
77
|
+
<option value="carrot">胡萝卜</option>
|
|
78
|
+
</optgroup>
|
|
79
|
+
</NativeSelect>
|
|
80
|
+
|
|
81
|
+
// 表单 submit
|
|
82
|
+
<form action="/api/sort">
|
|
83
|
+
<NativeSelect name="sort" defaultValue="date">
|
|
84
|
+
<option value="date">按日期</option>
|
|
85
|
+
<option value="name">按名称</option>
|
|
86
|
+
</NativeSelect>
|
|
87
|
+
</form>
|
|
88
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { NativeSelect } from './native-select';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof NativeSelect> = {
|
|
5
|
+
title: '表单与输入 · Form/NativeSelect',
|
|
6
|
+
component: NativeSelect,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'原生下拉选择 — 原生 `<select>` 元素的样式封装,零 JS 开销,移动端弹起系统 picker,直接参与 form submit。与 Radix `Select` 互为补充:NativeSelect 面向原生 / 移动端友好,Radix Select 面向自定义项 UI / 受控复杂表单。shadcn 2025-10 新增,等价 antd 体系中无直接对应(antd Select 全用 popup)。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
disabled: { control: 'boolean' },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof NativeSelect>;
|
|
23
|
+
|
|
24
|
+
export const Playground: Story = {
|
|
25
|
+
render: (args) => (
|
|
26
|
+
<NativeSelect {...args} defaultValue="apple" wrapperClassName="w-48">
|
|
27
|
+
<option value="apple">苹果</option>
|
|
28
|
+
<option value="banana">香蕉</option>
|
|
29
|
+
<option value="cherry">樱桃</option>
|
|
30
|
+
</NativeSelect>
|
|
31
|
+
),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Grouped: Story = {
|
|
35
|
+
parameters: { controls: { disable: true } },
|
|
36
|
+
render: () => (
|
|
37
|
+
<NativeSelect defaultValue="apple" wrapperClassName="w-48">
|
|
38
|
+
<optgroup label="水果">
|
|
39
|
+
<option value="apple">苹果</option>
|
|
40
|
+
<option value="banana">香蕉</option>
|
|
41
|
+
</optgroup>
|
|
42
|
+
<optgroup label="蔬菜">
|
|
43
|
+
<option value="carrot">胡萝卜</option>
|
|
44
|
+
<option value="celery">芹菜</option>
|
|
45
|
+
</optgroup>
|
|
46
|
+
</NativeSelect>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Disabled: Story = {
|
|
51
|
+
parameters: { controls: { disable: true } },
|
|
52
|
+
render: () => (
|
|
53
|
+
<NativeSelect disabled defaultValue="apple" wrapperClassName="w-48">
|
|
54
|
+
<option value="apple">不可改</option>
|
|
55
|
+
</NativeSelect>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const InForm: Story = {
|
|
60
|
+
parameters: { controls: { disable: true } },
|
|
61
|
+
render: () => (
|
|
62
|
+
<form
|
|
63
|
+
onSubmit={(e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
const fd = new FormData(e.currentTarget);
|
|
66
|
+
// eslint-disable-next-line no-alert
|
|
67
|
+
alert(`提交: sort=${fd.get('sort')}`);
|
|
68
|
+
}}
|
|
69
|
+
className="flex items-center gap-2"
|
|
70
|
+
>
|
|
71
|
+
<NativeSelect name="sort" defaultValue="date" wrapperClassName="w-40">
|
|
72
|
+
<option value="date">按日期</option>
|
|
73
|
+
<option value="name">按名称</option>
|
|
74
|
+
</NativeSelect>
|
|
75
|
+
<button type="submit" className="rounded-md border px-3 py-1 text-sm hover:bg-accent">
|
|
76
|
+
提交
|
|
77
|
+
</button>
|
|
78
|
+
</form>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChevronDown } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/utils/cn';
|
|
5
|
+
|
|
6
|
+
export interface NativeSelectProps
|
|
7
|
+
extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
|
8
|
+
/**
|
|
9
|
+
* 容器额外 className(给外层 wrapper,而非 `<select>` 本身)。
|
|
10
|
+
*/
|
|
11
|
+
wrapperClassName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 原生 `<select>` 包装 — shadcn 2025-10 新增。
|
|
16
|
+
* 与 Radix `Select` 的区别:
|
|
17
|
+
* - **零 JS 开销**,移动端弹起系统原生 picker(更熟悉)
|
|
18
|
+
* - 表单 `submit` 直接带 value,不需要受控
|
|
19
|
+
* - 不支持自定义选项渲染(那是 Radix Select 的活)
|
|
20
|
+
*
|
|
21
|
+
* 用 `<NativeSelect><option>...</option></NativeSelect>` 而非 `options` 数组,
|
|
22
|
+
* 保留原生标签的灵活性(`<optgroup>` 嵌套、disabled / selected 属性等)。
|
|
23
|
+
*/
|
|
24
|
+
const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
25
|
+
({ className, wrapperClassName, children, disabled, ...props }, ref) => (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
'relative inline-flex w-full items-center',
|
|
29
|
+
wrapperClassName,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<select
|
|
33
|
+
ref={ref}
|
|
34
|
+
disabled={disabled}
|
|
35
|
+
className={cn(
|
|
36
|
+
'flex h-9 w-full appearance-none rounded-md border border-input bg-background py-1 pl-3 pr-8 text-sm shadow-sm',
|
|
37
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
|
38
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</select>
|
|
45
|
+
<ChevronDown
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
className="pointer-events-none absolute right-2 size-4 text-muted-foreground"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
NativeSelect.displayName = 'NativeSelect';
|
|
53
|
+
|
|
54
|
+
export { NativeSelect };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: navigation-menu
|
|
3
|
+
name: NavigationMenu
|
|
4
|
+
type: component
|
|
5
|
+
category: navigation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# NavigationMenu
|
|
11
|
+
|
|
12
|
+
主导航菜单 — Radix NavigationMenu。**用于网站 / 营销页 / 应用顶部主导航**(产品 / 文档 / 定价 / 关于)。
|
|
13
|
+
**与 Menubar 区别**:NavigationMenu 是**网页级横向导航**(每项可有富 Content 面板,展示子页面预览);Menubar 是**应用级菜单**(文件 / 编辑等)。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 网站顶部主导航(产品 / 解决方案 / 资源 / 公司)
|
|
18
|
+
- 后台应用顶栏页面级跳转
|
|
19
|
+
- 多产品矩阵的入口面板
|
|
20
|
+
|
|
21
|
+
## When NOT to use
|
|
22
|
+
|
|
23
|
+
- 应用功能菜单 → `Menubar`
|
|
24
|
+
- 单按钮下拉 → `DropdownMenu`
|
|
25
|
+
- 侧边导航 → `Sidebar`
|
|
26
|
+
|
|
27
|
+
## Props
|
|
28
|
+
|
|
29
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `NavigationMenu`(Root) 的 props。
|
|
30
|
+
|
|
31
|
+
<!-- auto:props:begin -->
|
|
32
|
+
_(组件无 `<Name>Props` interface — props 详见 [`navigation-menu.tsx`](./navigation-menu.tsx))_
|
|
33
|
+
<!-- auto:props:end -->
|
|
34
|
+
|
|
35
|
+
## 依赖
|
|
36
|
+
|
|
37
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
38
|
+
|
|
39
|
+
<!-- auto:deps:begin -->
|
|
40
|
+
### 同库依赖
|
|
41
|
+
|
|
42
|
+
> `teamix-evo ui add navigation-menu` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
43
|
+
|
|
44
|
+
| Entry | 类型 | 描述 |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
47
|
+
|
|
48
|
+
### npm 依赖
|
|
49
|
+
|
|
50
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add @radix-ui/react-navigation-menu@^1.2.0 class-variance-authority@^0.7.0 lucide-react@^0.460.0
|
|
54
|
+
```
|
|
55
|
+
<!-- auto:deps:end -->
|
|
56
|
+
|
|
57
|
+
> 完整子组件:`NavigationMenu` / `NavigationMenuList` / `NavigationMenuItem` / `NavigationMenuTrigger` / `NavigationMenuContent` / `NavigationMenuLink` / `NavigationMenuIndicator` / `NavigationMenuViewport` + `navigationMenuTriggerStyle`(给独立 Link 复用 trigger 视觉)。
|
|
58
|
+
|
|
59
|
+
## AI 生成纪律
|
|
60
|
+
|
|
61
|
+
- **`<NavigationMenuLink asChild>` 配 router**:Next.js / React Router 的 Link 嵌入,asChild 透传
|
|
62
|
+
- **Content 面板内容富但克制**:常见结构是 grid + 条目卡片,**不要**塞表单 / 长文本
|
|
63
|
+
- **Triger 文字 ≤ 4 字**:主导航文案极简
|
|
64
|
+
- **不嵌套 NavigationMenu**:嵌套主导航是反模式
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import * as React from 'react';
|
|
70
|
+
import {
|
|
71
|
+
NavigationMenu, NavigationMenuList, NavigationMenuItem,
|
|
72
|
+
NavigationMenuTrigger, NavigationMenuContent,
|
|
73
|
+
NavigationMenuLink, navigationMenuTriggerStyle,
|
|
74
|
+
} from '@/components/ui/navigation-menu';
|
|
75
|
+
|
|
76
|
+
<NavigationMenu>
|
|
77
|
+
<NavigationMenuList>
|
|
78
|
+
<NavigationMenuItem>
|
|
79
|
+
<NavigationMenuTrigger>产品</NavigationMenuTrigger>
|
|
80
|
+
<NavigationMenuContent>
|
|
81
|
+
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2">
|
|
82
|
+
<li>
|
|
83
|
+
<NavigationMenuLink asChild>
|
|
84
|
+
<a href="/design" className="block rounded-md p-3 hover:bg-accent">
|
|
85
|
+
<div className="text-sm font-medium">Design</div>
|
|
86
|
+
<p className="text-xs text-muted-foreground">设计体系与 tokens</p>
|
|
87
|
+
</a>
|
|
88
|
+
</NavigationMenuLink>
|
|
89
|
+
</li>
|
|
90
|
+
<li>
|
|
91
|
+
<NavigationMenuLink asChild>
|
|
92
|
+
<a href="/ui" className="block rounded-md p-3 hover:bg-accent">
|
|
93
|
+
<div className="text-sm font-medium">UI</div>
|
|
94
|
+
<p className="text-xs text-muted-foreground">源码注入式组件</p>
|
|
95
|
+
</a>
|
|
96
|
+
</NavigationMenuLink>
|
|
97
|
+
</li>
|
|
98
|
+
</ul>
|
|
99
|
+
</NavigationMenuContent>
|
|
100
|
+
</NavigationMenuItem>
|
|
101
|
+
<NavigationMenuItem>
|
|
102
|
+
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
|
103
|
+
<a href="/docs">文档</a>
|
|
104
|
+
</NavigationMenuLink>
|
|
105
|
+
</NavigationMenuItem>
|
|
106
|
+
</NavigationMenuList>
|
|
107
|
+
</NavigationMenu>
|
|
108
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
NavigationMenu,
|
|
4
|
+
NavigationMenuList,
|
|
5
|
+
NavigationMenuItem,
|
|
6
|
+
NavigationMenuTrigger,
|
|
7
|
+
NavigationMenuContent,
|
|
8
|
+
NavigationMenuLink,
|
|
9
|
+
navigationMenuTriggerStyle,
|
|
10
|
+
} from './navigation-menu';
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof NavigationMenu> = {
|
|
13
|
+
title: '导航 · Navigation/NavigationMenu',
|
|
14
|
+
component: NavigationMenu,
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof NavigationMenu>;
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
parameters: { layout: 'centered' },
|
|
23
|
+
render: () => (
|
|
24
|
+
<NavigationMenu>
|
|
25
|
+
<NavigationMenuList>
|
|
26
|
+
<NavigationMenuItem>
|
|
27
|
+
<NavigationMenuTrigger>产品</NavigationMenuTrigger>
|
|
28
|
+
<NavigationMenuContent>
|
|
29
|
+
<ul className="grid w-[420px] gap-3 p-4 md:w-[520px] md:grid-cols-2">
|
|
30
|
+
<li>
|
|
31
|
+
<NavigationMenuLink asChild>
|
|
32
|
+
<a
|
|
33
|
+
href="#design"
|
|
34
|
+
onClick={(e) => e.preventDefault()}
|
|
35
|
+
className="block rounded-md p-3 hover:bg-accent"
|
|
36
|
+
>
|
|
37
|
+
<div className="text-sm font-medium">Design</div>
|
|
38
|
+
<p className="text-xs text-muted-foreground">
|
|
39
|
+
设计体系与 tokens
|
|
40
|
+
</p>
|
|
41
|
+
</a>
|
|
42
|
+
</NavigationMenuLink>
|
|
43
|
+
</li>
|
|
44
|
+
<li>
|
|
45
|
+
<NavigationMenuLink asChild>
|
|
46
|
+
<a
|
|
47
|
+
href="#ui"
|
|
48
|
+
onClick={(e) => e.preventDefault()}
|
|
49
|
+
className="block rounded-md p-3 hover:bg-accent"
|
|
50
|
+
>
|
|
51
|
+
<div className="text-sm font-medium">UI</div>
|
|
52
|
+
<p className="text-xs text-muted-foreground">
|
|
53
|
+
源码注入式组件
|
|
54
|
+
</p>
|
|
55
|
+
</a>
|
|
56
|
+
</NavigationMenuLink>
|
|
57
|
+
</li>
|
|
58
|
+
<li>
|
|
59
|
+
<NavigationMenuLink asChild>
|
|
60
|
+
<a
|
|
61
|
+
href="#cli"
|
|
62
|
+
onClick={(e) => e.preventDefault()}
|
|
63
|
+
className="block rounded-md p-3 hover:bg-accent"
|
|
64
|
+
>
|
|
65
|
+
<div className="text-sm font-medium">CLI</div>
|
|
66
|
+
<p className="text-xs text-muted-foreground">
|
|
67
|
+
teamix-evo 命令行
|
|
68
|
+
</p>
|
|
69
|
+
</a>
|
|
70
|
+
</NavigationMenuLink>
|
|
71
|
+
</li>
|
|
72
|
+
<li>
|
|
73
|
+
<NavigationMenuLink asChild>
|
|
74
|
+
<a
|
|
75
|
+
href="#skills"
|
|
76
|
+
onClick={(e) => e.preventDefault()}
|
|
77
|
+
className="block rounded-md p-3 hover:bg-accent"
|
|
78
|
+
>
|
|
79
|
+
<div className="text-sm font-medium">Skills</div>
|
|
80
|
+
<p className="text-xs text-muted-foreground">
|
|
81
|
+
AI IDE 技能
|
|
82
|
+
</p>
|
|
83
|
+
</a>
|
|
84
|
+
</NavigationMenuLink>
|
|
85
|
+
</li>
|
|
86
|
+
</ul>
|
|
87
|
+
</NavigationMenuContent>
|
|
88
|
+
</NavigationMenuItem>
|
|
89
|
+
<NavigationMenuItem>
|
|
90
|
+
<NavigationMenuLink
|
|
91
|
+
asChild
|
|
92
|
+
className={navigationMenuTriggerStyle()}
|
|
93
|
+
>
|
|
94
|
+
<a href="#docs" onClick={(e) => e.preventDefault()}>
|
|
95
|
+
文档
|
|
96
|
+
</a>
|
|
97
|
+
</NavigationMenuLink>
|
|
98
|
+
</NavigationMenuItem>
|
|
99
|
+
<NavigationMenuItem>
|
|
100
|
+
<NavigationMenuLink
|
|
101
|
+
asChild
|
|
102
|
+
className={navigationMenuTriggerStyle()}
|
|
103
|
+
>
|
|
104
|
+
<a href="#pricing" onClick={(e) => e.preventDefault()}>
|
|
105
|
+
定价
|
|
106
|
+
</a>
|
|
107
|
+
</NavigationMenuLink>
|
|
108
|
+
</NavigationMenuItem>
|
|
109
|
+
</NavigationMenuList>
|
|
110
|
+
</NavigationMenu>
|
|
111
|
+
),
|
|
112
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { ChevronDown } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/utils/cn';
|
|
7
|
+
|
|
8
|
+
const NavigationMenu = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
|
11
|
+
>(({ className, children, ...props }, ref) => (
|
|
12
|
+
<NavigationMenuPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn('relative z-10 flex max-w-max flex-1 items-center justify-center', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
<NavigationMenuViewport />
|
|
19
|
+
</NavigationMenuPrimitive.Root>
|
|
20
|
+
));
|
|
21
|
+
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
|
22
|
+
|
|
23
|
+
const NavigationMenuList = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
|
26
|
+
>(({ className, ...props }, ref) => (
|
|
27
|
+
<NavigationMenuPrimitive.List
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn(
|
|
30
|
+
'group flex flex-1 list-none items-center justify-center gap-1',
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
));
|
|
36
|
+
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
|
37
|
+
|
|
38
|
+
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
|
39
|
+
|
|
40
|
+
const navigationMenuTriggerStyle = cva(
|
|
41
|
+
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const NavigationMenuTrigger = React.forwardRef<
|
|
45
|
+
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
|
46
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
|
47
|
+
>(({ className, children, ...props }, ref) => (
|
|
48
|
+
<NavigationMenuPrimitive.Trigger
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={cn(navigationMenuTriggerStyle(), 'group', className)}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}{' '}
|
|
54
|
+
<ChevronDown
|
|
55
|
+
className="relative top-px ml-1 size-3 transition duration-200 group-data-[state=open]:rotate-180"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
/>
|
|
58
|
+
</NavigationMenuPrimitive.Trigger>
|
|
59
|
+
));
|
|
60
|
+
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
|
61
|
+
|
|
62
|
+
const NavigationMenuContent = React.forwardRef<
|
|
63
|
+
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
|
65
|
+
>(({ className, ...props }, ref) => (
|
|
66
|
+
<NavigationMenuPrimitive.Content
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
));
|
|
75
|
+
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
|
76
|
+
|
|
77
|
+
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
|
78
|
+
|
|
79
|
+
const NavigationMenuViewport = React.forwardRef<
|
|
80
|
+
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
|
81
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
|
82
|
+
>(({ className, ...props }, ref) => (
|
|
83
|
+
<div className={cn('absolute left-0 top-full flex justify-center')}>
|
|
84
|
+
<NavigationMenuPrimitive.Viewport
|
|
85
|
+
ref={ref}
|
|
86
|
+
className={cn(
|
|
87
|
+
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
));
|
|
94
|
+
NavigationMenuViewport.displayName =
|
|
95
|
+
NavigationMenuPrimitive.Viewport.displayName;
|
|
96
|
+
|
|
97
|
+
const NavigationMenuIndicator = React.forwardRef<
|
|
98
|
+
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
|
99
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
|
100
|
+
>(({ className, ...props }, ref) => (
|
|
101
|
+
<NavigationMenuPrimitive.Indicator
|
|
102
|
+
ref={ref}
|
|
103
|
+
className={cn(
|
|
104
|
+
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
<div className="relative top-[60%] size-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
|
110
|
+
</NavigationMenuPrimitive.Indicator>
|
|
111
|
+
));
|
|
112
|
+
NavigationMenuIndicator.displayName =
|
|
113
|
+
NavigationMenuPrimitive.Indicator.displayName;
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
navigationMenuTriggerStyle,
|
|
117
|
+
NavigationMenu,
|
|
118
|
+
NavigationMenuList,
|
|
119
|
+
NavigationMenuItem,
|
|
120
|
+
NavigationMenuContent,
|
|
121
|
+
NavigationMenuTrigger,
|
|
122
|
+
NavigationMenuLink,
|
|
123
|
+
NavigationMenuIndicator,
|
|
124
|
+
NavigationMenuViewport,
|
|
125
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: notification
|
|
3
|
+
name: notification
|
|
4
|
+
type: component
|
|
5
|
+
category: feedback
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Notification
|
|
11
|
+
|
|
12
|
+
卡片式通知 — antd 独有补足。**等价 antd `Notification`**(右上角 title + description 卡片),与 sonner 同一渲染栈。基于 sonner `toast.custom` 二次封装,业务侧**只需要挂载一次 `<Toaster />`**,后续在任意地方调 `notification.success(...)` / `notification.error(...)` 等触发。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 操作完成的"带描述卡片"反馈(部署成功、文件已上传、订单已生成)
|
|
17
|
+
- 异步流程的最终结果通知(配 description 解释下一步)
|
|
18
|
+
- 系统级提醒(订阅到期、新版本可用)
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 短文字提示("已保存") → `toast()`(对应 antd `message`)
|
|
23
|
+
- 阻塞式确认 → `Dialog` / `AlertDialog` / `Popconfirm`
|
|
24
|
+
- 表单字段错误 → `FieldError`(就近显示)
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
_(组件无 `<Name>Props` interface — props 详见 [`notification.tsx`](./notification.tsx))_
|
|
28
|
+
<!-- auto:props:end -->
|
|
29
|
+
|
|
30
|
+
<!-- auto:deps:begin -->
|
|
31
|
+
### 同库依赖
|
|
32
|
+
|
|
33
|
+
> `teamix-evo ui add notification` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
34
|
+
|
|
35
|
+
| Entry | 类型 | 描述 |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
38
|
+
| `sonner` | component | Toast 通知 — sonner 包装,等价 antd message + notification 并集(toast() 函数式 API + Toaster 容器) |
|
|
39
|
+
|
|
40
|
+
### npm 依赖
|
|
41
|
+
|
|
42
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm add sonner@^1.5.0 lucide-react@^0.460.0
|
|
46
|
+
```
|
|
47
|
+
<!-- auto:deps:end -->
|
|
48
|
+
|
|
49
|
+
## AI 生成纪律
|
|
50
|
+
|
|
51
|
+
- **业务侧必须挂载 `<Toaster />`** —— Notification 与 toast 共用同一容器,通常挂在 root layout
|
|
52
|
+
- **`title` 用动词或事件名**:"部署成功"/"订单已创建";**不要**写"提示"等空话
|
|
53
|
+
- **`description` 解释后果或下一步**:"用户可在 1 分钟内访问新版本"/"详情请查看订单页";**不要**重复 title
|
|
54
|
+
- **类型语义不要混淆**:`success` 用于成功完成,`warning` 用于风险提示但操作未失败,`error` 用于真正失败 — 与 `Alert` / `Toast` 的语义一致
|
|
55
|
+
- **不要在 onConfirm 里同时调 toast 和 notification** — 单次反馈选一种,避免视觉噪音
|
|
56
|
+
- **`destroy(id)` 主动关闭**:用于"自动重试成功"等场景,把之前的 error notification 替换掉
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { notification } from '@/components/ui/notification';
|
|
62
|
+
|
|
63
|
+
// 各类型快捷方法
|
|
64
|
+
notification.success({
|
|
65
|
+
title: '部署成功',
|
|
66
|
+
description: '用户可在 1 分钟内访问新版本',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
notification.warning({
|
|
70
|
+
title: '配额即将到期',
|
|
71
|
+
description: '请在 3 天内续费,否则服务将暂停',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
notification.error({
|
|
75
|
+
title: '上传失败',
|
|
76
|
+
description: '请检查网络连接后重试',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 自定义 + 操作按钮(action 透传 sonner)
|
|
80
|
+
const id = notification.open({
|
|
81
|
+
type: 'info',
|
|
82
|
+
title: '新版本可用',
|
|
83
|
+
description: 'v2.5 已发布,刷新页面以应用更新',
|
|
84
|
+
action: { label: '刷新', onClick: () => location.reload() },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 主动关闭
|
|
88
|
+
notification.destroy(id);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> 应用根组件需要挂载一次 `<Toaster />`(来自 `sonner` entry),Notification 与 toast 共用此容器。
|