@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
@@ -63,7 +63,7 @@ export const WithTrailingIcon: Story = {
63
63
  * 仅当 value 非空时显示清除按钮。
64
64
  */
65
65
  export const Search: Story = {
66
- render: function SearchStory() {
66
+ render: () => {
67
67
  const [value, setValue] = useState('');
68
68
  return (
69
69
  <InputGroup className="w-64">
@@ -93,7 +93,7 @@ export const Search: Story = {
93
93
  * 仅当 `visible` 为 true 时渲染,点击由父级清空 `value`。
94
94
  */
95
95
  export const Clear: Story = {
96
- render: function ClearStory() {
96
+ render: () => {
97
97
  const [value, setValue] = useState('hello world');
98
98
  return (
99
99
  <InputGroup className="w-64">
@@ -117,7 +117,7 @@ export const Clear: Story = {
117
117
  * 密码输入:使用 `InputGroupPasswordToggle` 受控版,由父级同时控制 `type` 与 `visible`。
118
118
  */
119
119
  export const Password: Story = {
120
- render: function PasswordStory() {
120
+ render: () => {
121
121
  const [visible, setVisible] = useState(false);
122
122
  return (
123
123
  <InputGroup className="w-64">
@@ -138,7 +138,7 @@ export const Password: Story = {
138
138
 
139
139
  /** 金额输入:后置货币符号,展示带格式化的数值输入。 */
140
140
  export const Currency: Story = {
141
- render: function CurrencyStory() {
141
+ render: () => {
142
142
  const [value, setValue] = useState('1234.56');
143
143
  const format = (v: string) => {
144
144
  const num = parseFloat(v.replace(/,/g, ''));
@@ -199,7 +199,7 @@ export const Loading: Story = {
199
199
  * 超过 maxLength 时计数自动转 destructive 色。
200
200
  */
201
201
  export const WithCount: Story = {
202
- render: function WithCountStory() {
202
+ render: () => {
203
203
  const [value, setValue] = useState('');
204
204
  const max = 20;
205
205
  return (
@@ -223,7 +223,7 @@ export const WithCount: Story = {
223
223
  * 对齐 cloud-design `hasLimitHint` 的视觉位置。
224
224
  */
225
225
  export const MultilineWithCount: Story = {
226
- render: function MultilineWithCountStory() {
226
+ render: () => {
227
227
  const [value, setValue] = useState('');
228
228
  const max = 100;
229
229
  return (
@@ -224,7 +224,7 @@ const InputIP = React.forwardRef<HTMLDivElement, InputIPProps>(
224
224
  {octets.map((octet, i) => (
225
225
  <React.Fragment key={i}>
226
226
  {i > 0 && (
227
- <span className="select-none text-muted-foreground">.</span>
227
+ <span className="text-muted-foreground select-none">.</span>
228
228
  )}
229
229
  <input
230
230
  ref={(el) => {
@@ -248,7 +248,7 @@ const InputIP = React.forwardRef<HTMLDivElement, InputIPProps>(
248
248
  ))}
249
249
  {showCidr && (
250
250
  <>
251
- <span className="select-none px-0.5 text-muted-foreground">/</span>
251
+ <span className="px-0.5 text-muted-foreground select-none">/</span>
252
252
  <Select
253
253
  value={cidr || undefined}
254
254
  onValueChange={handleCidrChange}
@@ -27,6 +27,28 @@ IP 地址输入框 InputIP
27
27
 
28
28
  ## 示例
29
29
 
30
+ ### Controlled
31
+
32
+ 受控模式,外部管理 value。
33
+
34
+ ```tsx
35
+ <div className="flex flex-col gap-2">
36
+ <InputIP value={value} onChange={setValue} />
37
+ <span className="text-xs text-muted-foreground">当前值:{value}</span>
38
+ </div>
39
+ ```
40
+
41
+ ### WithCidr
42
+
43
+ 带 CIDR 前缀选择的网段输入。右侧 CIDR 选择器使用 ui Select。
44
+
45
+ ```tsx
46
+ <div className="flex flex-col gap-2">
47
+ <InputIP showCidr value={value} onChange={setValue} />
48
+ <span className="text-xs text-muted-foreground">当前值:{value}</span>
49
+ </div>
50
+ ```
51
+
30
52
  ### Sizes
31
53
 
32
54
  三种尺寸。
@@ -39,6 +61,14 @@ IP 地址输入框 InputIP
39
61
  </div>
40
62
  ```
41
63
 
64
+ ### Disabled
65
+
66
+ 禁用态。
67
+
68
+ ```tsx
69
+ <InputIP className="w-52" defaultValue="10.0.0.1" disabled />
70
+ ```
71
+
42
72
  ### Invalid
43
73
 
44
74
  错误态(校验失败)。
@@ -21,7 +21,7 @@ export const Default: Story = {
21
21
 
22
22
  /** 受控模式,外部管理 value。 */
23
23
  export const Controlled: Story = {
24
- render: function ControlledStory() {
24
+ render: () => {
25
25
  const [value, setValue] = useState('192.168.1.1');
26
26
  return (
27
27
  <div className="flex flex-col gap-2">
@@ -34,7 +34,7 @@ export const Controlled: Story = {
34
34
 
35
35
  /** 带 CIDR 前缀选择的网段输入。右侧 CIDR 选择器使用 ui Select。 */
36
36
  export const WithCidr: Story = {
37
- render: function CidrStory() {
37
+ render: () => {
38
38
  const [value, setValue] = useState('192.168.1.0/24');
39
39
  return (
40
40
  <div className="flex flex-col gap-2">
@@ -304,7 +304,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
304
304
  onWheel={handleWheel}
305
305
  data-slot="input-number-control"
306
306
  className={cn(
307
- 'w-full bg-transparent px-2.5 text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed read-only:cursor-default',
307
+ 'w-full bg-transparent px-2.5 text-foreground placeholder:text-muted-foreground read-only:cursor-default focus-visible:outline-none disabled:cursor-not-allowed',
308
308
  variant === 'inline' && 'text-center',
309
309
  )}
310
310
  {...props}
@@ -325,7 +325,8 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
325
325
  {controls && variant === 'default' ? (
326
326
  <div
327
327
  data-slot="input-number-handlers"
328
- className="flex flex-col divide-y divide-input border-l border-input opacity-0 transition-opacity group-hover/input-number:opacity-100 group-focus-within/input-number:opacity-100"
328
+ // eslint-disable-next-line tailwindcss/no-contradicting-classname -- divide-color border-color
329
+ className="flex flex-col divide-y divide-input border-l border-input opacity-0 transition-opacity group-focus-within/input-number:opacity-100 group-hover/input-number:opacity-100"
329
330
  >
330
331
  <button
331
332
  type="button"
@@ -125,6 +125,14 @@
125
125
  </div>
126
126
  ```
127
127
 
128
+ ### ReadOnly
129
+
130
+ `readOnly` 只读:可聚焦/复制但不可编辑、不可步进。
131
+
132
+ ```tsx
133
+ <InputNumber readOnly defaultValue={42} className="w-40" />
134
+ ```
135
+
128
136
  ### Inline
129
137
 
130
138
  `variant="inline"` 两侧加减按钮,适合紧凑场景。
@@ -147,6 +155,38 @@
147
155
  <InputNumber controls={false} placeholder="纯输入" className="w-40" />
148
156
  ```
149
157
 
158
+ ### ChangeOnBlur
159
+
160
+ `changeOnBlur={false}` 让 onChange 在每次有效输入时触发,否则仅 blur/Enter 提交。
161
+
162
+ ```tsx
163
+ <div className="flex flex-col gap-3 text-xs">
164
+ <div className="flex items-center gap-3">
165
+ <InputNumber value={a} onChange={setA} className="w-40" />
166
+ <span className="text-muted-foreground">
167
+ 默认(仅 blur/Enter 触发):{String(a)}
168
+ </span>
169
+ </div>
170
+ <div className="flex items-center gap-3">
171
+ <InputNumber
172
+ value={b}
173
+ onChange={setB}
174
+ changeOnBlur={false}
175
+ className="w-40"
176
+ />
177
+ <span className="text-muted-foreground">实时触发:{String(b)}</span>
178
+ </div>
179
+ </div>
180
+ ```
181
+
182
+ ### ChangeOnWheel
183
+
184
+ `changeOnWheel` 启用滚轮调整(仅在聚焦时生效,默认关闭防误触)。
185
+
186
+ ```tsx
187
+ <InputNumber changeOnWheel defaultValue={0} className="w-40" />
188
+ ```
189
+
150
190
  ### Keyboard
151
191
 
152
192
  `keyboard={false}` 关闭键盘 ↑↓ 步进。
@@ -154,3 +194,30 @@
154
194
  ```tsx
155
195
  <InputNumber keyboard={false} defaultValue={0} className="w-40" />
156
196
  ```
197
+
198
+ ### Invalid
199
+
200
+ `aria-invalid` 视觉与 Input 一致。
201
+
202
+ ```tsx
203
+ <InputNumber aria-invalid defaultValue={42} className="w-40" />
204
+ ```
205
+
206
+ ### Disabled
207
+
208
+ 禁用状态。
209
+
210
+ ```tsx
211
+ <InputNumber disabled defaultValue={42} className="w-40" />
212
+ ```
213
+
214
+ ### Controlled
215
+
216
+ 受控模式。
217
+
218
+ ```tsx
219
+ <div className="flex items-center gap-3">
220
+ <InputNumber value={val} onChange={setVal} className="w-40" />
221
+ <span className="text-xs text-muted-foreground">值: {String(val)}</span>
222
+ </div>
223
+ ```
@@ -126,7 +126,7 @@ export const NoControls: Story = {
126
126
 
127
127
  /** `changeOnBlur={false}` 让 onChange 在每次有效输入时触发,否则仅 blur/Enter 提交。 */
128
128
  export const ChangeOnBlur: Story = {
129
- render: function ChangeOnBlurDemo() {
129
+ render: () => {
130
130
  const [a, setA] = useState<number | string | null>(0);
131
131
  const [b, setB] = useState<number | string | null>(0);
132
132
  return (
@@ -175,7 +175,7 @@ export const Disabled: Story = {
175
175
 
176
176
  /** 受控模式。 */
177
177
  export const Controlled: Story = {
178
- render: function ControlledDemo() {
178
+ render: () => {
179
179
  const [val, setVal] = useState<number | string | null>(10);
180
180
  return (
181
181
  <div className="flex items-center gap-3">
@@ -66,7 +66,7 @@ function ItemGroupHeader({ className, ...props }: ItemGroupHeaderProps) {
66
66
  <div
67
67
  data-slot="item-group-header"
68
68
  className={cn(
69
- 'flex items-center px-3 py-2.5 text-sm font-medium border-b border-border',
69
+ 'flex items-center border-b border-border px-3 py-2.5 text-sm font-medium',
70
70
  className,
71
71
  )}
72
72
  {...props}
@@ -83,7 +83,7 @@ function ItemGroupFooter({ className, ...props }: ItemGroupFooterProps) {
83
83
  <div
84
84
  data-slot="item-group-footer"
85
85
  className={cn(
86
- 'flex items-center px-3 py-2.5 text-xs text-muted-foreground border-t border-border',
86
+ 'flex items-center border-t border-border px-3 py-2.5 text-xs text-muted-foreground',
87
87
  className,
88
88
  )}
89
89
  {...props}
@@ -94,7 +94,7 @@ function ItemGroupFooter({ className, ...props }: ItemGroupFooterProps) {
94
94
  // ─── Item ─────────────────────────────────────────────────────────────────────
95
95
 
96
96
  const itemVariants = cva(
97
- 'group/item flex w-full items-center text-xs outline-none transition-colors duration-100 focus-visible:ring-1 focus-visible:ring-ring',
97
+ 'group/item flex w-full items-center text-xs transition-colors duration-100 outline-none focus-visible:ring-1 focus-visible:ring-ring',
98
98
  {
99
99
  variants: {
100
100
  variant: {
@@ -103,7 +103,7 @@ const itemVariants = cva(
103
103
  muted: 'rounded-md bg-muted/50',
104
104
  },
105
105
  size: {
106
- default: 'gap-3 px-3 py-3',
106
+ default: 'gap-3 p-3',
107
107
  sm: 'gap-2.5 px-3 py-2.5',
108
108
  xs: 'gap-2 px-2.5 py-2',
109
109
  },
@@ -54,6 +54,14 @@
54
54
  </div>
55
55
  ```
56
56
 
57
+ ### Required
58
+
59
+ 必填标记
60
+
61
+ ```tsx
62
+ <Label required>用户名</Label>
63
+ ```
64
+
57
65
  ### RequiredWithInput
58
66
 
59
67
  必填 + 关联输入框
@@ -63,6 +63,21 @@
63
63
  </div>
64
64
  ```
65
65
 
66
+ ### AsyncSearch
67
+
68
+ 通过 `onSearch` 配合 `filterOption={false}` 实现服务端搜索。
69
+
70
+ ```tsx
71
+ <Mentions
72
+ options={opts}
73
+ loading={loading}
74
+ filterOption={false}
75
+ onSearch={handleSearch}
76
+ placeholder="输入 @ 远程搜索用户"
77
+ className="max-w-sm"
78
+ />
79
+ ```
80
+
66
81
  ### PlacementTop
67
82
 
68
83
  浮层向上展开,适合靠近底部的输入区。
@@ -132,7 +132,7 @@ function MenubarItem({
132
132
  data-inset={inset}
133
133
  data-variant={variant}
134
134
  className={cn(
135
- 'group/menubar-item relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-1 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!',
135
+ 'group/menubar-item relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-1 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!',
136
136
  className,
137
137
  )}
138
138
  {...props}
@@ -154,7 +154,7 @@ function MenubarCheckboxItem({
154
154
  data-slot="menubar-checkbox-item"
155
155
  data-inset={inset}
156
156
  className={cn(
157
- 'relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
157
+ 'relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0',
158
158
  className,
159
159
  )}
160
160
  checked={checked}
@@ -183,7 +183,7 @@ function MenubarRadioItem({
183
183
  data-slot="menubar-radio-item"
184
184
  data-inset={inset}
185
185
  className={cn(
186
- 'relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
186
+ 'relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
187
187
  className,
188
188
  )}
189
189
  {...props}
@@ -285,7 +285,7 @@ function MenubarSubContent({
285
285
  <MenubarPrimitive.SubContent
286
286
  data-slot="menubar-sub-content"
287
287
  className={cn(
288
- 'z-50 min-w-32 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 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',
288
+ 'z-50 min-w-32 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 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',
289
289
  className,
290
290
  )}
291
291
  {...props}
@@ -120,7 +120,7 @@ function NavigationMenuTrigger({
120
120
  >
121
121
  {children}{' '}
122
122
  <ChevronDownIcon
123
- className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180"
123
+ className="relative top-px ml-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180"
124
124
  aria-hidden="true"
125
125
  />
126
126
  </NavigationMenuPrimitive.Trigger>
@@ -136,7 +136,7 @@ function NavigationMenuContent({
136
136
  <NavigationMenuPrimitive.Content
137
137
  data-slot="navigation-menu-content"
138
138
  className={cn(
139
- 'top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 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 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95',
139
+ 'top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 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 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 md:absolute md:w-auto',
140
140
  className,
141
141
  )}
142
142
  {...props}
@@ -158,7 +158,7 @@ function NavigationMenuViewport({
158
158
  <NavigationMenuPrimitive.Viewport
159
159
  data-slot="navigation-menu-viewport"
160
160
  className={cn(
161
- 'origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 duration-100 md:w-(--radix-navigation-menu-viewport-width) data-[state=open]:animate-in data-[state=open]:zoom-in-90 data-[state=closed]:animate-out data-[state=closed]:zoom-out-90',
161
+ 'origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 duration-100 data-[state=closed]:animate-out data-[state=closed]:zoom-out-90 data-[state=open]:animate-in data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)',
162
162
  className,
163
163
  )}
164
164
  {...props}
@@ -198,7 +198,7 @@ function NavigationMenuIndicator({
198
198
  )}
199
199
  {...props}
200
200
  >
201
- <div className="relative top-3/5 h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
201
+ <div className="relative top-3/5 size-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
202
202
  </NavigationMenuPrimitive.Indicator>
203
203
  );
204
204
  }
@@ -102,7 +102,7 @@ const PageHeaderHeading = React.forwardRef<
102
102
  aria-label="返回"
103
103
  className={cn(
104
104
  'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors',
105
- 'hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
105
+ 'hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',
106
106
  )}
107
107
  >
108
108
  <ArrowLeft className="size-6" aria-hidden="true" />
@@ -166,7 +166,7 @@ const PageHeaderDescription = React.forwardRef<
166
166
  <p
167
167
  ref={ref}
168
168
  data-slot="page-header-description"
169
- className={cn('my-2 text-xs text-muted-foreground', className)}
169
+ className={cn('mt-2 text-xs text-muted-foreground', className)}
170
170
  {...props}
171
171
  />
172
172
  ));
@@ -23,6 +23,151 @@
23
23
 
24
24
  ## 示例
25
25
 
26
+ ### WithDescription
27
+
28
+ 标题 + 描述:标题下方附加一行描述文字。
29
+
30
+ ```tsx
31
+ <PageHeader>
32
+ <PageHeaderContent>
33
+ <div className="flex flex-col">
34
+ <PageHeaderHeading>
35
+ <PageHeaderTitle>页面标题</PageHeaderTitle>
36
+ </PageHeaderHeading>
37
+ <PageHeaderDescription>
38
+ 页面描述页面描述页面描述页面描述页面描述页面描述页面描述
39
+ </PageHeaderDescription>
40
+ </div>
41
+ </PageHeaderContent>
42
+ </PageHeader>
43
+ ```
44
+
45
+ ### OnlyBreadcrumb
46
+
47
+ 仅面包屑:只有面包屑导航,无标题。
48
+
49
+ ```tsx
50
+ <PageHeader>
51
+ <PageHeaderNav>
52
+ <Breadcrumb>
53
+ <BreadcrumbList>
54
+ <BreadcrumbItem>
55
+ <BreadcrumbLink href="#">首页</BreadcrumbLink>
56
+ </BreadcrumbItem>
57
+ <BreadcrumbSeparator />
58
+ <BreadcrumbItem>
59
+ <BreadcrumbLink href="#">实例列表</BreadcrumbLink>
60
+ </BreadcrumbItem>
61
+ <BreadcrumbSeparator />
62
+ <BreadcrumbItem>
63
+ <BreadcrumbPage>实例详情</BreadcrumbPage>
64
+ </BreadcrumbItem>
65
+ </BreadcrumbList>
66
+ </Breadcrumb>
67
+ </PageHeaderNav>
68
+ </PageHeader>
69
+ ```
70
+
71
+ ### WithBreadcrumb
72
+
73
+ 面包屑 + 标题:面包屑导航 + 标题,常见于二级页面。
74
+
75
+ ```tsx
76
+ <PageHeader>
77
+ <PageHeaderNav>
78
+ <Breadcrumb>
79
+ <BreadcrumbList>
80
+ <BreadcrumbItem>
81
+ <BreadcrumbLink href="#">首页</BreadcrumbLink>
82
+ </BreadcrumbItem>
83
+ <BreadcrumbSeparator />
84
+ <BreadcrumbItem>
85
+ <BreadcrumbLink href="#">实例列表</BreadcrumbLink>
86
+ </BreadcrumbItem>
87
+ <BreadcrumbSeparator />
88
+ <BreadcrumbItem>
89
+ <BreadcrumbPage>实例详情</BreadcrumbPage>
90
+ </BreadcrumbItem>
91
+ </BreadcrumbList>
92
+ </Breadcrumb>
93
+ </PageHeaderNav>
94
+ <PageHeaderContent>
95
+ <PageHeaderHeading>
96
+ <PageHeaderTitle>实例详情</PageHeaderTitle>
97
+ </PageHeaderHeading>
98
+ </PageHeaderContent>
99
+ </PageHeader>
100
+ ```
101
+
102
+ ### Full
103
+
104
+ 完整版页头:面包屑 + 帮助链接 + 返回 + 主图标 + 标题(含 ⓘ) + 描述 + 多操作。
105
+
106
+ ```tsx
107
+ <PageHeader>
108
+ <PageHeaderNav>
109
+ <Breadcrumb>
110
+ <BreadcrumbList>
111
+ <BreadcrumbItem>
112
+ <BreadcrumbLink href="#">首页</BreadcrumbLink>
113
+ </BreadcrumbItem>
114
+ <BreadcrumbSeparator />
115
+ <BreadcrumbItem>
116
+ <BreadcrumbLink href="#">实例列表</BreadcrumbLink>
117
+ </BreadcrumbItem>
118
+ <BreadcrumbSeparator />
119
+ <BreadcrumbItem>
120
+ <BreadcrumbPage>实例详情</BreadcrumbPage>
121
+ </BreadcrumbItem>
122
+ </BreadcrumbList>
123
+ </Breadcrumb>
124
+ <div className="flex items-center gap-4">
125
+ <a
126
+ href="#"
127
+ className="inline-flex items-center gap-1 text-muted-foreground hover:text-foreground"
128
+ >
129
+ <BookOpen className="size-3.5" aria-hidden="true" />
130
+ 帮助文档
131
+ </a>
132
+ <a href="#" className="text-muted-foreground hover:text-foreground">
133
+ 切换实例
134
+ </a>
135
+ </div>
136
+ </PageHeaderNav>
137
+ <PageHeaderContent>
138
+ <div className="flex flex-col">
139
+ <PageHeaderHeading goBack onBack={() => alert('返回')}>
140
+ <span
141
+ aria-hidden="true"
142
+ className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground"
143
+ >
144
+ <Boxes className="size-5" />
145
+ </span>
146
+ <PageHeaderTitle tooltip="该实例为生产环境,建议谨慎操作。">
147
+ 页面标题
148
+ </PageHeaderTitle>
149
+ <Tag variant="solid" color="success" size="lg">
150
+ 标签一
151
+ </Tag>
152
+ <Tag variant="solid" color="primary" size="lg">
153
+ 标签二
154
+ </Tag>
155
+ <Tag variant="solid" color="destructive" size="lg">
156
+ 标签三
157
+ </Tag>
158
+ </PageHeaderHeading>
159
+ <PageHeaderDescription>
160
+ 页面描述页面描述页面描述页面描述页面描述页面描述页面描述
161
+ </PageHeaderDescription>
162
+ </div>
163
+ <PageHeaderActions>
164
+ <Button variant="outline">普通按钮</Button>
165
+ <Button>主要按钮</Button>
166
+ </PageHeaderActions>
167
+ </PageHeaderContent>
168
+ </PageHeader>
169
+ ```
170
+
26
171
  ### WithStatistic
27
172
 
28
173
  带数据概览:右侧使用 Statistic 组件展示 KPI 指标。
@@ -93,7 +93,7 @@ function PageShell({
93
93
  // 受限容器(如 storybook `LayoutSandbox`)通过外级注入 `min-height: 0; height: 100%` 重置。
94
94
  // 说明:传入的 sidebar 如果是默认 collapsible="offcanvas",其内部使用 `position: fixed`、
95
95
  // 高度 100svh,适用于全屏页面;在受限容器中需使用 `collapsible="none"` 退出 fixed 布局。
96
- className={cn('!flex-col', className)}
96
+ className={cn('!h-svh !flex-col', className)}
97
97
  style={
98
98
  {
99
99
  '--sidebar-width': sidebarWidth,
@@ -103,11 +103,11 @@ function PageShell({
103
103
  {...props}
104
104
  >
105
105
  {header}
106
- <div className="flex w-full flex-1 min-h-0">
106
+ <div className="flex min-h-0 w-full flex-1">
107
107
  {sidebar}
108
108
  <SidebarInset
109
109
  data-slot="page-shell-content"
110
- className={cn('!min-h-0 overflow-y-auto', bgClass)}
110
+ className={cn('!min-h-0 overflow-hidden', bgClass)}
111
111
  >
112
112
  {children}
113
113
  </SidebarInset>
@@ -487,7 +487,7 @@ function Pagination({
487
487
  {/* 页码指示器 */}
488
488
  <span
489
489
  className={cn(
490
- 'ml-2 tabular-nums text-muted-foreground',
490
+ 'ml-2 text-muted-foreground tabular-nums',
491
491
  sizeClass[size],
492
492
  )}
493
493
  >