@teamix-evo/ui 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +19 -19
  2. package/src/components/alert/index.tsx +1 -1
  3. package/src/components/alert-dialog/index.tsx +17 -24
  4. package/src/components/alert-dialog/meta.md +102 -8
  5. package/src/components/alert-dialog/stories.tsx +117 -7
  6. package/src/components/avatar/index.tsx +1 -1
  7. package/src/components/badge/index.tsx +1 -1
  8. package/src/components/button/index.tsx +1 -29
  9. package/src/components/button/meta.md +24 -13
  10. package/src/components/button/stories.tsx +21 -12
  11. package/src/components/button-group/meta.md +6 -9
  12. package/src/components/button-group/stories.tsx +2 -6
  13. package/src/components/calendar/index.tsx +12 -7
  14. package/src/components/cascader-select/index.tsx +1 -1
  15. package/src/components/checkbox/index.tsx +1 -1
  16. package/src/components/combobox/index.tsx +54 -10
  17. package/src/components/combobox/meta.md +3 -5
  18. package/src/components/combobox/stories.tsx +104 -25
  19. package/src/components/data-table/stories.tsx +4 -1
  20. package/src/components/date-picker/index.tsx +25 -2
  21. package/src/components/field/index.tsx +1 -1
  22. package/src/components/filter-bar/index.tsx +1 -1
  23. package/src/components/float-button/meta.md +3 -15
  24. package/src/components/icon/index.tsx +3 -4
  25. package/src/components/icon/meta.md +1 -2
  26. package/src/components/input/index.tsx +10 -2
  27. package/src/components/input-group/index.tsx +3 -3
  28. package/src/components/input-group/meta.md +15 -0
  29. package/src/components/input-group/stories.tsx +14 -0
  30. package/src/components/input-ip/index.tsx +1 -1
  31. package/src/components/input-number/index.tsx +5 -5
  32. package/src/components/item/meta.md +11 -11
  33. package/src/components/radio-group/index.tsx +1 -1
  34. package/src/components/rate/index.tsx +3 -3
  35. package/src/components/select/index.tsx +2 -2
  36. package/src/components/skeleton/index.tsx +1 -1
  37. package/src/components/skeleton/meta.md +6 -6
  38. package/src/components/skeleton/stories.tsx +8 -8
  39. package/src/components/slider/index.tsx +27 -1
  40. package/src/components/sonner/index.tsx +43 -40
  41. package/src/components/sonner/meta.md +84 -68
  42. package/src/components/sonner/stories.tsx +122 -83
  43. package/src/components/spinner/index.tsx +170 -0
  44. package/src/components/spinner/meta.md +27 -1
  45. package/src/components/spinner/stories.tsx +23 -0
  46. package/src/components/steps/index.tsx +5 -1
  47. package/src/components/switch/index.tsx +1 -1
  48. package/src/components/tag/index.tsx +14 -0
  49. package/src/components/tag/meta.md +1 -0
  50. package/src/components/tag/stories.tsx +13 -0
  51. package/src/components/textarea/index.tsx +1 -1
  52. package/src/components/textarea/stories.tsx +1 -1
  53. package/src/components/time-picker/index.tsx +3 -1
  54. package/src/components/toggle/index.tsx +1 -1
  55. package/src/components/tooltip/index.tsx +5 -1
  56. package/src/components/tooltip/meta.md +13 -28
  57. package/src/components/tooltip/stories.tsx +11 -28
  58. package/src/components/tree-select/index.tsx +1 -1
  59. package/LICENSE +0 -21
@@ -19,6 +19,174 @@ import { Loader2Icon } from 'lucide-react';
19
19
 
20
20
  import { cn } from '@/lib/utils';
21
21
 
22
+ /* ────────────────────────────────────────────────────────────────────────── */
23
+ /* SpinnerDots keyframes(singleton 注入) */
24
+ /* ────────────────────────────────────────────────────────────────────────── */
25
+
26
+ const SPINNER_DOTS_STYLE_ID = '__spinner-dots-keyframes__';
27
+
28
+ /**
29
+ * 确保 keyframes 在 DOM 中只注入一次。
30
+ * 双层动画架构(对齐 cloud-design fusion-reactor):
31
+ * - 外层容器:阶梯式旋转(快速转 90° → 停住),实现弧线位移
32
+ * - 每个点:径向脉动(边缘 → 中心 → 边缘),实现汇聚与发散
33
+ */
34
+ function ensureDotsKeyframes() {
35
+ if (typeof document === 'undefined') return;
36
+ if (document.getElementById(SPINNER_DOTS_STYLE_ID)) return;
37
+ const style = document.createElement('style');
38
+ style.id = SPINNER_DOTS_STYLE_ID;
39
+ style.textContent = `
40
+ @keyframes __sdots-orbit__ {
41
+ 0% { transform: rotate(0deg); }
42
+ 5% { transform: rotate(90deg); }
43
+ 25% { transform: rotate(90deg); }
44
+ 30% { transform: rotate(180deg); }
45
+ 50% { transform: rotate(180deg); }
46
+ 55% { transform: rotate(270deg); }
47
+ 75% { transform: rotate(270deg); }
48
+ 80% { transform: rotate(360deg); }
49
+ 100% { transform: rotate(360deg); }
50
+ }
51
+ @keyframes __sdot-x__ {
52
+ 25% { left: 0; }
53
+ 45%, 50% { left: calc(50% - 4px); }
54
+ 90% { left: 0; }
55
+ }
56
+ @keyframes __sdot-y__ {
57
+ 25% { top: 0; }
58
+ 45%, 50% { top: calc(50% - 4px); }
59
+ 90% { top: 0; }
60
+ }
61
+ @keyframes __sdot-xr__ {
62
+ 25% { right: 0; }
63
+ 45%, 50% { right: calc(50% - 4px); }
64
+ 90% { right: 0; }
65
+ }
66
+ @keyframes __sdot-yr__ {
67
+ 25% { bottom: 0; }
68
+ 45%, 50% { bottom: calc(50% - 4px); }
69
+ 90% { bottom: 0; }
70
+ }
71
+ `;
72
+ document.head.appendChild(style);
73
+ }
74
+
75
+ /* ────────────────────────────────────────────────────────────────────────── */
76
+ /* SpinnerDots(四点轨道位移原子) */
77
+ /* ────────────────────────────────────────────────────────────────────────── */
78
+
79
+ /** 容器尺寸(点固定 8×8px) */
80
+ const DOTS_SIZES = {
81
+ sm: { container: 'size-6' },
82
+ md: { container: 'size-7' },
83
+ lg: { container: 'size-8' },
84
+ } as const;
85
+
86
+ export interface SpinnerDotsProps
87
+ extends Omit<React.ComponentProps<'span'>, 'children'> {
88
+ /** 三档尺寸,控制点的扩散半径 */
89
+ size?: 'sm' | 'md' | 'lg';
90
+ /** sr-only 读屏文本 */
91
+ label?: string;
92
+ }
93
+
94
+ /**
95
+ * 四点轨道位移旋转指示器(对齐 cloud-design fusion-reactor 动效)。
96
+ * 4 个主题色(primary)圆点 8×8px,透明度 1 / 0.8 / 0.6 / 0.2,分居左上右下。
97
+ * 双层动画:外层容器阶梯旋转(弧线位移)+ 每个点径向脉动(汇聚/发散)。
98
+ */
99
+ function SpinnerDots({
100
+ size = 'md',
101
+ label = '加载中',
102
+ className,
103
+ ...props
104
+ }: SpinnerDotsProps) {
105
+ React.useEffect(() => {
106
+ ensureDotsKeyframes();
107
+ }, []);
108
+
109
+ const sizeToken = DOTS_SIZES[size ?? 'md'];
110
+
111
+ const dotBase: React.CSSProperties = {
112
+ position: 'absolute',
113
+ width: '8px',
114
+ height: '8px',
115
+ borderRadius: '50%',
116
+ background: 'var(--color-primary)',
117
+ margin: 'auto',
118
+ animationDuration: '1.4s',
119
+ animationTimingFunction: 'ease-in-out',
120
+ animationIterationCount: 'infinite',
121
+ };
122
+
123
+ return (
124
+ <span
125
+ data-slot="spinner-dots-root"
126
+ role="status"
127
+ aria-live="polite"
128
+ className={cn(
129
+ 'inline-flex shrink-0 items-center justify-center',
130
+ className,
131
+ )}
132
+ {...props}
133
+ >
134
+ {/* 外层容器:阶梯旋转(5.6s = 4 × 1.4s) */}
135
+ <span
136
+ data-slot="spinner-dots"
137
+ className={cn('relative inline-block', sizeToken.container)}
138
+ style={{ animation: '__sdots-orbit__ 5.6s linear infinite' }}
139
+ >
140
+ {/* 左 · opacity 1 — 径向向右脉动 (nextVectorDotsX) */}
141
+ <span
142
+ style={{
143
+ ...dotBase,
144
+ top: 0,
145
+ bottom: 0,
146
+ left: 0,
147
+ opacity: 1,
148
+ animationName: '__sdot-x__',
149
+ }}
150
+ />
151
+ {/* 上 · opacity 0.8 — 径向向下脉动 (nextVectorDotsY) */}
152
+ <span
153
+ style={{
154
+ ...dotBase,
155
+ left: 0,
156
+ right: 0,
157
+ top: 0,
158
+ opacity: 0.8,
159
+ animationName: '__sdot-y__',
160
+ }}
161
+ />
162
+ {/* 右 · opacity 0.6 — 径向向左脉动 (nextVectorDotsXR) */}
163
+ <span
164
+ style={{
165
+ ...dotBase,
166
+ top: 0,
167
+ bottom: 0,
168
+ right: 0,
169
+ opacity: 0.6,
170
+ animationName: '__sdot-xr__',
171
+ }}
172
+ />
173
+ {/* 下 · opacity 0.2 — 径向向上脉动 (nextVectorDotsYR) */}
174
+ <span
175
+ style={{
176
+ ...dotBase,
177
+ left: 0,
178
+ right: 0,
179
+ bottom: 0,
180
+ opacity: 0.2,
181
+ animationName: '__sdot-yr__',
182
+ }}
183
+ />
184
+ </span>
185
+ {label ? <span className="sr-only">{label}</span> : null}
186
+ </span>
187
+ );
188
+ }
189
+
22
190
  /* ────────────────────────────────────────────────────────────────────────── */
23
191
  /* Spinner(原子) */
24
192
  /* ────────────────────────────────────────────────────────────────────────── */
@@ -72,6 +240,7 @@ function Spinner({
72
240
  data-size={size}
73
241
  data-tone={tone ?? 'default'}
74
242
  aria-hidden="true"
243
+ strokeWidth={1.5}
75
244
  className={cn(spinnerVariants({ size, tone }), className)}
76
245
  {...props}
77
246
  />
@@ -327,6 +496,7 @@ function SpinnerFullscreen({
327
496
 
328
497
  export {
329
498
  Spinner,
499
+ SpinnerDots,
330
500
  SpinnerTip,
331
501
  SpinnerOverlay,
332
502
  SpinnerOverlayMask,
@@ -1,4 +1,4 @@
1
- # Spinner
1
+ # SpinnerDots
2
2
 
3
3
  > 本文件由 `pnpm gen:meta` 自动生成,**请勿手动编辑**。
4
4
 
@@ -19,6 +19,8 @@ shadcn v4 composable 套件:原子 Spinner + SpinnerTip 文案 + SpinnerOverla
19
19
 
20
20
  | 名称 | 类型 | 默认值 | 必填 | 说明 |
21
21
  | --- | --- | --- | --- | --- |
22
+ | `size` | `'sm' \| 'md' \| 'lg'` | – | – | 三档尺寸,控制点的扩散半径 |
23
+ | `label` | `string` | – | – | sr-only 读屏文本 |
22
24
  | `size` | `"xs" \| "sm" \| "md" \| "lg"` | – | – | – |
23
25
  | `tone` | `"default" \| "primary" \| "inverse" \| "muted"` | `"default"` | – | – |
24
26
  | `label` | `string` | – | – | sr-only 读屏文本,默认 `加载中`;如父级已有可见 tip 可置空 |
@@ -146,6 +148,30 @@ tip 位置:right(行内) 与 bottom(堆叠)。
146
148
  </SpinnerOverlay>
147
149
  ```
148
150
 
151
+ ### DotsDefault
152
+
153
+ SpinnerDots 三档尺寸:四点沿弧线轨道位移动画。
154
+
155
+ ```tsx
156
+ <div className="flex items-center gap-6">
157
+ <SpinnerDots size="sm" />
158
+ <SpinnerDots size="md" />
159
+ <SpinnerDots size="lg" />
160
+ </div>
161
+ ```
162
+
163
+ ### OverlayWithDots
164
+
165
+ SpinnerDots 作为 SpinnerOverlay 的自定义 indicator。
166
+
167
+ ```tsx
168
+ <SpinnerOverlay indicator={<SpinnerDots size="md" />} tip="加载中...">
169
+ <div className="h-20 w-48 rounded border border-border p-4 text-xs">
170
+ 四点轨道动画
171
+ </div>
172
+ </SpinnerOverlay>
173
+ ```
174
+
149
175
  ### InlineVsBlock
150
176
 
151
177
  inline(默认)与 block 通栏模式对比。
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import * as React from 'react';
3
3
  import {
4
4
  Spinner,
5
+ SpinnerDots,
5
6
  SpinnerTip,
6
7
  SpinnerOverlay,
7
8
  SpinnerFullscreen,
@@ -172,6 +173,28 @@ export const Fullscreen: Story = {
172
173
  },
173
174
  };
174
175
 
176
+ /** SpinnerDots 三档尺寸:四点沿弧线轨道位移动画。 */
177
+ export const DotsDefault: Story = {
178
+ render: () => (
179
+ <div className="flex items-center gap-6">
180
+ <SpinnerDots size="sm" />
181
+ <SpinnerDots size="md" />
182
+ <SpinnerDots size="lg" />
183
+ </div>
184
+ ),
185
+ };
186
+
187
+ /** SpinnerDots 作为 SpinnerOverlay 的自定义 indicator。 */
188
+ export const OverlayWithDots: Story = {
189
+ render: () => (
190
+ <SpinnerOverlay indicator={<SpinnerDots size="md" />} tip="加载中...">
191
+ <div className="h-20 w-48 rounded border border-border p-4 text-xs">
192
+ 四点轨道动画
193
+ </div>
194
+ </SpinnerOverlay>
195
+ ),
196
+ };
197
+
175
198
  /** inline(默认)与 block 通栏模式对比。 */
176
199
  export const InlineVsBlock: Story = {
177
200
  render: () => (
@@ -135,6 +135,7 @@ const ArrowSteps = React.forwardRef<
135
135
  <div
136
136
  ref={ref}
137
137
  data-slot="steps"
138
+ data-shape="arrow"
138
139
  className={cn('flex flex-row', className)}
139
140
  {...props}
140
141
  >
@@ -150,6 +151,7 @@ const ArrowSteps = React.forwardRef<
150
151
  <button
151
152
  key={i}
152
153
  type="button"
154
+ data-status={s}
153
155
  tabIndex={clickable ? 0 : -1}
154
156
  onClick={clickable ? () => onChange!(i) : undefined}
155
157
  aria-disabled={isDisabled || undefined}
@@ -259,6 +261,7 @@ function CircleDotSteps({
259
261
  <div
260
262
  ref={forwardedRef}
261
263
  data-slot="steps"
264
+ data-shape={shape}
262
265
  className={cn(
263
266
  'flex',
264
267
  isVertical ? 'flex-col gap-0' : 'flex-row items-start gap-0',
@@ -286,7 +289,7 @@ function CircleDotSteps({
286
289
  ? null
287
290
  : item.icon ??
288
291
  (s === 'finish' ? (
289
- <Check className={iconSize} />
292
+ <Check className={iconSize} strokeWidth={3} />
290
293
  ) : s === 'error' ? (
291
294
  <X className={iconSize} />
292
295
  ) : (
@@ -296,6 +299,7 @@ function CircleDotSteps({
296
299
  const indicatorBtn = (
297
300
  <button
298
301
  type="button"
302
+ data-status={s}
299
303
  tabIndex={clickable ? 0 : -1}
300
304
  onClick={clickable ? () => onChange!(i) : undefined}
301
305
  className={cn(
@@ -23,7 +23,7 @@ import { cn } from '@/lib/utils';
23
23
  // ─── cva ────────────────────────────────────────────────────────────────────
24
24
 
25
25
  const switchVariants = cva(
26
- 'peer group/switch relative inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[loading=true]:pointer-events-none data-[loading=true]:opacity-70',
26
+ 'peer group/switch relative inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-ring/20 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[loading=true]:pointer-events-none data-[loading=true]:opacity-70',
27
27
  {
28
28
  variants: {
29
29
  size: {
@@ -263,6 +263,12 @@ export interface TagClosableConfig {
263
263
  closeArea?: 'tag' | 'tail';
264
264
  /** 关闭按钮 aria-label(默认 `'Close'`)。 */
265
265
  'aria-label'?: string;
266
+ /**
267
+ * hover 时才显示关闭按钮,默认隐藏。
268
+ * 效果:hover 后关闭按钮淡入,文字向左微移让出空间。
269
+ * @default false
270
+ */
271
+ hoverReveal?: boolean;
266
272
  }
267
273
 
268
274
  export interface TagProps
@@ -352,6 +358,7 @@ function Tag({
352
358
  const closableConfig = asChild ? null : normalizeClosable(closable);
353
359
  const closeArea = closableConfig?.closeArea ?? 'tail';
354
360
  const closeAriaLabel = closableConfig?.['aria-label'] ?? 'Close';
361
+ const hoverReveal = closableConfig?.hoverReveal ?? false;
355
362
 
356
363
  const triggerClose = (
357
364
  e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
@@ -380,6 +387,7 @@ function Tag({
380
387
  const closeButton = closableConfig ? (
381
388
  <button
382
389
  type="button"
390
+ data-slot="tag-close"
383
391
  aria-label={closeAriaLabel}
384
392
  disabled={disabled}
385
393
  onClick={(e) => {
@@ -397,6 +405,8 @@ function Tag({
397
405
  '-mr-0.5 inline-flex shrink-0 cursor-pointer rounded-sm opacity-60 transition-opacity hover:opacity-100',
398
406
  'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
399
407
  disabled && 'cursor-not-allowed opacity-30 hover:opacity-30',
408
+ hoverReveal &&
409
+ 'mr-0 overflow-hidden w-0 opacity-0 transition-[width,opacity] duration-150 group-hover/tag:w-3 group-hover/tag:opacity-60',
400
410
  )}
401
411
  >
402
412
  {renderCloseIcon(closableConfig.closeIcon)}
@@ -427,6 +437,8 @@ function Tag({
427
437
  closeArea === 'tag' &&
428
438
  !disabled &&
429
439
  'cursor-pointer',
440
+ hoverReveal &&
441
+ 'group/tag transition-[padding-inline] duration-150 hover:!px-1.5',
430
442
  className,
431
443
  )}
432
444
  style={{ color: tone, ...style }}
@@ -530,6 +542,8 @@ function Tag({
530
542
  'bg-[var(--tag-bg)] hover:bg-[var(--tag-bg-hover)]',
531
543
  disabled && 'cursor-not-allowed opacity-50',
532
544
  closableConfig && closeArea === 'tag' && !disabled && 'cursor-pointer',
545
+ hoverReveal &&
546
+ 'group/tag transition-[padding-inline] duration-150 hover:!px-1.5',
533
547
  className,
534
548
  )}
535
549
  style={{ ...composedStyle, ...style }}
@@ -38,6 +38,7 @@ closeArea / aria-label),`onClose(e)` 调用 `e.preventDefault()` 即可
38
38
  | `visible` | `boolean` | – | – | 受控可见性。 |
39
39
  | `defaultVisible` | `boolean` | – | – | 非受控初始可见性,默认 `true`。 |
40
40
  | `disabled` | `boolean` | – | – | 禁用态:灰显 + cursor-not-allowed,点击 / 关闭均无效。 |
41
+ | `borderless` | `boolean` | – | – | 去除边框显示。在 OP 主题下,带 icon 的分类 Tag 使用此样式: 仅保留浅色背景 + 彩色文字 + 前缀图标,无边框线。 |
41
42
  | `asChild` | `boolean` | – | – | Slot 模式渲染为子元素(如 `<a>`);此模式下不内置渲染 icon 与关闭按钮。 |
42
43
  | `gap` | `'sm' \| 'md' \| 'lg'` | – | – | 间距档位:`sm` 6px / `md` 8px(默认) / `lg` 12px。 |
43
44
  | `checked` | `boolean` | – | ✓ | 是否选中(受控)。 |
@@ -192,6 +192,19 @@ export const Closable: Story = {
192
192
  </CheckableTag>
193
193
  </TagGroup>
194
194
  </div>
195
+ <div>
196
+ <h4 className="mb-2 text-xs text-muted-foreground">
197
+ hoverReveal(hover 时才显示 ×)
198
+ </h4>
199
+ <TagGroup>
200
+ <Tag closable={{ hoverReveal: true }} color="primary">
201
+ hover me
202
+ </Tag>
203
+ <Tag closable={{ hoverReveal: true }} color="success">
204
+ hover me too
205
+ </Tag>
206
+ </TagGroup>
207
+ </div>
195
208
  </div>
196
209
  );
197
210
  },
@@ -17,7 +17,7 @@ import { cn } from '@/lib/utils';
17
17
  // ─── Textarea cva ────────────────────────────────────────────────────────────
18
18
 
19
19
  const textareaVariants = cva(
20
- 'flex w-full cursor-text rounded-md border border-input bg-transparent font-normal field-sizing-content transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
20
+ 'flex w-full cursor-text rounded-md border border-input bg-transparent font-normal field-sizing-content transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
21
21
  {
22
22
  variants: {
23
23
  size: {
@@ -27,7 +27,7 @@ export default meta;
27
27
  type Story = StoryObj<typeof meta>;
28
28
 
29
29
  export const Default: Story = {
30
- args: { placeholder: '请输入备注信息...' },
30
+ args: { placeholder: '请输入备注信息...', className: 'w-80 resize-none' },
31
31
  };
32
32
 
33
33
  /** 可以通过设置 `size` 属性控制多行输入框的尺寸。 */
@@ -71,7 +71,7 @@ export function inspectFormat(format: string) {
71
71
  const triggerVariants = cva(
72
72
  [
73
73
  'flex cursor-pointer items-center gap-2 rounded-md border border-input bg-transparent transition-colors',
74
- 'hover:border-ring focus-within:border-ring focus-within:ring-1 focus-within:ring-ring',
74
+ 'hover:border-ring focus-within:border-ring focus-within:ring-ring/20',
75
75
  '[&:has(input:disabled)]:pointer-events-none [&:has(input:disabled)]:cursor-not-allowed [&:has(input:disabled)]:bg-input/50 [&:has(input:disabled)]:opacity-50',
76
76
  'aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
77
77
  ].join(' '),
@@ -509,6 +509,7 @@ function TimePicker({
509
509
  <Clock
510
510
  data-slot="time-picker-icon"
511
511
  aria-hidden
512
+ strokeWidth={1.5}
512
513
  className="size-4 shrink-0 text-muted-foreground"
513
514
  />
514
515
  )}
@@ -825,6 +826,7 @@ function TimeRangePicker({
825
826
  <Clock
826
827
  data-slot="time-range-picker-icon"
827
828
  aria-hidden
829
+ strokeWidth={1.5}
828
830
  className="size-4 shrink-0 text-muted-foreground"
829
831
  />
830
832
  )}
@@ -15,7 +15,7 @@ import { Toggle as TogglePrimitive } from 'radix-ui';
15
15
  import { cn } from '@/lib/utils';
16
16
 
17
17
  const toggleVariants = cva(
18
- 'group/toggle inline-flex items-center justify-center gap-1 rounded-md text-xs font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted cursor-pointer [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
18
+ 'group/toggle inline-flex items-center justify-center gap-1 rounded-md text-xs font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted cursor-pointer [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
19
19
  {
20
20
  variants: {
21
21
  variant: {
@@ -8,7 +8,7 @@
8
8
  * - 截断文本悬停展示完整内容
9
9
  * - 为操作提供额外上下文信息
10
10
  *
11
- * 组合结构:TooltipProvider > Tooltip > TooltipTrigger + TooltipContent (> TooltipArrow?)
11
+ * 组合结构:TooltipProvider > Tooltip > TooltipTrigger + TooltipContent (默认带 Arrow,showArrow={false} 可隐藏)
12
12
  */
13
13
 
14
14
  'use client';
@@ -86,6 +86,8 @@ export interface TooltipContentProps
86
86
  variant?: 'default' | 'inverse';
87
87
  /** 浮层渲染容器,透传给 Portal。 @default document.body */
88
88
  container?: HTMLElement | null;
89
+ /** 是否显示箭头。 @default true */
90
+ showArrow?: boolean;
89
91
  }
90
92
 
91
93
  function TooltipContent({
@@ -93,6 +95,7 @@ function TooltipContent({
93
95
  sideOffset = 4,
94
96
  variant = 'default',
95
97
  container,
98
+ showArrow = true,
96
99
  children,
97
100
  ...props
98
101
  }: TooltipContentProps) {
@@ -106,6 +109,7 @@ function TooltipContent({
106
109
  {...props}
107
110
  >
108
111
  {children}
112
+ {showArrow && <TooltipArrow />}
109
113
  </TooltipPrimitive.Content>
110
114
  </TooltipPrimitive.Portal>
111
115
  );
@@ -12,7 +12,7 @@
12
12
  - 图标按钮需要文字解释
13
13
  - 截断文本悬停展示完整内容
14
14
  - 为操作提供额外上下文信息
15
- - 组合结构:TooltipProvider > Tooltip > TooltipTrigger + TooltipContent (> TooltipArrow?)
15
+ - 组合结构:TooltipProvider > Tooltip > TooltipTrigger + TooltipContent (默认带 Arrow,showArrow={false} 可隐藏)
16
16
 
17
17
  ## Props
18
18
 
@@ -25,6 +25,7 @@
25
25
  | `disableHoverableContent` | `boolean` | `false` | – | 是否禁用鼠标进入 Tooltip 内保持显示。 |
26
26
  | `variant` | `'default' \| 'inverse'` | `'default'` | – | 视觉变体:default = 亮底;inverse = 反色(深底白字)。 |
27
27
  | `container` | `HTMLElement \| null` | `document.body` | – | 浮层渲染容器,透传给 Portal。 |
28
+ | `showArrow` | `boolean` | `true` | – | 是否显示箭头。 |
28
29
 
29
30
  ## 示例
30
31
 
@@ -86,29 +87,25 @@
86
87
  </TooltipProvider>
87
88
  ```
88
89
 
89
- ### WithArrow
90
+ ### WithoutArrow
90
91
 
91
- 显示尖角箭头。
92
+ 隐藏箭头:showArrow={false}。
92
93
 
93
94
  ```tsx
94
95
  <TooltipProvider>
95
96
  <div className="flex items-center gap-4">
96
97
  <Tooltip>
97
98
  <TooltipTrigger asChild>
98
- <Button>带箭头</Button>
99
+ <Button>无箭头</Button>
99
100
  </TooltipTrigger>
100
- <TooltipContent>
101
- 提示内容
102
- <TooltipArrow />
103
- </TooltipContent>
101
+ <TooltipContent showArrow={false}>提示内容</TooltipContent>
104
102
  </Tooltip>
105
103
  <Tooltip>
106
104
  <TooltipTrigger asChild>
107
- <Button>反色 + 箭头</Button>
105
+ <Button>反色无箭头</Button>
108
106
  </TooltipTrigger>
109
- <TooltipContent variant="inverse">
107
+ <TooltipContent variant="inverse" showArrow={false}>
110
108
  深色提示
111
- <TooltipArrow />
112
109
  </TooltipContent>
113
110
  </Tooltip>
114
111
  </div>
@@ -117,7 +114,7 @@
117
114
 
118
115
  ### ArrowSides
119
116
 
120
- 四个方向的箭头 — 箭头随 side 自动朝向 trigger
117
+ 四个方向的箭头 — 箭头随 side 自动朝向 trigger(默认即带箭头)。
121
118
 
122
119
  ```tsx
123
120
  <TooltipProvider delayDuration={0}>
@@ -126,37 +123,25 @@
126
123
  <TooltipTrigger asChild>
127
124
  <Button>side=top</Button>
128
125
  </TooltipTrigger>
129
- <TooltipContent side="top">
130
- top
131
- <TooltipArrow />
132
- </TooltipContent>
126
+ <TooltipContent side="top">top</TooltipContent>
133
127
  </Tooltip>
134
128
  <Tooltip open>
135
129
  <TooltipTrigger asChild>
136
130
  <Button>side=bottom</Button>
137
131
  </TooltipTrigger>
138
- <TooltipContent side="bottom">
139
- bottom
140
- <TooltipArrow />
141
- </TooltipContent>
132
+ <TooltipContent side="bottom">bottom</TooltipContent>
142
133
  </Tooltip>
143
134
  <Tooltip open>
144
135
  <TooltipTrigger asChild>
145
136
  <Button>side=left</Button>
146
137
  </TooltipTrigger>
147
- <TooltipContent side="left">
148
- left
149
- <TooltipArrow />
150
- </TooltipContent>
138
+ <TooltipContent side="left">left</TooltipContent>
151
139
  </Tooltip>
152
140
  <Tooltip open>
153
141
  <TooltipTrigger asChild>
154
142
  <Button>side=right</Button>
155
143
  </TooltipTrigger>
156
- <TooltipContent side="right">
157
- right
158
- <TooltipArrow />
159
- </TooltipContent>
144
+ <TooltipContent side="right">right</TooltipContent>
160
145
  </Tooltip>
161
146
  </div>
162
147
  </TooltipProvider>