@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamix-evo/ui",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Source-injected UI components for Teamix Evo (shadcn-based, antd capabilities)",
5
5
  "type": "module",
6
6
  "files": [
@@ -41,11 +41,12 @@
41
41
  "@radix-ui/react-toggle": "^1",
42
42
  "@radix-ui/react-toggle-group": "^1",
43
43
  "@radix-ui/react-tooltip": "^1",
44
- "@storybook/addon-essentials": "^8",
45
- "@storybook/react": "^8",
46
- "@storybook/react-vite": "^8",
44
+ "@storybook/addon-docs": "^10.4.1",
45
+ "@storybook/react": "^10.4.1",
46
+ "@storybook/react-vite": "^10.4.1",
47
47
  "@tailwindcss/vite": "^4",
48
48
  "@tanstack/react-table": "^8",
49
+ "@types/node": "^20.0.0",
49
50
  "@types/react": "^18.3.0",
50
51
  "@types/react-dom": "^18.3.0",
51
52
  "@vitejs/plugin-react": "^4.3.0",
@@ -55,6 +56,7 @@
55
56
  "date-fns": "^3",
56
57
  "embla-carousel-react": "^8",
57
58
  "eslint": "^9.0.0",
59
+ "eslint-plugin-storybook": "10.4.1",
58
60
  "input-otp": "^1",
59
61
  "lucide-react": "^0.460.0",
60
62
  "react": "^18.3.0",
@@ -63,7 +65,7 @@
63
65
  "react-hook-form": "^7",
64
66
  "react-resizable-panels": "^2",
65
67
  "sonner": "^1",
66
- "storybook": "^8",
68
+ "storybook": "^10.4.1",
67
69
  "tailwind-merge": "^2.5.0",
68
70
  "tailwindcss": "^4",
69
71
  "ts-morph": "^22",
@@ -72,9 +74,16 @@
72
74
  "vite": "^5.4.0",
73
75
  "vite-tsconfig-paths": "^6.1.1",
74
76
  "zod": "^3",
75
- "@teamix-evo/eslint-config": "0.1.0",
76
- "@teamix-evo/registry": "0.2.0",
77
- "@teamix-evo/design": "^0.2.0"
77
+ "@teamix-evo/tokens": "^0.5.0",
78
+ "@teamix-evo/eslint-config": "0.2.1",
79
+ "@teamix-evo/registry": "0.3.0"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public",
83
+ "registry": "https://registry.npmjs.org/"
84
+ },
85
+ "dependencies": {
86
+ "@tanstack/react-virtual": "^3.13.26"
78
87
  },
79
88
  "scripts": {
80
89
  "validate": "tsx scripts/validate-entries.ts",
@@ -84,7 +93,8 @@
84
93
  "typecheck": "tsc --noEmit",
85
94
  "lint": "eslint .",
86
95
  "build": "echo 'pure resource package, no build needed'",
87
- "storybook": "storybook dev -p 6006",
88
- "build-storybook": "storybook build"
96
+ "gen:theme-overrides": "tsx .storybook/gen-theme-overrides.ts",
97
+ "storybook": "pnpm gen:theme-overrides && storybook dev -p 6006",
98
+ "build-storybook": "pnpm gen:theme-overrides && storybook build"
89
99
  }
90
100
  }
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  id: accordion
3
3
  name: Accordion
4
+ displayName: 折叠面板
4
5
  type: component
5
- category: navigation
6
+ category: data-display
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Accordion
11
+ # Accordion 折叠面板
11
12
 
12
13
  折叠面板 — Radix Accordion + antd Collapse 的 `accordion` 模式(`type="single"` 即单展开)。
13
14
  **与 Collapsible 区别**:Accordion 是**多 item 列表**,可单展开 / 多展开;Collapsible 是单个区域的展开收起。
@@ -78,7 +79,7 @@ import {
78
79
  </AccordionItem>
79
80
  <AccordionItem value="item-2">
80
81
  <AccordionTrigger>如何安装?</AccordionTrigger>
81
- <AccordionContent>npx teamix-evo design init opentrek</AccordionContent>
82
+ <AccordionContent>npx teamix-evo tokens init opentrek</AccordionContent>
82
83
  </AccordionItem>
83
84
  </Accordion>
84
85
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import {
3
3
  Accordion,
4
4
  AccordionItem,
@@ -7,9 +7,17 @@ import {
7
7
  } from './accordion';
8
8
 
9
9
  const meta: Meta<typeof Accordion> = {
10
- title: '导航 · Navigation/Accordion',
10
+ title: '数据展示 · Data Display/Accordion',
11
11
  component: Accordion,
12
12
  tags: ['autodocs'],
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ '折叠面板 — Radix Accordion + antd Collapse accordion 模式。支持单展开(collapsible) / 多展开,每项自带 ChevronDown 箭头翻转动画。',
18
+ },
19
+ },
20
+ },
13
21
  };
14
22
 
15
23
  export default meta;
@@ -21,14 +29,15 @@ export const Single: Story = {
21
29
  <AccordionItem value="item-1">
22
30
  <AccordionTrigger>什么是 Teamix Evo?</AccordionTrigger>
23
31
  <AccordionContent>
24
- 面向产品研发场景的 AI Coding 套件,提供设计体系、技能、组件、文档等可装配资产。
32
+ 面向产品研发场景的 AI Coding
33
+ 套件,提供设计体系、技能、组件、文档等可装配资产。
25
34
  </AccordionContent>
26
35
  </AccordionItem>
27
36
  <AccordionItem value="item-2">
28
37
  <AccordionTrigger>如何安装?</AccordionTrigger>
29
38
  <AccordionContent>
30
39
  <code className="rounded bg-muted px-1.5 py-0.5">
31
- npx teamix-evo design init opentrek
40
+ npx teamix-evo tokens init opentrek
32
41
  </code>
33
42
  </AccordionContent>
34
43
  </AccordionItem>
@@ -45,11 +54,7 @@ export const Single: Story = {
45
54
  export const Multiple: Story = {
46
55
  parameters: { controls: { disable: true } },
47
56
  render: () => (
48
- <Accordion
49
- type="multiple"
50
- defaultValue={['a', 'b']}
51
- className="w-96"
52
- >
57
+ <Accordion type="multiple" defaultValue={['a', 'b']} className="w-96">
53
58
  <AccordionItem value="a">
54
59
  <AccordionTrigger>分组 A</AccordionTrigger>
55
60
  <AccordionContent>分组 A 的内容</AccordionContent>
@@ -4,10 +4,103 @@ import { ChevronDown } from 'lucide-react';
4
4
 
5
5
  import { cn } from '@/utils/cn';
6
6
 
7
- const Accordion = AccordionPrimitive.Root;
8
- export type AccordionProps = React.ComponentPropsWithoutRef<
9
- typeof AccordionPrimitive.Root
10
- >;
7
+ // ─── AccordionProps(ADR 0025 合规:显式声明交互 props)─────────────────────
8
+
9
+ interface AccordionBaseProps {
10
+ /**
11
+ * 排列方向(antd 未提供,shadcn 独有扩展) — 竖直为默认手风琴;水平适用 FAQ 横排。
12
+ * @default "vertical"
13
+ */
14
+ orientation?: 'vertical' | 'horizontal';
15
+ /**
16
+ * 禁用整组交互(灰化 + 所有 Trigger 不可点)。
17
+ * @default false
18
+ */
19
+ disabled?: boolean;
20
+ }
21
+
22
+ export interface AccordionSingleProps
23
+ extends Omit<
24
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>,
25
+ | 'type'
26
+ | 'value'
27
+ | 'defaultValue'
28
+ | 'onValueChange'
29
+ | 'collapsible'
30
+ | 'orientation'
31
+ | 'disabled'
32
+ >,
33
+ AccordionBaseProps {
34
+ /**
35
+ * 模式 — `single` 同时只展开一项(互斥);`multiple` 可展开任意多项。
36
+ */
37
+ type: 'single';
38
+ /**
39
+ * 受控当前展开 item 的 value(仅 `type="single"` 时为 string)。
40
+ */
41
+ value?: string;
42
+ /**
43
+ * 非受控初始展开项。
44
+ * @default ""
45
+ */
46
+ defaultValue?: string;
47
+ /**
48
+ * 展开状态变化回调(`type="single"` 时返回单个 value string)。
49
+ */
50
+ onValueChange?: (value: string) => void;
51
+ /**
52
+ * 是否允许全部收起(antd `accordion` 模式下默认不可收起,此属性允许) — 仅 `type="single"` 有效。
53
+ * @default false
54
+ */
55
+ collapsible?: boolean;
56
+ }
57
+
58
+ export interface AccordionMultipleProps
59
+ extends Omit<
60
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>,
61
+ | 'type'
62
+ | 'value'
63
+ | 'defaultValue'
64
+ | 'onValueChange'
65
+ | 'collapsible'
66
+ | 'orientation'
67
+ | 'disabled'
68
+ >,
69
+ AccordionBaseProps {
70
+ /**
71
+ * 模式 — `multiple` 可展开任意多项。
72
+ */
73
+ type: 'multiple';
74
+ /**
75
+ * 受控当前展开项 value 数组(`type="multiple"` 时为 string[])。
76
+ */
77
+ value?: string[];
78
+ /**
79
+ * 非受控初始展开项(数组)。
80
+ * @default []
81
+ */
82
+ defaultValue?: string[];
83
+ /**
84
+ * 展开状态变化回调(`type="multiple"` 时返回 string[])。
85
+ */
86
+ onValueChange?: (value: string[]) => void;
87
+ }
88
+
89
+ export type AccordionProps = AccordionSingleProps | AccordionMultipleProps;
90
+
91
+ const Accordion = React.forwardRef<
92
+ React.ElementRef<typeof AccordionPrimitive.Root>,
93
+ AccordionProps
94
+ >(({ className, ...props }, ref) => (
95
+ <AccordionPrimitive.Root
96
+ ref={ref}
97
+ className={cn(className)}
98
+ {...(props as React.ComponentPropsWithoutRef<
99
+ typeof AccordionPrimitive.Root
100
+ >)}
101
+ />
102
+ ));
103
+ Accordion.displayName = 'Accordion';
11
104
 
12
105
  const AccordionItem = React.forwardRef<
13
106
  React.ElementRef<typeof AccordionPrimitive.Item>,
@@ -15,7 +108,7 @@ const AccordionItem = React.forwardRef<
15
108
  >(({ className, ...props }, ref) => (
16
109
  <AccordionPrimitive.Item
17
110
  ref={ref}
18
- className={cn('border-b', className)}
111
+ className={cn('border-b border-b-border', className)}
19
112
  {...props}
20
113
  />
21
114
  ));
@@ -29,13 +122,16 @@ const AccordionTrigger = React.forwardRef<
29
122
  <AccordionPrimitive.Trigger
30
123
  ref={ref}
31
124
  className={cn(
32
- 'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180',
125
+ 'group flex cursor-pointer flex-1 items-center justify-between gap-3 rounded-sm py-4 text-xs font-medium text-left transition-colors',
126
+ 'hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
127
+ 'disabled:cursor-not-allowed disabled:opacity-50',
128
+ '[&[data-state=open]>svg]:rotate-180',
33
129
  className,
34
130
  )}
35
131
  {...props}
36
132
  >
37
133
  {children}
38
- <ChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
134
+ <ChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-hover:text-primary" />
39
135
  </AccordionPrimitive.Trigger>
40
136
  </AccordionPrimitive.Header>
41
137
  ));
@@ -47,7 +143,7 @@ const AccordionContent = React.forwardRef<
47
143
  >(({ className, children, ...props }, ref) => (
48
144
  <AccordionPrimitive.Content
49
145
  ref={ref}
50
- className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
146
+ className="overflow-hidden text-xs data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
51
147
  {...props}
52
148
  >
53
149
  <div className={cn('pb-4 pt-0', className)}>{children}</div>
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  id: affix
3
3
  name: Affix
4
+ displayName: 固钉
4
5
  type: component
5
6
  category: navigation
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Affix
11
+ # Affix 固钉
11
12
 
12
13
  滚动吸顶 / 吸底 — antd 独有补足。**等价 antd `Affix`**。当容器滚出指定偏移时,把 children 切换为 `position: fixed`,保留原占位避免页面跳动。监听 `scroll` 事件 + `getBoundingClientRect()`,无第三方依赖。
13
14
 
@@ -24,14 +25,19 @@ package: "@teamix-evo/ui"
24
25
  - 弹窗内的吸附 → Drawer / Sheet 已有内置
25
26
  - 一页超过 1 个 Affix:容易形成"漂浮屏",有损可读性
26
27
 
28
+ ## Props
29
+
27
30
  <!-- auto:props:begin -->
28
31
  | 名称 | 类型 | 默认值 | 必填 | 说明 |
29
32
  | --- | --- | --- | --- | --- |
30
33
  | `offsetTop` | `number` | `0` | – | 滚动到此偏移量(相对视口顶部)时开始吸顶,单位 px。 |
31
34
  | `offsetBottom` | `number` | – | – | 滚动到此偏移量(相对视口底部)时开始吸底 — 与 `offsetTop` 互斥。 |
35
+ | `container` | `() => HTMLElement \| Window` | `() => window` | – | 滚动容器(antd `target` 并集) — 返回需要监听 `scroll` / `resize` 事件的滚动容器, 默认 `window`。常见场景:在内层 `overflow:auto` 容器(对话框 / 抽屉 / 自定义滚动区)中 让 Affix 跟随该容器滚动,而非主视口。 切到自定义容器后,内部使用 `position: sticky` 由浏览器原生处理"吸附后跟随容器视口顶部" (无需手动算 scrollTop 偏移),容器只需保证未被 `overflow: hidden` 切割。 |
32
36
  | `onAffixChange` | `(affixed: boolean) => void` | – | – | 吸附状态变化回调(antd `onChange` 并集)。 |
33
37
  <!-- auto:props:end -->
34
38
 
39
+ ## 依赖
40
+
35
41
  <!-- auto:deps:begin -->
36
42
  ### 同库依赖
37
43
 
@@ -53,6 +59,18 @@ _无 — 本组件不依赖任何 npm 包。_
53
59
  - **优先用 Tailwind `sticky top-N`** — 大部分"吸附顶部"用 sticky 就够,只有需要"超出容器边界仍跟随视口"才用 Affix
54
60
  - **不要让 Affix 包裹复杂动态高度内容** — width / height 是按初次渲染算的,内容剧烈变化会失同步
55
61
  - **`zIndex` 默认 10** — 与 Sonner toast(z=9999)、Dialog(z=50)等级别错开;如需更高自行 className 覆盖
62
+ - **`container` 自定义容器时,内部走 `position: sticky`** — 容器只需保证 `overflow: auto/scroll`(不能是 `hidden`),且祖先链不能有 `transform` / `filter` 等限定 stacking context 的属性。无需手动设 `position: relative`(浏览器原生托管)
63
+ - **Storybook 中默认 window 模式不可演示** — Storybook 8 autodocs iframe 不暴露稳定的 window scroll + toolbar globals 不刷新,但**业务真实页面上 `<Affix offsetTop={64}>` 零配置即可工作**;在文档站验证时请用 container 模式(三件 sticky stories 已就位)
64
+
65
+ ## Affix 形态 — 旧库 API → 新库映射
66
+
67
+ | 旧库 (Teamix 1.0 / antd `Affix`) | 新库 (`@teamix-evo/ui` `Affix`) | 备注 |
68
+ | --- | --- | --- |
69
+ | `offsetTop` | `offsetTop` | 行为一致 |
70
+ | `offsetBottom` | `offsetBottom` | 行为一致;与 `offsetTop` 互斥(同传时 `offsetBottom` 优先) |
71
+ | `onAffix` | `onAffixChange` | 重命名;签名一致 `(affixed: boolean) => void` |
72
+ | `target` | `container` | 重命名;返回 `HTMLElement \| Window`,默认 `window` |
73
+ | `useAbsolute` | — | 不修复 — 内部根据 `container` 是否传入自动切 `fixed`/`absolute` |
56
74
 
57
75
  ## Examples
58
76
 
@@ -1,4 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
3
  import { Affix } from './affix';
3
4
  import { Button } from '@/components/button/button';
4
5
 
@@ -6,11 +7,15 @@ const meta: Meta<typeof Affix> = {
6
7
  title: '导航 · Navigation/Affix',
7
8
  component: Affix,
8
9
  tags: ['autodocs'],
10
+ // 全局 preview.layout='centered' 会让 iframe 不产生可滚动的 window,
11
+ // Affix 默认监听 window 时无法触发吸顶。改为 'padded' 让 stories 各自掌控
12
+ // 内部滚动容器,并统一通过 `container` prop 指向自身,行为可观测、可复现。
9
13
  parameters: {
14
+ layout: 'padded',
10
15
  docs: {
11
16
  description: {
12
17
  component:
13
- '滚动吸顶 / 吸底 当容器滚出指定偏移时把 children 切换为 position: fixed,保留原占位。基于 scroll 事件 + getBoundingClientRect。等价 antd `Affix`。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
18
+ 'Affix 固钉 —— 在页面滚动超出指定偏移时,把 children 切为 `position: fixed`(默认监听 window) 或 `position: absolute`(传 `container` 时以自定义滚动容器为定位上下文),同时保留原占位避免页面跳动。**原生 scroll + getBoundingClientRect 实现、无依赖**,**等价 antd `Affix`** 能力(`offsetTop` / `offsetBottom` / `container`(对齐 antd `target`) / `onAffixChange`(对齐 antd `onChange`)),shadcn 未提供对应原子。视觉走 OpenTrek semantic tokens(吸附后 shadow-sm 同 tokens 一致),所有样式来自 `@teamix-evo/tokens`,无 mock。',
14
19
  },
15
20
  },
16
21
  },
@@ -23,35 +28,107 @@ const meta: Meta<typeof Affix> = {
23
28
  export default meta;
24
29
  type Story = StoryObj<typeof Affix>;
25
30
 
31
+ /**
32
+ * 在内层容器内吸顶 — Storybook 的 `Docs` / `Canvas` iframe 不一定提供可滚动的 window,
33
+ * 因此所有 Affix Story 都用 `container={() => ref.current}` 把吸附逻辑挂到自身的
34
+ * `overflow:auto` 容器上(容器 `position: relative`,吸附后切 `position: absolute`)。
35
+ * 业务侧最常见用法仍是 `Affix offsetTop={64}`(默认 window),不用传 container。
36
+ */
26
37
  export const Playground: Story = {
27
38
  parameters: { controls: { disable: true } },
28
- render: () => (
29
- <div className="space-y-3">
30
- <div className="rounded-md border p-4 text-sm text-muted-foreground">
31
- ↓ 在 Storybook 容器内滚动以触发吸顶
32
- </div>
33
- <div className="h-64 rounded-md border bg-muted/30 p-4">占位 1</div>
34
- <Affix offsetTop={0}>
35
- <div className="rounded-md border bg-card p-3 shadow-sm">
36
- <Button>吸顶按钮</Button>
39
+ render: () => {
40
+ const ref = React.useRef<HTMLDivElement | null>(null);
41
+ return (
42
+ <div
43
+ ref={ref}
44
+ className="relative h-96 overflow-auto rounded-md border border-border bg-muted/20 p-4"
45
+ >
46
+ <div className="flex flex-col gap-3">
47
+ <div className="rounded-md border border-border bg-card p-4 text-sm text-muted-foreground">
48
+ ↓ 在本容器内向下滚动以触发吸顶
49
+ </div>
50
+ <div className="h-40 rounded-md border border-border bg-card p-4">占位 1</div>
51
+ <Affix
52
+ offsetTop={0}
53
+ container={() => ref.current as HTMLElement}
54
+ onAffixChange={(a) => console.log('[Playground] affixed:', a)}
55
+ >
56
+ <div className="rounded-md border border-border bg-card p-3 shadow-sm">
57
+ <Button>吸顶按钮</Button>
58
+ </div>
59
+ </Affix>
60
+ <div className="h-[800px] rounded-md border border-border bg-card p-4">长占位</div>
37
61
  </div>
38
- </Affix>
39
- <div className="h-[800px] rounded-md border bg-muted/30 p-4">长占位</div>
40
- </div>
41
- ),
62
+ </div>
63
+ );
64
+ },
42
65
  };
43
66
 
44
67
  export const Bottom: Story = {
45
68
  parameters: { controls: { disable: true } },
46
- render: () => (
47
- <div className="space-y-3">
48
- <div className="h-[600px] rounded-md border bg-muted/30 p-4">向下滚动</div>
49
- <Affix offsetBottom={24}>
50
- <div className="rounded-md border bg-card p-3 shadow-sm">
51
- <Button block>立即提交(吸底)</Button>
69
+ render: () => {
70
+ const ref = React.useRef<HTMLDivElement | null>(null);
71
+ return (
72
+ <div
73
+ ref={ref}
74
+ className="relative h-96 overflow-auto rounded-md border border-border bg-muted/20 p-4"
75
+ >
76
+ <div className="flex flex-col gap-3">
77
+ <div className="h-[500px] rounded-md border border-border bg-card p-4">向下滚动</div>
78
+ <Affix offsetBottom={16} container={() => ref.current as HTMLElement}>
79
+ <div className="rounded-md border border-border bg-card p-3 shadow-sm">
80
+ <Button block>立即提交(吸底)</Button>
81
+ </div>
82
+ </Affix>
83
+ <div className="h-[500px] rounded-md border border-border bg-card p-4">滚到底</div>
52
84
  </div>
53
- </Affix>
54
- <div className="h-[600px] rounded-md border bg-muted/30 p-4">滚到底</div>
55
- </div>
56
- ),
85
+ </div>
86
+ );
87
+ },
57
88
  };
89
+
90
+ export const InContainer: Story = {
91
+ parameters: {
92
+ controls: { disable: true },
93
+ docs: {
94
+ description: {
95
+ story:
96
+ '显式传 `container` 把 Affix 挂到内层 `overflow:auto` 容器(对话框 / 抽屉 / 自定义滚动区) — 容器需 `position: relative`,吸附后元素切 `position: absolute`。',
97
+ },
98
+ },
99
+ },
100
+ render: () => {
101
+ const ref = React.useRef<HTMLDivElement | null>(null);
102
+ return (
103
+ <div
104
+ ref={ref}
105
+ className="relative h-80 overflow-auto rounded-md border border-border bg-muted/20 p-4"
106
+ >
107
+ <div className="flex flex-col gap-3">
108
+ <div className="text-sm text-muted-foreground">
109
+ ↓ 在内层容器(非 window)中滚动 — Affix 跟随该容器吸顶
110
+ </div>
111
+ <div className="h-32 rounded-md border border-border bg-card p-3">占位 1</div>
112
+ <Affix
113
+ offsetTop={0}
114
+ container={() => ref.current as HTMLElement}
115
+ >
116
+ <div className="rounded-md border border-border bg-card p-3 shadow-sm">
117
+ <Button>容器内吸顶</Button>
118
+ </div>
119
+ </Affix>
120
+ <div className="h-[600px] rounded-md border border-border bg-card p-3">长占位</div>
121
+ </div>
122
+ </div>
123
+ );
124
+ },
125
+ };
126
+
127
+ // ─── 关于"默认 window 模式"的说明 ─────────────────────────────────────────────
128
+ //
129
+ // 默认形态 `<Affix offsetTop={64}>`(不传 `container`)监听 `window` 并切
130
+ // `position: fixed`,在**真实业务页面上零配置即可工作**。Storybook autodocs 受
131
+ // iframe 限制(共享 canvas 不暴露独立 window scroll;`inline:false` 独立 iframe
132
+ // 又拿不到 toolbar 切换后的 globals.theme,导致主题回退),无法稳定演示该形态;
133
+ // 因此本组件仅在 Storybook 中演示 container 模式(三件 sticky stories),
134
+ // 默认 window 模式以 meta.md 文档 + 业务侧实测为准。
@@ -12,17 +12,36 @@ export interface AffixProps extends React.HTMLAttributes<HTMLDivElement> {
12
12
  * 滚动到此偏移量(相对视口底部)时开始吸底 — 与 `offsetTop` 互斥。
13
13
  */
14
14
  offsetBottom?: number;
15
+ /**
16
+ * 滚动容器(antd `target` 并集) — 返回需要监听 `scroll` / `resize` 事件的滚动容器,
17
+ * 默认 `window`。常见场景:在内层 `overflow:auto` 容器(对话框 / 抽屉 / 自定义滚动区)中
18
+ * 让 Affix 跟随该容器滚动,而非主视口。
19
+ *
20
+ * 切到自定义容器后,内部使用 `position: sticky` 由浏览器原生处理"吸附后跟随容器视口顶部"
21
+ * (无需手动算 scrollTop 偏移),容器只需保证未被 `overflow: hidden` 切割。
22
+ * @default () => window
23
+ */
24
+ container?: () => HTMLElement | Window;
15
25
  /**
16
26
  * 吸附状态变化回调(antd `onChange` 并集)。
17
27
  */
18
28
  onAffixChange?: (affixed: boolean) => void;
19
29
  }
20
30
 
31
+ const isWindow = (target: HTMLElement | Window): target is Window =>
32
+ target === window;
33
+
21
34
  /**
22
35
  * 滚动吸顶 / 吸底 — antd 独有补足。**等价 antd `Affix`**。
23
- * 当容器滚出指定偏移时,把 children 切换为 `position: fixed`,保留原占位避免页面跳动。
24
36
  *
25
- * 实现:监听 `scroll` 事件 + 节点 `getBoundingClientRect()`,无依赖。
37
+ * 实现分两路:
38
+ * - **window 模式**(不传 `container`):监听 `window` 滚动 + `position: fixed` + 占位高度,
39
+ * 保持页面布局稳定,吸顶后元素脱离文档流跟随视口
40
+ * - **container 模式**(传 `container`):用 `position: sticky` 由浏览器原生托管,
41
+ * 滚动监听仅用于触发 `onAffixChange` 与 `shadow-sm` 视觉反馈;
42
+ * 容器需保证非 `overflow: hidden`(`overflow: auto/scroll` 即可),
43
+ * 且祖先链中不能有 `transform` / `filter` 等 stacking context 限定
44
+ *
26
45
  * **务必慎用** — 滥用 Affix 会让页面充满浮动元素,通常一页 ≤ 1 个。
27
46
  */
28
47
  const Affix = React.forwardRef<HTMLDivElement, AffixProps>(
@@ -30,6 +49,7 @@ const Affix = React.forwardRef<HTMLDivElement, AffixProps>(
30
49
  {
31
50
  offsetTop = 0,
32
51
  offsetBottom,
52
+ container,
33
53
  onAffixChange,
34
54
  className,
35
55
  style,
@@ -41,18 +61,38 @@ const Affix = React.forwardRef<HTMLDivElement, AffixProps>(
41
61
  const wrapperRef = React.useRef<HTMLDivElement | null>(null);
42
62
  React.useImperativeHandle(ref, () => wrapperRef.current as HTMLDivElement);
43
63
  const [affixed, setAffixed] = React.useState(false);
44
- const [size, setSize] = React.useState<{ w: number; h: number }>({ w: 0, h: 0 });
64
+ const [size, setSize] = React.useState<{ w: number; h: number }>({
65
+ w: 0,
66
+ h: 0,
67
+ });
45
68
 
46
69
  React.useEffect(() => {
47
70
  const el = wrapperRef.current;
48
71
  if (!el) return;
49
72
 
73
+ const target = container ? container() : window;
74
+ const useCustomContainer = !isWindow(target);
75
+
50
76
  const compute = () => {
51
77
  const rect = el.getBoundingClientRect();
52
78
  const useBottom = typeof offsetBottom === 'number';
79
+
80
+ let top: number;
81
+ let viewport: number;
82
+ if (useCustomContainer) {
83
+ const cRect = (target as HTMLElement).getBoundingClientRect();
84
+ top = rect.top - cRect.top;
85
+ viewport = cRect.height;
86
+ } else {
87
+ top = rect.top;
88
+ viewport = window.innerHeight;
89
+ }
90
+
91
+ const bottom = viewport - (top + rect.height);
53
92
  const shouldAffix = useBottom
54
- ? window.innerHeight - rect.bottom < (offsetBottom ?? 0)
55
- : rect.top < offsetTop;
93
+ ? bottom < (offsetBottom ?? 0)
94
+ : top < offsetTop;
95
+
56
96
  if (shouldAffix !== affixed) {
57
97
  setAffixed(shouldAffix);
58
98
  onAffixChange?.(shouldAffix);
@@ -61,14 +101,40 @@ const Affix = React.forwardRef<HTMLDivElement, AffixProps>(
61
101
  };
62
102
 
63
103
  compute();
64
- window.addEventListener('scroll', compute, { passive: true });
104
+ target.addEventListener('scroll', compute, { passive: true });
65
105
  window.addEventListener('resize', compute);
66
106
  return () => {
67
- window.removeEventListener('scroll', compute);
107
+ target.removeEventListener('scroll', compute);
68
108
  window.removeEventListener('resize', compute);
69
109
  };
70
- }, [affixed, offsetBottom, offsetTop, onAffixChange]);
110
+ }, [affixed, offsetBottom, offsetTop, onAffixChange, container]);
111
+
112
+ // ─── Container 模式:position: sticky 由浏览器原生托管 ─────────────────────
113
+ if (container) {
114
+ const stickyStyle: React.CSSProperties = {
115
+ position: 'sticky',
116
+ top: typeof offsetBottom === 'number' ? undefined : offsetTop,
117
+ bottom: typeof offsetBottom === 'number' ? offsetBottom : undefined,
118
+ zIndex: 10,
119
+ ...style,
120
+ };
121
+ return (
122
+ <div
123
+ ref={wrapperRef}
124
+ style={stickyStyle}
125
+ className={cn(
126
+ 'transition-shadow',
127
+ affixed && 'shadow-sm',
128
+ className,
129
+ )}
130
+ {...props}
131
+ >
132
+ {children}
133
+ </div>
134
+ );
135
+ }
71
136
 
137
+ // ─── Window 模式:position: fixed + 占位高度 ─────────────────────────────
72
138
  const fixedStyle: React.CSSProperties | undefined = affixed
73
139
  ? {
74
140
  position: 'fixed',
@@ -85,7 +151,11 @@ const Affix = React.forwardRef<HTMLDivElement, AffixProps>(
85
151
  style={{ ...style, height: affixed ? size.h : undefined }}
86
152
  className={className}
87
153
  >
88
- <div style={fixedStyle} className={cn(affixed && 'shadow-sm')} {...props}>
154
+ <div
155
+ style={fixedStyle}
156
+ className={cn(affixed && 'shadow-sm')}
157
+ {...props}
158
+ >
89
159
  {children}
90
160
  </div>
91
161
  </div>