@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.
- package/README.md +184 -184
- package/manifest.json +680 -492
- package/package.json +20 -10
- package/src/components/accordion/accordion.meta.md +5 -4
- package/src/components/accordion/accordion.stories.tsx +14 -9
- package/src/components/accordion/accordion.tsx +104 -8
- package/src/components/affix/affix.meta.md +20 -2
- package/src/components/affix/affix.stories.tsx +102 -25
- package/src/components/affix/affix.tsx +79 -9
- package/src/components/alert/alert.meta.md +44 -13
- package/src/components/alert/alert.stories.tsx +66 -21
- package/src/components/alert/alert.tsx +81 -34
- package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
- package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
- package/src/components/alert-dialog/alert-dialog.tsx +60 -13
- package/src/components/anchor/anchor.meta.md +8 -3
- package/src/components/anchor/anchor.stories.tsx +3 -3
- package/src/components/anchor/anchor.tsx +2 -2
- package/src/components/app/app.meta.md +9 -4
- package/src/components/app/app.stories.tsx +9 -7
- package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
- package/src/components/auto-complete/auto-complete.meta.md +14 -6
- package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
- package/src/components/auto-complete/auto-complete.tsx +119 -71
- package/src/components/avatar/avatar.meta.md +6 -7
- package/src/components/avatar/avatar.stories.tsx +21 -3
- package/src/components/avatar/avatar.tsx +24 -23
- package/src/components/badge/badge.meta.md +10 -9
- package/src/components/badge/badge.stories.tsx +2 -2
- package/src/components/badge/badge.tsx +9 -15
- package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
- package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
- package/src/components/breadcrumb/breadcrumb.tsx +22 -8
- package/src/components/button/button.meta.md +258 -21
- package/src/components/button/button.stories.tsx +549 -41
- package/src/components/button/button.tsx +335 -33
- package/src/components/button/demo/as-child.tsx +24 -0
- package/src/components/button/demo/basic.tsx +8 -0
- package/src/components/button/demo/block.tsx +16 -0
- package/src/components/button/demo/loading.tsx +19 -0
- package/src/components/button/demo/shapes.tsx +18 -0
- package/src/components/button/demo/sizes.tsx +19 -0
- package/src/components/button/demo/variants.tsx +19 -0
- package/src/components/button/demo/with-icon.tsx +20 -0
- package/src/components/calendar/calendar.meta.md +13 -3
- package/src/components/calendar/calendar.stories.tsx +6 -6
- package/src/components/calendar/calendar.tsx +73 -8
- package/src/components/card/card.meta.md +27 -5
- package/src/components/card/card.stories.tsx +42 -3
- package/src/components/card/card.tsx +146 -63
- package/src/components/carousel/carousel.meta.md +4 -3
- package/src/components/carousel/carousel.stories.tsx +11 -6
- package/src/components/cascader/cascader.meta.md +47 -17
- package/src/components/cascader/cascader.stories.tsx +22 -10
- package/src/components/cascader/cascader.tsx +428 -85
- package/src/components/checkbox/checkbox.meta.md +75 -7
- package/src/components/checkbox/checkbox.stories.tsx +161 -3
- package/src/components/checkbox/checkbox.tsx +77 -9
- package/src/components/collapsible/collapsible.meta.md +14 -6
- package/src/components/collapsible/collapsible.stories.tsx +10 -2
- package/src/components/collapsible/collapsible.tsx +93 -6
- package/src/components/color-picker/color-picker.meta.md +12 -7
- package/src/components/color-picker/color-picker.stories.tsx +86 -7
- package/src/components/color-picker/color-picker.tsx +20 -9
- package/src/components/command/command.meta.md +29 -13
- package/src/components/command/command.stories.tsx +4 -4
- package/src/components/command/command.tsx +19 -8
- package/src/components/context-menu/context-menu.meta.md +11 -8
- package/src/components/context-menu/context-menu.stories.tsx +11 -3
- package/src/components/context-menu/context-menu.tsx +21 -8
- package/src/components/data-table/data-table.meta.md +6 -5
- package/src/components/data-table/data-table.stories.tsx +13 -6
- package/src/components/data-table/data-table.tsx +2 -2
- package/src/components/date-picker/date-picker.meta.md +88 -19
- package/src/components/date-picker/date-picker.stories.tsx +55 -5
- package/src/components/date-picker/date-picker.tsx +1489 -91
- package/src/components/descriptions/descriptions.meta.md +10 -5
- package/src/components/descriptions/descriptions.stories.tsx +3 -3
- package/src/components/descriptions/descriptions.tsx +22 -14
- package/src/components/dialog/dialog.meta.md +76 -13
- package/src/components/dialog/dialog.stories.tsx +182 -20
- package/src/components/dialog/dialog.tsx +67 -15
- package/src/components/dialog/imperative.tsx +252 -0
- package/src/components/drawer/drawer.meta.md +33 -34
- package/src/components/drawer/drawer.stories.tsx +29 -12
- package/src/components/drawer/drawer.tsx +22 -113
- package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
- package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
- package/src/components/ellipsis/ellipsis.meta.md +87 -0
- package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
- package/src/components/ellipsis/ellipsis.tsx +153 -0
- package/src/components/empty/empty.meta.md +9 -4
- package/src/components/empty/empty.stories.tsx +4 -4
- package/src/components/empty/empty.tsx +10 -3
- package/src/components/field/field.meta.md +47 -9
- package/src/components/field/field.stories.tsx +385 -5
- package/src/components/field/field.tsx +263 -35
- package/src/components/filter-bar/filter-bar.meta.md +92 -0
- package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
- package/src/components/filter-bar/filter-bar.tsx +568 -0
- package/src/components/flex/flex.meta.md +54 -6
- package/src/components/flex/flex.stories.tsx +107 -20
- package/src/components/flex/flex.tsx +27 -4
- package/src/components/float-button/float-button.meta.md +8 -3
- package/src/components/float-button/float-button.stories.tsx +9 -7
- package/src/components/float-button/float-button.tsx +1 -1
- package/src/components/form/form.meta.md +39 -17
- package/src/components/form/form.stories.tsx +350 -3
- package/src/components/form/form.tsx +101 -35
- package/src/components/grid/grid.meta.md +7 -2
- package/src/components/grid/grid.stories.tsx +6 -4
- package/src/components/hover-card/hover-card.meta.md +20 -9
- package/src/components/hover-card/hover-card.stories.tsx +34 -5
- package/src/components/hover-card/hover-card.tsx +51 -13
- package/src/components/icon/DEVELOPMENT.md +809 -0
- package/src/components/icon/icon.meta.md +170 -0
- package/src/components/icon/icon.stories.tsx +344 -0
- package/src/components/icon/icon.tsx +248 -0
- package/src/components/image/image.meta.md +9 -4
- package/src/components/image/image.stories.tsx +3 -3
- package/src/components/image/image.tsx +6 -4
- package/src/components/input/demo/basic.tsx +12 -0
- package/src/components/input/demo/clearable.tsx +21 -0
- package/src/components/input/demo/show-count.tsx +18 -0
- package/src/components/input/demo/sizes.tsx +15 -0
- package/src/components/input/input.meta.md +39 -33
- package/src/components/input/input.stories.tsx +62 -35
- package/src/components/input/input.tsx +97 -98
- package/src/components/input-group/input-group.meta.md +54 -22
- package/src/components/input-group/input-group.stories.tsx +49 -16
- package/src/components/input-group/input-group.tsx +44 -8
- package/src/components/input-number/input-number.meta.md +64 -7
- package/src/components/input-number/input-number.stories.tsx +46 -8
- package/src/components/input-number/input-number.tsx +99 -26
- package/src/components/input-otp/input-otp.meta.md +4 -3
- package/src/components/input-otp/input-otp.stories.tsx +3 -3
- package/src/components/input-otp/input-otp.tsx +1 -1
- package/src/components/item/item.meta.md +8 -3
- package/src/components/item/item.stories.tsx +8 -5
- package/src/components/item/item.tsx +7 -6
- package/src/components/kbd/kbd.meta.md +13 -4
- package/src/components/kbd/kbd.stories.tsx +4 -4
- package/src/components/kbd/kbd.tsx +10 -5
- package/src/components/label/label.meta.md +18 -10
- package/src/components/label/label.stories.tsx +64 -6
- package/src/components/label/label.tsx +91 -19
- package/src/components/masonry/masonry.meta.md +8 -3
- package/src/components/masonry/masonry.stories.tsx +7 -5
- package/src/components/masonry/masonry.tsx +1 -0
- package/src/components/mentions/mentions.meta.md +36 -6
- package/src/components/mentions/mentions.stories.tsx +120 -6
- package/src/components/mentions/mentions.tsx +11 -5
- package/src/components/menubar/menubar.meta.md +30 -12
- package/src/components/menubar/menubar.stories.tsx +62 -2
- package/src/components/menubar/menubar.tsx +9 -9
- package/src/components/native-select/native-select.meta.md +8 -3
- package/src/components/native-select/native-select.stories.tsx +8 -5
- package/src/components/native-select/native-select.tsx +1 -1
- package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
- package/src/components/navigation-menu/navigation-menu.tsx +8 -4
- package/src/components/notification/notification.meta.md +52 -10
- package/src/components/notification/notification.stories.tsx +11 -9
- package/src/components/notification/notification.tsx +36 -21
- package/src/components/page-header/DEVELOPMENT.md +842 -0
- package/src/components/page-header/page-header.meta.md +208 -0
- package/src/components/page-header/page-header.stories.tsx +421 -0
- package/src/components/page-header/page-header.tsx +281 -0
- package/src/components/pagination/pagination.meta.md +140 -37
- package/src/components/pagination/pagination.stories.tsx +232 -10
- package/src/components/pagination/pagination.tsx +355 -63
- package/src/components/popconfirm/popconfirm.meta.md +9 -4
- package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
- package/src/components/popconfirm/popconfirm.tsx +2 -2
- package/src/components/popover/popover.meta.md +62 -5
- package/src/components/popover/popover.stories.tsx +83 -7
- package/src/components/popover/popover.tsx +77 -28
- package/src/components/progress/progress.meta.md +38 -6
- package/src/components/progress/progress.stories.tsx +3 -3
- package/src/components/progress/progress.tsx +24 -16
- package/src/components/radio-group/radio-group.meta.md +79 -7
- package/src/components/radio-group/radio-group.stories.tsx +39 -3
- package/src/components/radio-group/radio-group.tsx +149 -18
- package/src/components/rate/rate.meta.md +35 -4
- package/src/components/rate/rate.stories.tsx +13 -5
- package/src/components/rate/rate.tsx +37 -10
- package/src/components/resizable/resizable.meta.md +7 -4
- package/src/components/resizable/resizable.stories.tsx +6 -6
- package/src/components/resizable/resizable.tsx +1 -1
- package/src/components/result/result.meta.md +7 -2
- package/src/components/result/result.stories.tsx +4 -8
- package/src/components/result/result.tsx +24 -15
- package/src/components/scroll-area/scroll-area.meta.md +4 -3
- package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
- package/src/components/scroll-area/scroll-area.tsx +3 -3
- package/src/components/segmented/segmented.meta.md +7 -4
- package/src/components/segmented/segmented.stories.tsx +37 -8
- package/src/components/segmented/segmented.tsx +15 -7
- package/src/components/select/select.meta.md +197 -52
- package/src/components/select/select.stories.tsx +238 -63
- package/src/components/select/select.tsx +718 -171
- package/src/components/separator/separator.meta.md +4 -3
- package/src/components/separator/separator.stories.tsx +3 -3
- package/src/components/separator/separator.tsx +3 -7
- package/src/components/sheet/sheet.meta.md +32 -16
- package/src/components/sheet/sheet.stories.tsx +116 -10
- package/src/components/sheet/sheet.tsx +116 -29
- package/src/components/sidebar/sidebar.meta.md +37 -18
- package/src/components/sidebar/sidebar.stories.tsx +701 -29
- package/src/components/sidebar/sidebar.tsx +615 -142
- package/src/components/skeleton/skeleton.meta.md +4 -5
- package/src/components/skeleton/skeleton.stories.tsx +4 -4
- package/src/components/skeleton/skeleton.tsx +7 -7
- package/src/components/slider/slider.meta.md +57 -5
- package/src/components/slider/slider.stories.tsx +58 -6
- package/src/components/slider/slider.tsx +154 -13
- package/src/components/sonner/sonner.meta.md +58 -7
- package/src/components/sonner/sonner.stories.tsx +78 -5
- package/src/components/sonner/sonner.tsx +137 -8
- package/src/components/spinner/spinner.meta.md +62 -13
- package/src/components/spinner/spinner.stories.tsx +66 -14
- package/src/components/spinner/spinner.tsx +111 -9
- package/src/components/statistic/statistic.meta.md +7 -2
- package/src/components/statistic/statistic.stories.tsx +3 -7
- package/src/components/statistic/statistic.tsx +5 -6
- package/src/components/steps/steps.meta.md +18 -4
- package/src/components/steps/steps.stories.tsx +43 -3
- package/src/components/steps/steps.tsx +15 -12
- package/src/components/switch/switch.meta.md +51 -5
- package/src/components/switch/switch.stories.tsx +6 -6
- package/src/components/switch/switch.tsx +109 -41
- package/src/components/table/table.meta.md +17 -6
- package/src/components/table/table.stories.tsx +10 -5
- package/src/components/table/table.tsx +4 -4
- package/src/components/tabs/tabs.meta.md +38 -25
- package/src/components/tabs/tabs.stories.tsx +111 -25
- package/src/components/tabs/tabs.tsx +125 -54
- package/src/components/tag/tag.meta.md +105 -40
- package/src/components/tag/tag.stories.tsx +189 -16
- package/src/components/tag/tag.tsx +222 -21
- package/src/components/textarea/textarea.meta.md +35 -19
- package/src/components/textarea/textarea.stories.tsx +32 -6
- package/src/components/textarea/textarea.tsx +33 -9
- package/src/components/time-picker/time-picker.meta.md +124 -32
- package/src/components/time-picker/time-picker.stories.tsx +85 -15
- package/src/components/time-picker/time-picker.tsx +913 -61
- package/src/components/timeline/timeline.meta.md +14 -6
- package/src/components/timeline/timeline.stories.tsx +37 -7
- package/src/components/timeline/timeline.tsx +35 -14
- package/src/components/toggle/toggle.meta.md +5 -4
- package/src/components/toggle/toggle.stories.tsx +4 -4
- package/src/components/toggle/toggle.tsx +4 -3
- package/src/components/toggle-group/toggle-group.meta.md +5 -4
- package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
- package/src/components/toggle-group/toggle-group.tsx +2 -2
- package/src/components/tooltip/tooltip.meta.md +55 -5
- package/src/components/tooltip/tooltip.stories.tsx +42 -5
- package/src/components/tooltip/tooltip.tsx +81 -21
- package/src/components/tour/tour.meta.md +9 -4
- package/src/components/tour/tour.stories.tsx +3 -3
- package/src/components/tour/tour.tsx +4 -4
- package/src/components/transfer/transfer.meta.md +11 -6
- package/src/components/transfer/transfer.stories.tsx +4 -8
- package/src/components/transfer/transfer.tsx +28 -21
- package/src/components/tree/tree.meta.md +63 -5
- package/src/components/tree/tree.stories.tsx +31 -12
- package/src/components/tree/tree.tsx +9 -8
- package/src/components/tree-select/tree-select.meta.md +59 -8
- package/src/components/tree-select/tree-select.stories.tsx +3 -3
- package/src/components/tree-select/tree-select.tsx +42 -7
- package/src/components/typography/typography.meta.md +61 -14
- package/src/components/typography/typography.stories.tsx +12 -11
- package/src/components/typography/typography.tsx +43 -28
- package/src/components/upload/upload.meta.md +49 -4
- package/src/components/upload/upload.stories.tsx +72 -12
- package/src/components/upload/upload.tsx +170 -37
- package/src/components/watermark/watermark.meta.md +7 -2
- package/src/components/watermark/watermark.stories.tsx +101 -9
- package/src/components/watermark/watermark.tsx +1 -0
- package/src/hooks/use-breakpoint.ts +117 -0
- package/src/hooks/use-debounce-callback.ts +52 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/stories/theme-tokens.stories.tsx +747 -0
- package/src/utils/trigger-input.ts +53 -0
- package/src/components/button-group/button-group.meta.md +0 -92
- package/src/components/button-group/button-group.stories.tsx +0 -90
- package/src/components/button-group/button-group.tsx +0 -75
- package/src/components/combobox/combobox.meta.md +0 -93
- package/src/components/combobox/combobox.stories.tsx +0 -55
- package/src/components/combobox/combobox.tsx +0 -130
- package/src/components/space/space.meta.md +0 -94
- package/src/components/space/space.stories.tsx +0 -94
- 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.
|
|
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-
|
|
45
|
-
"@storybook/react": "^
|
|
46
|
-
"@storybook/react-vite": "^
|
|
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": "^
|
|
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/
|
|
76
|
-
"@teamix-evo/
|
|
77
|
-
"@teamix-evo/
|
|
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
|
-
"
|
|
88
|
-
"
|
|
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:
|
|
6
|
+
category: data-display
|
|
6
7
|
since: 0.1.0
|
|
7
|
-
package:
|
|
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
|
|
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: '
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
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-
|
|
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:
|
|
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
|
|
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
|
-
'
|
|
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
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<div className="
|
|
36
|
-
<
|
|
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
|
-
</
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
),
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
42
65
|
};
|
|
43
66
|
|
|
44
67
|
export const Bottom: Story = {
|
|
45
68
|
parameters: { controls: { disable: true } },
|
|
46
|
-
render: () =>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
</
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
*
|
|
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 }>({
|
|
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
|
-
?
|
|
55
|
-
:
|
|
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
|
-
|
|
104
|
+
target.addEventListener('scroll', compute, { passive: true });
|
|
65
105
|
window.addEventListener('resize', compute);
|
|
66
106
|
return () => {
|
|
67
|
-
|
|
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
|
|
154
|
+
<div
|
|
155
|
+
style={fixedStyle}
|
|
156
|
+
className={cn(affixed && 'shadow-sm')}
|
|
157
|
+
{...props}
|
|
158
|
+
>
|
|
89
159
|
{children}
|
|
90
160
|
</div>
|
|
91
161
|
</div>
|