@teamix-evo/ui 0.1.1 → 0.3.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 (295) hide show
  1. package/README.md +184 -184
  2. package/manifest.json +680 -492
  3. package/package.json +20 -10
  4. package/src/components/accordion/accordion.meta.md +5 -4
  5. package/src/components/accordion/accordion.stories.tsx +14 -9
  6. package/src/components/accordion/accordion.tsx +104 -8
  7. package/src/components/affix/affix.meta.md +20 -2
  8. package/src/components/affix/affix.stories.tsx +102 -25
  9. package/src/components/affix/affix.tsx +79 -9
  10. package/src/components/alert/alert.meta.md +44 -13
  11. package/src/components/alert/alert.stories.tsx +66 -21
  12. package/src/components/alert/alert.tsx +81 -34
  13. package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
  14. package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
  15. package/src/components/alert-dialog/alert-dialog.tsx +60 -13
  16. package/src/components/anchor/anchor.meta.md +8 -3
  17. package/src/components/anchor/anchor.stories.tsx +3 -3
  18. package/src/components/anchor/anchor.tsx +2 -2
  19. package/src/components/app/app.meta.md +9 -4
  20. package/src/components/app/app.stories.tsx +9 -7
  21. package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
  22. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
  23. package/src/components/auto-complete/auto-complete.meta.md +14 -6
  24. package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
  25. package/src/components/auto-complete/auto-complete.tsx +119 -71
  26. package/src/components/avatar/avatar.meta.md +6 -7
  27. package/src/components/avatar/avatar.stories.tsx +21 -3
  28. package/src/components/avatar/avatar.tsx +24 -23
  29. package/src/components/badge/badge.meta.md +10 -9
  30. package/src/components/badge/badge.stories.tsx +2 -2
  31. package/src/components/badge/badge.tsx +9 -15
  32. package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
  33. package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
  34. package/src/components/breadcrumb/breadcrumb.tsx +22 -8
  35. package/src/components/button/button.meta.md +258 -21
  36. package/src/components/button/button.stories.tsx +549 -41
  37. package/src/components/button/button.tsx +335 -33
  38. package/src/components/button/demo/as-child.tsx +24 -0
  39. package/src/components/button/demo/basic.tsx +8 -0
  40. package/src/components/button/demo/block.tsx +16 -0
  41. package/src/components/button/demo/loading.tsx +19 -0
  42. package/src/components/button/demo/shapes.tsx +18 -0
  43. package/src/components/button/demo/sizes.tsx +19 -0
  44. package/src/components/button/demo/variants.tsx +19 -0
  45. package/src/components/button/demo/with-icon.tsx +20 -0
  46. package/src/components/calendar/calendar.meta.md +13 -3
  47. package/src/components/calendar/calendar.stories.tsx +6 -6
  48. package/src/components/calendar/calendar.tsx +73 -8
  49. package/src/components/card/card.meta.md +27 -5
  50. package/src/components/card/card.stories.tsx +42 -3
  51. package/src/components/card/card.tsx +146 -63
  52. package/src/components/carousel/carousel.meta.md +4 -3
  53. package/src/components/carousel/carousel.stories.tsx +11 -6
  54. package/src/components/cascader/cascader.meta.md +47 -17
  55. package/src/components/cascader/cascader.stories.tsx +22 -10
  56. package/src/components/cascader/cascader.tsx +428 -85
  57. package/src/components/checkbox/checkbox.meta.md +75 -7
  58. package/src/components/checkbox/checkbox.stories.tsx +161 -3
  59. package/src/components/checkbox/checkbox.tsx +77 -9
  60. package/src/components/collapsible/collapsible.meta.md +14 -6
  61. package/src/components/collapsible/collapsible.stories.tsx +10 -2
  62. package/src/components/collapsible/collapsible.tsx +93 -6
  63. package/src/components/color-picker/color-picker.meta.md +12 -7
  64. package/src/components/color-picker/color-picker.stories.tsx +86 -7
  65. package/src/components/color-picker/color-picker.tsx +20 -9
  66. package/src/components/command/command.meta.md +29 -13
  67. package/src/components/command/command.stories.tsx +4 -4
  68. package/src/components/command/command.tsx +19 -8
  69. package/src/components/context-menu/context-menu.meta.md +11 -8
  70. package/src/components/context-menu/context-menu.stories.tsx +11 -3
  71. package/src/components/context-menu/context-menu.tsx +21 -8
  72. package/src/components/data-table/data-table.meta.md +6 -5
  73. package/src/components/data-table/data-table.stories.tsx +13 -6
  74. package/src/components/data-table/data-table.tsx +2 -2
  75. package/src/components/date-picker/date-picker.meta.md +88 -19
  76. package/src/components/date-picker/date-picker.stories.tsx +55 -5
  77. package/src/components/date-picker/date-picker.tsx +1489 -91
  78. package/src/components/descriptions/descriptions.meta.md +10 -5
  79. package/src/components/descriptions/descriptions.stories.tsx +3 -3
  80. package/src/components/descriptions/descriptions.tsx +22 -14
  81. package/src/components/dialog/dialog.meta.md +76 -13
  82. package/src/components/dialog/dialog.stories.tsx +182 -20
  83. package/src/components/dialog/dialog.tsx +67 -15
  84. package/src/components/dialog/imperative.tsx +252 -0
  85. package/src/components/drawer/drawer.meta.md +33 -34
  86. package/src/components/drawer/drawer.stories.tsx +29 -12
  87. package/src/components/drawer/drawer.tsx +22 -113
  88. package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
  89. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
  90. package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
  91. package/src/components/ellipsis/ellipsis.meta.md +87 -0
  92. package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
  93. package/src/components/ellipsis/ellipsis.tsx +153 -0
  94. package/src/components/empty/empty.meta.md +9 -4
  95. package/src/components/empty/empty.stories.tsx +4 -4
  96. package/src/components/empty/empty.tsx +10 -3
  97. package/src/components/field/field.meta.md +47 -9
  98. package/src/components/field/field.stories.tsx +385 -5
  99. package/src/components/field/field.tsx +263 -35
  100. package/src/components/filter-bar/filter-bar.meta.md +92 -0
  101. package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
  102. package/src/components/filter-bar/filter-bar.tsx +568 -0
  103. package/src/components/flex/flex.meta.md +54 -6
  104. package/src/components/flex/flex.stories.tsx +107 -20
  105. package/src/components/flex/flex.tsx +27 -4
  106. package/src/components/float-button/float-button.meta.md +8 -3
  107. package/src/components/float-button/float-button.stories.tsx +9 -7
  108. package/src/components/float-button/float-button.tsx +1 -1
  109. package/src/components/form/form.meta.md +39 -17
  110. package/src/components/form/form.stories.tsx +350 -3
  111. package/src/components/form/form.tsx +101 -35
  112. package/src/components/grid/grid.meta.md +7 -2
  113. package/src/components/grid/grid.stories.tsx +6 -4
  114. package/src/components/hover-card/hover-card.meta.md +20 -9
  115. package/src/components/hover-card/hover-card.stories.tsx +34 -5
  116. package/src/components/hover-card/hover-card.tsx +51 -13
  117. package/src/components/icon/DEVELOPMENT.md +809 -0
  118. package/src/components/icon/icon.meta.md +170 -0
  119. package/src/components/icon/icon.stories.tsx +344 -0
  120. package/src/components/icon/icon.tsx +248 -0
  121. package/src/components/image/image.meta.md +9 -4
  122. package/src/components/image/image.stories.tsx +3 -3
  123. package/src/components/image/image.tsx +6 -4
  124. package/src/components/input/demo/basic.tsx +12 -0
  125. package/src/components/input/demo/clearable.tsx +21 -0
  126. package/src/components/input/demo/show-count.tsx +18 -0
  127. package/src/components/input/demo/sizes.tsx +15 -0
  128. package/src/components/input/input.meta.md +39 -33
  129. package/src/components/input/input.stories.tsx +62 -35
  130. package/src/components/input/input.tsx +97 -98
  131. package/src/components/input-group/input-group.meta.md +54 -22
  132. package/src/components/input-group/input-group.stories.tsx +49 -16
  133. package/src/components/input-group/input-group.tsx +44 -8
  134. package/src/components/input-number/input-number.meta.md +64 -7
  135. package/src/components/input-number/input-number.stories.tsx +46 -8
  136. package/src/components/input-number/input-number.tsx +99 -26
  137. package/src/components/input-otp/input-otp.meta.md +4 -3
  138. package/src/components/input-otp/input-otp.stories.tsx +3 -3
  139. package/src/components/input-otp/input-otp.tsx +1 -1
  140. package/src/components/item/item.meta.md +8 -3
  141. package/src/components/item/item.stories.tsx +8 -5
  142. package/src/components/item/item.tsx +7 -6
  143. package/src/components/kbd/kbd.meta.md +13 -4
  144. package/src/components/kbd/kbd.stories.tsx +4 -4
  145. package/src/components/kbd/kbd.tsx +10 -5
  146. package/src/components/label/label.meta.md +18 -10
  147. package/src/components/label/label.stories.tsx +64 -6
  148. package/src/components/label/label.tsx +91 -19
  149. package/src/components/masonry/masonry.meta.md +8 -3
  150. package/src/components/masonry/masonry.stories.tsx +7 -5
  151. package/src/components/masonry/masonry.tsx +1 -0
  152. package/src/components/mentions/mentions.meta.md +36 -6
  153. package/src/components/mentions/mentions.stories.tsx +120 -6
  154. package/src/components/mentions/mentions.tsx +11 -5
  155. package/src/components/menubar/menubar.meta.md +30 -12
  156. package/src/components/menubar/menubar.stories.tsx +62 -2
  157. package/src/components/menubar/menubar.tsx +9 -9
  158. package/src/components/native-select/native-select.meta.md +8 -3
  159. package/src/components/native-select/native-select.stories.tsx +8 -5
  160. package/src/components/native-select/native-select.tsx +1 -1
  161. package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
  162. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
  163. package/src/components/navigation-menu/navigation-menu.tsx +8 -4
  164. package/src/components/notification/notification.meta.md +52 -10
  165. package/src/components/notification/notification.stories.tsx +11 -9
  166. package/src/components/notification/notification.tsx +36 -21
  167. package/src/components/page-header/DEVELOPMENT.md +842 -0
  168. package/src/components/page-header/page-header.meta.md +208 -0
  169. package/src/components/page-header/page-header.stories.tsx +421 -0
  170. package/src/components/page-header/page-header.tsx +281 -0
  171. package/src/components/pagination/pagination.meta.md +140 -37
  172. package/src/components/pagination/pagination.stories.tsx +232 -10
  173. package/src/components/pagination/pagination.tsx +355 -63
  174. package/src/components/popconfirm/popconfirm.meta.md +9 -4
  175. package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
  176. package/src/components/popconfirm/popconfirm.tsx +2 -2
  177. package/src/components/popover/popover.meta.md +62 -5
  178. package/src/components/popover/popover.stories.tsx +83 -7
  179. package/src/components/popover/popover.tsx +77 -28
  180. package/src/components/progress/progress.meta.md +38 -6
  181. package/src/components/progress/progress.stories.tsx +3 -3
  182. package/src/components/progress/progress.tsx +24 -16
  183. package/src/components/radio-group/radio-group.meta.md +79 -7
  184. package/src/components/radio-group/radio-group.stories.tsx +39 -3
  185. package/src/components/radio-group/radio-group.tsx +149 -18
  186. package/src/components/rate/rate.meta.md +35 -4
  187. package/src/components/rate/rate.stories.tsx +13 -5
  188. package/src/components/rate/rate.tsx +37 -10
  189. package/src/components/resizable/resizable.meta.md +7 -4
  190. package/src/components/resizable/resizable.stories.tsx +6 -6
  191. package/src/components/resizable/resizable.tsx +1 -1
  192. package/src/components/result/result.meta.md +7 -2
  193. package/src/components/result/result.stories.tsx +4 -8
  194. package/src/components/result/result.tsx +24 -15
  195. package/src/components/scroll-area/scroll-area.meta.md +4 -3
  196. package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
  197. package/src/components/scroll-area/scroll-area.tsx +3 -3
  198. package/src/components/segmented/segmented.meta.md +7 -4
  199. package/src/components/segmented/segmented.stories.tsx +37 -8
  200. package/src/components/segmented/segmented.tsx +15 -7
  201. package/src/components/select/select.meta.md +197 -52
  202. package/src/components/select/select.stories.tsx +238 -63
  203. package/src/components/select/select.tsx +718 -171
  204. package/src/components/separator/separator.meta.md +4 -3
  205. package/src/components/separator/separator.stories.tsx +3 -3
  206. package/src/components/separator/separator.tsx +3 -7
  207. package/src/components/sheet/sheet.meta.md +32 -16
  208. package/src/components/sheet/sheet.stories.tsx +116 -10
  209. package/src/components/sheet/sheet.tsx +116 -29
  210. package/src/components/sidebar/sidebar.meta.md +37 -18
  211. package/src/components/sidebar/sidebar.stories.tsx +701 -29
  212. package/src/components/sidebar/sidebar.tsx +615 -142
  213. package/src/components/skeleton/skeleton.meta.md +4 -5
  214. package/src/components/skeleton/skeleton.stories.tsx +4 -4
  215. package/src/components/skeleton/skeleton.tsx +7 -7
  216. package/src/components/slider/slider.meta.md +57 -5
  217. package/src/components/slider/slider.stories.tsx +58 -6
  218. package/src/components/slider/slider.tsx +154 -13
  219. package/src/components/sonner/sonner.meta.md +58 -7
  220. package/src/components/sonner/sonner.stories.tsx +78 -5
  221. package/src/components/sonner/sonner.tsx +137 -8
  222. package/src/components/spinner/spinner.meta.md +62 -13
  223. package/src/components/spinner/spinner.stories.tsx +66 -14
  224. package/src/components/spinner/spinner.tsx +111 -9
  225. package/src/components/statistic/statistic.meta.md +7 -2
  226. package/src/components/statistic/statistic.stories.tsx +3 -7
  227. package/src/components/statistic/statistic.tsx +5 -6
  228. package/src/components/steps/steps.meta.md +18 -4
  229. package/src/components/steps/steps.stories.tsx +43 -3
  230. package/src/components/steps/steps.tsx +15 -12
  231. package/src/components/switch/switch.meta.md +51 -5
  232. package/src/components/switch/switch.stories.tsx +6 -6
  233. package/src/components/switch/switch.tsx +109 -41
  234. package/src/components/table/table.meta.md +17 -6
  235. package/src/components/table/table.stories.tsx +10 -5
  236. package/src/components/table/table.tsx +4 -4
  237. package/src/components/tabs/tabs.meta.md +38 -25
  238. package/src/components/tabs/tabs.stories.tsx +111 -25
  239. package/src/components/tabs/tabs.tsx +125 -54
  240. package/src/components/tag/tag.meta.md +105 -40
  241. package/src/components/tag/tag.stories.tsx +189 -16
  242. package/src/components/tag/tag.tsx +222 -21
  243. package/src/components/textarea/textarea.meta.md +35 -19
  244. package/src/components/textarea/textarea.stories.tsx +32 -6
  245. package/src/components/textarea/textarea.tsx +33 -9
  246. package/src/components/time-picker/time-picker.meta.md +124 -32
  247. package/src/components/time-picker/time-picker.stories.tsx +85 -15
  248. package/src/components/time-picker/time-picker.tsx +913 -61
  249. package/src/components/timeline/timeline.meta.md +14 -6
  250. package/src/components/timeline/timeline.stories.tsx +37 -7
  251. package/src/components/timeline/timeline.tsx +35 -14
  252. package/src/components/toggle/toggle.meta.md +5 -4
  253. package/src/components/toggle/toggle.stories.tsx +4 -4
  254. package/src/components/toggle/toggle.tsx +4 -3
  255. package/src/components/toggle-group/toggle-group.meta.md +5 -4
  256. package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
  257. package/src/components/toggle-group/toggle-group.tsx +2 -2
  258. package/src/components/tooltip/tooltip.meta.md +55 -5
  259. package/src/components/tooltip/tooltip.stories.tsx +42 -5
  260. package/src/components/tooltip/tooltip.tsx +81 -21
  261. package/src/components/tour/tour.meta.md +9 -4
  262. package/src/components/tour/tour.stories.tsx +3 -3
  263. package/src/components/tour/tour.tsx +4 -4
  264. package/src/components/transfer/transfer.meta.md +11 -6
  265. package/src/components/transfer/transfer.stories.tsx +4 -8
  266. package/src/components/transfer/transfer.tsx +28 -21
  267. package/src/components/tree/tree.meta.md +63 -5
  268. package/src/components/tree/tree.stories.tsx +31 -12
  269. package/src/components/tree/tree.tsx +9 -8
  270. package/src/components/tree-select/tree-select.meta.md +59 -8
  271. package/src/components/tree-select/tree-select.stories.tsx +3 -3
  272. package/src/components/tree-select/tree-select.tsx +42 -7
  273. package/src/components/typography/typography.meta.md +61 -14
  274. package/src/components/typography/typography.stories.tsx +12 -11
  275. package/src/components/typography/typography.tsx +43 -28
  276. package/src/components/upload/upload.meta.md +49 -4
  277. package/src/components/upload/upload.stories.tsx +72 -12
  278. package/src/components/upload/upload.tsx +170 -37
  279. package/src/components/watermark/watermark.meta.md +7 -2
  280. package/src/components/watermark/watermark.stories.tsx +101 -9
  281. package/src/components/watermark/watermark.tsx +1 -0
  282. package/src/hooks/use-breakpoint.ts +117 -0
  283. package/src/hooks/use-debounce-callback.ts +52 -0
  284. package/src/hooks/use-mobile.ts +23 -0
  285. package/src/stories/theme-tokens.stories.tsx +747 -0
  286. package/src/utils/trigger-input.ts +53 -0
  287. package/src/components/button-group/button-group.meta.md +0 -92
  288. package/src/components/button-group/button-group.stories.tsx +0 -90
  289. package/src/components/button-group/button-group.tsx +0 -75
  290. package/src/components/combobox/combobox.meta.md +0 -93
  291. package/src/components/combobox/combobox.stories.tsx +0 -55
  292. package/src/components/combobox/combobox.tsx +0 -130
  293. package/src/components/space/space.meta.md +0 -94
  294. package/src/components/space/space.stories.tsx +0 -94
  295. package/src/components/space/space.tsx +0 -106
@@ -1,15 +1,15 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { Watermark } from './watermark';
3
3
 
4
4
  const meta: Meta<typeof Watermark> = {
5
- title: '反馈与浮层 · Feedback/Watermark',
5
+ title: '反馈 · Feedback/Watermark',
6
6
  component: Watermark,
7
7
  tags: ['autodocs'],
8
8
  parameters: {
9
9
  docs: {
10
10
  description: {
11
11
  component:
12
- '水印 容器内重复平铺的 SVG 文字 / 图片水印(内部敏感页面 / 文档预览 / demo 录屏)。前端视觉效果,不作为安全防护。等价 antd `Watermark`(v5.1+)。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
12
+ '水印 —— 在容器内重复平铺 SVG 文字 / 图片水印,用于内部敏感页面、文档预览、设计稿评审、demo 录屏等"威慑泄露"场景。antd 独有补足,等价 antd `Watermark`(v5.1+):`content`(单行 / 多行字符串)、`image`(优先级高于 content)、`width` / `height` 单块尺寸、`gapX` / `gapY` 平铺间距、`rotate` 旋转角度(默认 -22°)、`fontSize` / `fontColor` / `fontWeight` 文字样式。**前端视觉效果 ≠ 安全防护**,真正合规需要服务端水印 + 数字签名。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/tokens`,无 mock。',
13
13
  },
14
14
  },
15
15
  },
@@ -17,6 +17,7 @@ const meta: Meta<typeof Watermark> = {
17
17
  content: { control: 'text' },
18
18
  rotate: { control: { type: 'number', min: -90, max: 90 } },
19
19
  fontSize: { control: { type: 'number', min: 8, max: 48 } },
20
+ fontWeight: { control: 'inline-radio', options: ['normal', 'medium', 'bold'] },
20
21
  gapX: { control: 'number' },
21
22
  gapY: { control: 'number' },
22
23
  },
@@ -24,6 +25,7 @@ const meta: Meta<typeof Watermark> = {
24
25
  content: 'Acme Corp · CONFIDENTIAL',
25
26
  rotate: -22,
26
27
  fontSize: 14,
28
+ fontWeight: 'normal',
27
29
  gapX: 80,
28
30
  gapY: 80,
29
31
  },
@@ -33,8 +35,16 @@ export default meta;
33
35
  type Story = StoryObj<typeof Watermark>;
34
36
 
35
37
  export const Playground: Story = {
38
+ parameters: {
39
+ docs: {
40
+ description: {
41
+ story:
42
+ '基础文字水印 —— 单行 `content` + 默认 `-22°` 旋转,常用于"公司名 · 机密"等场景。',
43
+ },
44
+ },
45
+ },
36
46
  render: (args) => (
37
- <Watermark {...args} className="h-72 w-full rounded-md border bg-card p-6">
47
+ <Watermark {...args} className="h-72 w-full rounded-md border border-border bg-card p-6">
38
48
  <h3 className="mb-3 text-lg font-semibold">内部文档</h3>
39
49
  <p className="text-sm text-muted-foreground">
40
50
  这是一段示例文档内容。水印铺在底层,内容可正常交互。
@@ -45,11 +55,19 @@ export const Playground: Story = {
45
55
  };
46
56
 
47
57
  export const MultilineUser: Story = {
48
- parameters: { controls: { disable: true } },
58
+ parameters: {
59
+ controls: { disable: true },
60
+ docs: {
61
+ description: {
62
+ story:
63
+ '多行水印 —— `content` 传字符串数组渲染成两行(用户名 + 工号),起到"截图即追溯"的威慑作用。',
64
+ },
65
+ },
66
+ },
49
67
  render: () => (
50
68
  <Watermark
51
69
  content={['Alice Wong', 'EMP-2026-001']}
52
- className="h-72 w-full rounded-md border bg-card p-6"
70
+ className="h-72 w-full rounded-md border border-border bg-card p-6"
53
71
  >
54
72
  <h3 className="mb-3 text-lg font-semibold">用户信息水印</h3>
55
73
  <p className="text-sm text-muted-foreground">
@@ -59,8 +77,53 @@ export const MultilineUser: Story = {
59
77
  ),
60
78
  };
61
79
 
80
+ export const ImageWatermark: Story = {
81
+ parameters: {
82
+ controls: { disable: true },
83
+ docs: {
84
+ description: {
85
+ story:
86
+ '图片水印 —— `image` 传 data URL 或 URL,优先级高于 `content`。建议透明 PNG / SVG,否则会出现矩形背景。',
87
+ },
88
+ },
89
+ },
90
+ render: () => {
91
+ // 一个简单的内联 SVG 标识,用作水印图片。
92
+ const svg = encodeURIComponent(
93
+ '<svg xmlns="http://www.w3.org/2000/svg" width="80" height="40" viewBox="0 0 80 40">' +
94
+ '<rect x="2" y="2" width="76" height="36" rx="6" fill="none" stroke="rgba(0,0,0,0.25)" stroke-width="1.5"/>' +
95
+ '<text x="40" y="26" text-anchor="middle" font-family="system-ui" font-size="14" font-weight="600" fill="rgba(0,0,0,0.25)">EVO</text>' +
96
+ '</svg>',
97
+ );
98
+ const dataUrl = `data:image/svg+xml;utf8,${svg}`;
99
+ return (
100
+ <Watermark
101
+ image={dataUrl}
102
+ width={80}
103
+ height={40}
104
+ gapX={60}
105
+ gapY={60}
106
+ className="h-72 w-full rounded-md border border-border bg-card p-6"
107
+ >
108
+ <h3 className="mb-3 text-lg font-semibold">品牌图标水印</h3>
109
+ <p className="text-sm text-muted-foreground">
110
+ 适合在内部站点 / 产品截图标注品牌或工作组标识。
111
+ </p>
112
+ </Watermark>
113
+ );
114
+ },
115
+ };
116
+
62
117
  export const ColoredAndDense: Story = {
63
- parameters: { controls: { disable: true } },
118
+ parameters: {
119
+ controls: { disable: true },
120
+ docs: {
121
+ description: {
122
+ story:
123
+ '自定义样式 —— 通过 `fontColor`(带 alpha)、`fontWeight`、`gapX/Y`、`rotate` 调整视觉强度,适配不同重要级别的页面。',
124
+ },
125
+ },
126
+ },
64
127
  render: () => (
65
128
  <Watermark
66
129
  content="内部资料"
@@ -69,10 +132,39 @@ export const ColoredAndDense: Story = {
69
132
  gapX={60}
70
133
  gapY={60}
71
134
  rotate={-30}
72
- className="h-72 w-full rounded-md border bg-card p-6"
135
+ className="h-72 w-full rounded-md border border-border bg-card p-6"
73
136
  >
74
137
  <h3 className="mb-3 text-lg font-semibold">红色密集水印</h3>
75
- <p className="text-sm text-muted-foreground">配合 fontWeight / gap / rotate 调整视觉强度。</p>
138
+ <p className="text-sm text-muted-foreground">
139
+ 配合 fontWeight / gap / rotate 调整视觉强度。
140
+ </p>
141
+ </Watermark>
142
+ ),
143
+ };
144
+
145
+ export const SparseLayout: Story = {
146
+ parameters: {
147
+ controls: { disable: true },
148
+ docs: {
149
+ description: {
150
+ story:
151
+ '稀疏布局 —— 增大 `gapX` / `gapY` 让水印块之间留更多空白,适合阅读类页面。',
152
+ },
153
+ },
154
+ },
155
+ render: () => (
156
+ <Watermark
157
+ content="DRAFT"
158
+ gapX={160}
159
+ gapY={140}
160
+ fontSize={18}
161
+ className="h-72 w-full rounded-md border border-border bg-card p-6"
162
+ >
163
+ <h3 className="mb-3 text-lg font-semibold">长文档预览</h3>
164
+ <p className="text-sm leading-7 text-muted-foreground">
165
+ 这是一份还未定稿的文档。稀疏的 DRAFT 水印不会过度干扰阅读,
166
+ 但能在截图中清晰地传达"非正式版本"的状态。
167
+ </p>
76
168
  </Watermark>
77
169
  ),
78
170
  };
@@ -112,6 +112,7 @@ const Watermark = React.forwardRef<HTMLDivElement, WatermarkProps>(
112
112
  height = 64,
113
113
  rotate = -22,
114
114
  fontSize = 14,
115
+ // eslint-disable-next-line teamix-evo/no-color-literal -- Watermark renders to canvas; CSS var() not supported in canvas API. Default value matches --overlay-watermark token.
115
116
  fontColor = 'rgba(0,0,0,0.15)',
116
117
  fontWeight = 'normal',
117
118
  gapX = 80,
@@ -0,0 +1,117 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * 响应式断点 5 档 — 对齐《Teamix UI 表单设计规范》§4.1。
5
+ * CSS 真值见 `@teamix-evo/tokens` 的 `--bp-*` 变量(theme.css)。
6
+ */
7
+ export type Breakpoint = 'xs' | 's' | 'm' | 'l' | 'xl';
8
+
9
+ export const BREAKPOINTS: Record<Breakpoint, number> = {
10
+ xs: 432,
11
+ s: 672,
12
+ m: 944,
13
+ l: 1200,
14
+ xl: 1600,
15
+ };
16
+
17
+ const ORDER: Breakpoint[] = ['xl', 'l', 'm', 's', 'xs'];
18
+
19
+ function resolveCurrent(width: number): Breakpoint | null {
20
+ for (const bp of ORDER) {
21
+ if (width >= BREAKPOINTS[bp]) return bp;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * 检测当前视口落在哪一档断点上。
28
+ *
29
+ * 返回 ≤ viewport 的最大档位(`viewport ≥ 1600` → `xl`;`viewport < 432` → `null`)。
30
+ *
31
+ * 服务端渲染场景下首次返回 `null`,客户端 hydrate 后基于 `matchMedia` 实时同步。
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * const bp = useBreakpoint();
36
+ * const orientation = bp === null ? 'vertical' : 'horizontal';
37
+ * ```
38
+ */
39
+ export function useBreakpoint(): Breakpoint | null {
40
+ const [current, setCurrent] = React.useState<Breakpoint | null>(null);
41
+
42
+ React.useEffect(() => {
43
+ const update = () => setCurrent(resolveCurrent(window.innerWidth));
44
+ update();
45
+
46
+ const queries = ORDER.map((bp) => {
47
+ const mql = window.matchMedia(`(min-width: ${BREAKPOINTS[bp]}px)`);
48
+ mql.addEventListener('change', update);
49
+ return mql;
50
+ });
51
+
52
+ return () => {
53
+ for (const mql of queries) mql.removeEventListener('change', update);
54
+ };
55
+ }, []);
56
+
57
+ return current;
58
+ }
59
+
60
+ /**
61
+ * 响应式值 — 标量或断点映射对象。`base` 槽是兜底(没有任何断点匹配时使用)。
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const orientation: ResponsiveValue<'vertical' | 'horizontal'> = {
66
+ * base: 'vertical', // < 432
67
+ * xs: 'horizontal', // ≥ 432
68
+ * };
69
+ * ```
70
+ */
71
+ export type ResponsiveValue<T> =
72
+ | T
73
+ | (Partial<Record<Breakpoint, T>> & { base?: T });
74
+
75
+ function isResponsiveObject<T>(
76
+ value: ResponsiveValue<T>,
77
+ ): value is Partial<Record<Breakpoint, T>> & { base?: T } {
78
+ return (
79
+ typeof value === 'object' &&
80
+ value !== null &&
81
+ !Array.isArray(value) &&
82
+ !React.isValidElement(value as React.ReactNode)
83
+ );
84
+ }
85
+
86
+ /**
87
+ * 从响应式值中挑选适合当前断点的真值。
88
+ *
89
+ * 解析规则(级联回退):
90
+ * `current` → 下移到 ≤ current 的最大档位 → ... → `base` → `undefined`
91
+ *
92
+ * 例:`current='m'`,值为 `{ base: 1, xs: 2, m: 3 }`,返回 `3`;
93
+ * `current='s'`,值同上,返回 `2`(xs 是 s 之下的最大档位);
94
+ * `current=null`,值同上,返回 `1`(base 兜底)。
95
+ *
96
+ * 传入标量时原样返回,不做断点匹配。
97
+ */
98
+ export function pickByBreakpoint<T>(
99
+ value: ResponsiveValue<T> | undefined,
100
+ current: Breakpoint | null,
101
+ ): T | undefined {
102
+ if (value === undefined) return undefined;
103
+ if (!isResponsiveObject(value)) return value as T;
104
+
105
+ const map = value as Partial<Record<Breakpoint, T>> & { base?: T };
106
+
107
+ if (current === null) return map.base;
108
+
109
+ // 从 current 档位往下回退,找到第一个定义过的值
110
+ const startIdx = ORDER.indexOf(current);
111
+ for (let i = startIdx; i < ORDER.length; i++) {
112
+ const bp = ORDER[i]!;
113
+ const v = map[bp];
114
+ if (v !== undefined) return v;
115
+ }
116
+ return map.base;
117
+ }
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * 防抖回调 hook — 返回一个防抖包装后的函数,在指定 `delay` 毫秒内
5
+ * 连续调用只会执行最后一次。适用于搜索输入、筛选变更等高频场景。
6
+ *
7
+ * @param callback - 需要防抖的回调函数
8
+ * @param delay - 防抖延迟(ms)
9
+ * @default delay 300
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const debouncedSearch = useDebounceCallback((value: string) => {
14
+ * fetchResults(value);
15
+ * }, 300);
16
+ * ```
17
+ */
18
+ export function useDebounceCallback<T extends (...args: any[]) => any>(
19
+ callback: T,
20
+ delay: number = 300,
21
+ ): (...args: Parameters<T>) => void {
22
+ const callbackRef = React.useRef(callback);
23
+ const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
24
+
25
+ // 始终持有最新 callback 引用,避免闭包陈旧
26
+ React.useEffect(() => {
27
+ callbackRef.current = callback;
28
+ }, [callback]);
29
+
30
+ // 组件卸载时清除 timer
31
+ React.useEffect(() => {
32
+ return () => {
33
+ if (timerRef.current) {
34
+ clearTimeout(timerRef.current);
35
+ }
36
+ };
37
+ }, []);
38
+
39
+ const debouncedFn = React.useCallback(
40
+ (...args: Parameters<T>) => {
41
+ if (timerRef.current) {
42
+ clearTimeout(timerRef.current);
43
+ }
44
+ timerRef.current = setTimeout(() => {
45
+ callbackRef.current(...args);
46
+ }, delay);
47
+ },
48
+ [delay],
49
+ );
50
+
51
+ return debouncedFn;
52
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ /**
6
+ * 检测视口是否处于移动尺寸(< 768px) — Sidebar 复合组件在 mobile 模式下
7
+ * 切换为 Sheet 抽屉的判定依据。
8
+ *
9
+ * 服务端渲染场景下首次返回 `false`,客户端 hydrate 后基于 matchMedia 实时同步。
10
+ */
11
+ export function useIsMobile(): boolean {
12
+ const [isMobile, setIsMobile] = React.useState<boolean>(false);
13
+
14
+ React.useEffect(() => {
15
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
16
+ const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ mql.addEventListener('change', onChange);
18
+ onChange();
19
+ return () => mql.removeEventListener('change', onChange);
20
+ }, []);
21
+
22
+ return isMobile;
23
+ }