@rainbow-oh/yee-c 0.9.1 → 0.10.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 (127) hide show
  1. package/dist/cjs/Badge/badge.js +5 -3
  2. package/dist/cjs/Badge/interface.d.ts +4 -0
  3. package/dist/cjs/Input/style/index.less +1 -0
  4. package/dist/cjs/Popconfirm/interface.d.ts +0 -4
  5. package/dist/cjs/Select/select.js +2 -1
  6. package/dist/cjs/TableSelect/table-select.js +2 -2
  7. package/dist/cjs/Tabs/demo/overflow-center.d.ts +3 -0
  8. package/dist/cjs/Tabs/demo/overflow-center.js +54 -0
  9. package/dist/cjs/Tabs/hooks/useNavListMove.d.ts +2 -1
  10. package/dist/cjs/Tabs/hooks/useNavListMove.js +51 -16
  11. package/dist/cjs/Tabs/hooks/useTabScroll.d.ts +1 -0
  12. package/dist/cjs/Tabs/hooks/useTabScroll.js +5 -1
  13. package/dist/cjs/Tabs/interface.d.ts +9 -0
  14. package/dist/cjs/Tabs/style/index.less +5 -0
  15. package/dist/cjs/Tabs/tab-header/index.d.ts +1 -0
  16. package/dist/cjs/Tabs/tab-header/index.js +9 -2
  17. package/dist/cjs/Tabs/tabs.js +4 -1
  18. package/dist/cjs/TreeSelect/tree-select.js +1 -1
  19. package/dist/cjs/Upload/demo/auto-upload.d.ts +3 -0
  20. package/dist/cjs/Upload/demo/auto-upload.js +65 -0
  21. package/dist/cjs/Upload/demo/basic.js +5 -10
  22. package/dist/cjs/Upload/demo/controlled.js +4 -4
  23. package/dist/cjs/Upload/demo/pictureWall.js +14 -13
  24. package/dist/cjs/Upload/dragger.d.ts +2 -1
  25. package/dist/cjs/Upload/dragger.js +32 -21
  26. package/dist/cjs/Upload/index.d.ts +1 -1
  27. package/dist/cjs/Upload/interface.d.ts +67 -120
  28. package/dist/cjs/Upload/style/index.less +40 -13
  29. package/dist/cjs/Upload/upload.d.ts +2 -2
  30. package/dist/cjs/Upload/upload.js +435 -185
  31. package/dist/cjs/Upload/uploaded-list/index.d.ts +2 -1
  32. package/dist/cjs/Upload/uploaded-list/index.js +6 -3
  33. package/dist/cjs/Upload/uploaded-list/uploaded-item.d.ts +6 -3
  34. package/dist/cjs/Upload/uploaded-list/uploaded-item.js +127 -138
  35. package/dist/cjs/style/size.css +1 -1
  36. package/dist/cjs/style/themes.css +1 -1
  37. package/dist/cjs/style/variables.css +2 -2
  38. package/dist/cjs/utils/ax/core.d.ts +3 -1
  39. package/dist/cjs/utils/ax/core.js +24 -37
  40. package/dist/cjs/utils/ax/index.d.ts +3 -1
  41. package/dist/cjs/utils/folder-scanner.d.ts +1 -1
  42. package/dist/cjs/utils/folder-scanner.js +43 -34
  43. package/dist/cjs/version.d.ts +1 -1
  44. package/dist/cjs/version.js +1 -1
  45. package/dist/esm/Badge/badge.js +3 -2
  46. package/dist/esm/Badge/interface.d.ts +4 -0
  47. package/dist/esm/Input/style/index.less +1 -0
  48. package/dist/esm/Popconfirm/interface.d.ts +0 -4
  49. package/dist/esm/Select/select.js +2 -1
  50. package/dist/esm/TableSelect/table-select.js +2 -2
  51. package/dist/esm/Tabs/demo/overflow-center.d.ts +3 -0
  52. package/dist/esm/Tabs/demo/overflow-center.js +61 -0
  53. package/dist/esm/Tabs/hooks/useNavListMove.d.ts +2 -1
  54. package/dist/esm/Tabs/hooks/useNavListMove.js +62 -25
  55. package/dist/esm/Tabs/hooks/useTabScroll.d.ts +1 -0
  56. package/dist/esm/Tabs/hooks/useTabScroll.js +9 -0
  57. package/dist/esm/Tabs/interface.d.ts +9 -0
  58. package/dist/esm/Tabs/style/index.less +5 -0
  59. package/dist/esm/Tabs/tab-header/index.d.ts +1 -0
  60. package/dist/esm/Tabs/tab-header/index.js +13 -2
  61. package/dist/esm/Tabs/tabs.js +4 -2
  62. package/dist/esm/TreeSelect/tree-select.js +1 -1
  63. package/dist/esm/Upload/demo/auto-upload.d.ts +3 -0
  64. package/dist/esm/Upload/demo/auto-upload.js +42 -0
  65. package/dist/esm/Upload/demo/basic.js +5 -11
  66. package/dist/esm/Upload/demo/controlled.js +8 -6
  67. package/dist/esm/Upload/demo/pictureWall.js +17 -14
  68. package/dist/esm/Upload/dragger.d.ts +2 -1
  69. package/dist/esm/Upload/dragger.js +10 -5
  70. package/dist/esm/Upload/index.d.ts +1 -1
  71. package/dist/esm/Upload/interface.d.ts +67 -120
  72. package/dist/esm/Upload/style/index.less +40 -13
  73. package/dist/esm/Upload/upload.d.ts +2 -2
  74. package/dist/esm/Upload/upload.js +573 -271
  75. package/dist/esm/Upload/uploaded-list/index.d.ts +2 -1
  76. package/dist/esm/Upload/uploaded-list/index.js +9 -4
  77. package/dist/esm/Upload/uploaded-list/uploaded-item.d.ts +6 -3
  78. package/dist/esm/Upload/uploaded-list/uploaded-item.js +145 -150
  79. package/dist/esm/style/size.css +1 -1
  80. package/dist/esm/style/themes.css +1 -1
  81. package/dist/esm/style/variables.css +2 -2
  82. package/dist/esm/utils/ax/core.d.ts +3 -1
  83. package/dist/esm/utils/ax/core.js +32 -40
  84. package/dist/esm/utils/ax/index.d.ts +3 -1
  85. package/dist/esm/utils/folder-scanner.d.ts +1 -1
  86. package/dist/esm/utils/folder-scanner.js +76 -75
  87. package/dist/esm/version.d.ts +1 -1
  88. package/dist/esm/version.js +1 -1
  89. package/dist/umd/yee-c.min.css +2 -2
  90. package/dist/umd/yee-c.min.js +1 -1
  91. package/package.json +1 -1
  92. package/src/Badge/badge.tsx +2 -1
  93. package/src/Badge/interface.ts +4 -0
  94. package/src/Input/style/index.less +1 -0
  95. package/src/Popconfirm/interface.ts +0 -4
  96. package/src/Select/select.tsx +2 -1
  97. package/src/TableSelect/table-select.tsx +1 -0
  98. package/src/Tabs/demo/overflow-center.tsx +35 -0
  99. package/src/Tabs/hooks/useNavListMove.ts +60 -18
  100. package/src/Tabs/hooks/useTabScroll.ts +7 -1
  101. package/src/Tabs/index.en-US.md +33 -31
  102. package/src/Tabs/index.zh-CN.md +33 -31
  103. package/src/Tabs/interface.ts +9 -0
  104. package/src/Tabs/style/index.less +5 -0
  105. package/src/Tabs/tab-header/index.tsx +12 -2
  106. package/src/Tabs/tabs.tsx +2 -0
  107. package/src/TreeSelect/tree-select.tsx +1 -1
  108. package/src/Upload/demo/auto-upload.tsx +35 -0
  109. package/src/Upload/demo/basic.tsx +7 -11
  110. package/src/Upload/demo/controlled.tsx +12 -8
  111. package/src/Upload/demo/directory.tsx +3 -4
  112. package/src/Upload/demo/drag.tsx +5 -12
  113. package/src/Upload/demo/multiple.tsx +3 -4
  114. package/src/Upload/demo/pictureWall.tsx +22 -21
  115. package/src/Upload/dragger.tsx +47 -34
  116. package/src/Upload/index.en-US.md +63 -43
  117. package/src/Upload/index.ts +8 -1
  118. package/src/Upload/index.zh-CN.md +62 -42
  119. package/src/Upload/interface.ts +68 -120
  120. package/src/Upload/style/index.less +40 -13
  121. package/src/Upload/upload.tsx +546 -215
  122. package/src/Upload/uploaded-list/index.tsx +9 -5
  123. package/src/Upload/uploaded-list/uploaded-item.tsx +199 -228
  124. package/src/style/size.css +1 -1
  125. package/src/utils/ax/core.ts +28 -41
  126. package/src/utils/folder-scanner.ts +51 -37
  127. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-oh/yee-c",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "yee basic components",
5
5
  "keywords": [
6
6
  "react",
@@ -27,6 +27,7 @@ const InternalBadge: React.FC<BadgeProps> = (baseprops) => {
27
27
  className,
28
28
  styles,
29
29
  classNames,
30
+ title,
30
31
  ...rest
31
32
  } = props;
32
33
 
@@ -43,7 +44,7 @@ const InternalBadge: React.FC<BadgeProps> = (baseprops) => {
43
44
  if (count === null || count === undefined) return null;
44
45
 
45
46
  if (typeof count === 'number') {
46
- supTitle = String(count);
47
+ supTitle = title || String(count);
47
48
 
48
49
  // When count is 0 and showZero is false, return null
49
50
  if (count === 0 && !showZero) return null;
@@ -27,6 +27,10 @@ export interface BadgeProps extends DataAttributeProps {
27
27
  * Child component
28
28
  */
29
29
  children?: React.ReactNode;
30
+ /**
31
+ * Native Title
32
+ */
33
+ title?: string;
30
34
  /**
31
35
  * Number to display or custom content
32
36
  */
@@ -13,6 +13,7 @@
13
13
  border: var(--yee-input-border-width) solid var(--yee-input-border-color);
14
14
  border-radius: var(--yee-input-border-radius, 4px);
15
15
  font-family: var(--yee-font-family);
16
+ outline: inherit;
16
17
 
17
18
  &:not(@{pls}-disabled):hover {
18
19
  border-color: var(--yee-input-hover-border-color);
@@ -47,10 +47,6 @@ export interface PopconfirmProps extends Omit<
47
47
  * open, controllered
48
48
  * */
49
49
  open?: boolean;
50
- /**
51
- * placement
52
- */
53
- placement?: 'top' | 'bottom' | 'left' | 'right';
54
50
  /**
55
51
  * 设置确认按钮文本
56
52
  * */
@@ -13,6 +13,7 @@ import mergeContextToProps from '../utils/mergeContextToProps';
13
13
  import useSelectKeyboard from './hooks/useSelectKeyboard';
14
14
  import { Option, SelectProps } from './interface';
15
15
  import Options from './options';
16
+ import Omit from '../utils/omit';
16
17
  import './style/index.less';
17
18
 
18
19
  // ---------------------------------------------------------------------------
@@ -229,7 +230,7 @@ const Select = (baseprops: SelectProps) => {
229
230
  onOpenChange={handleOpenChange}
230
231
  >
231
232
  <Selector
232
- {...rest}
233
+ {...Omit(rest, ['forceRender'])}
233
234
  mode={mode}
234
235
  value={searchValue}
235
236
  options={options}
@@ -198,6 +198,7 @@ const TableSelect = (baseprops: TableSelectProps) => {
198
198
 
199
199
  return (
200
200
  <Trigger
201
+ {...rest}
201
202
  trigger="click"
202
203
  popupClassName={`${prefixCls}-popup`}
203
204
  popup={renderPopup()}
@@ -0,0 +1,35 @@
1
+ import { Tabs } from '@rainbow-oh/yee-c';
2
+ import React, { useState } from 'react';
3
+
4
+ export default () => {
5
+ const items = Array.from({ length: 24 }, (_, i) => ({
6
+ key: `tab-${i + 1}`,
7
+ label: `Tab ${i + 1}`,
8
+ children: `Content of Tab Pane ${i + 1}`,
9
+ }));
10
+
11
+ const [centerKey, setCenterKey] = useState<string | number>('tab-1');
12
+ const [autoKey, setAutoKey] = useState<string | number>('tab-1');
13
+
14
+ return (
15
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
16
+ <div>
17
+ <p style={{ marginBottom: 8 }}>activeAlign=&quot;center&quot;</p>
18
+ <div style={{ maxWidth: 480 }} data-testid="center-wrap">
19
+ <Tabs
20
+ activeAlign="center"
21
+ items={items}
22
+ activeKey={centerKey}
23
+ onChange={setCenterKey}
24
+ />
25
+ </div>
26
+ </div>
27
+ <div>
28
+ <p style={{ marginBottom: 8 }}>default (auto)</p>
29
+ <div style={{ maxWidth: 480 }} data-testid="auto-wrap">
30
+ <Tabs items={items} activeKey={autoKey} onChange={setAutoKey} />
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
@@ -9,6 +9,7 @@ interface UseNavListMoveOptions {
9
9
  direction: 'horizontal' | 'vertical';
10
10
  activeKey: string | number;
11
11
  enabled: boolean;
12
+ align?: 'auto' | 'center';
12
13
  activetab: React.RefObject<HTMLElement | null>;
13
14
  navList: React.RefObject<HTMLDivElement | null>;
14
15
  navContainer: React.RefObject<HTMLDivElement | null>;
@@ -19,6 +20,7 @@ export function useNavListMove({
19
20
  direction,
20
21
  activeKey,
21
22
  enabled,
23
+ align = 'auto',
22
24
  activetab,
23
25
  navList,
24
26
  navContainer,
@@ -33,29 +35,64 @@ export function useNavListMove({
33
35
  return;
34
36
  }
35
37
 
36
- const [isVisible] = isInContainer(direction, tab, list, container);
37
- if (isVisible) return;
38
+ // 'auto': keep as-is when the active tab is already fully visible.
39
+ // 'center': always re-center, even for tabs already visible on the header.
40
+ if (align !== 'center') {
41
+ const [isVisible] = isInContainer(direction, tab, list, container);
42
+ if (isVisible) return;
43
+ }
44
+
45
+ let moved = false;
38
46
 
39
47
  if (direction === 'horizontal') {
40
- const [x] = getEleTranslate(list);
41
- const tabLeft = tab.offsetLeft;
42
- const tabWidth = tab.offsetWidth;
43
- const containerWidth = container.clientWidth;
48
+ if (align === 'center') {
49
+ const containerWidth = container.clientWidth;
50
+ const maxOffset = list.scrollWidth - containerWidth;
51
+ const tabCenter = tab.offsetLeft + tab.offsetWidth / 2;
44
52
 
45
- let newX = x;
53
+ let newX = Math.round(containerWidth / 2 - tabCenter);
54
+ newX = Math.max(-maxOffset, Math.min(0, newX));
46
55
 
47
- // Tab overflows on the right side
48
- if (tabLeft + tabWidth - Math.abs(x) > containerWidth) {
49
- const overflow = tabLeft + tabWidth - Math.abs(x) - containerWidth;
50
- newX = x - overflow;
51
- }
52
- // Tab overflows on the left side
53
- else if (tabLeft < Math.abs(x)) {
54
- const overflow = Math.abs(x) - tabLeft;
55
- newX = x + overflow;
56
+ const [x] = getEleTranslate(list);
57
+ if (Math.abs(newX - x) > 0.5) {
58
+ setEleTranslate(list, newX);
59
+ moved = true;
60
+ }
61
+ } else {
62
+ const [x] = getEleTranslate(list);
63
+ const tabLeft = tab.offsetLeft;
64
+ const tabWidth = tab.offsetWidth;
65
+ const containerWidth = container.clientWidth;
66
+
67
+ let newX = x;
68
+
69
+ // Tab overflows on the right side
70
+ if (tabLeft + tabWidth - Math.abs(x) > containerWidth) {
71
+ const overflow = tabLeft + tabWidth - Math.abs(x) - containerWidth;
72
+ newX = x - overflow;
73
+ }
74
+ // Tab overflows on the left side
75
+ else if (tabLeft < Math.abs(x)) {
76
+ const overflow = Math.abs(x) - tabLeft;
77
+ newX = x + overflow;
78
+ }
79
+
80
+ setEleTranslate(list, newX);
81
+ moved = true;
56
82
  }
83
+ } else if (align === 'center') {
84
+ const containerHeight = container.clientHeight;
85
+ const maxOffset = container.scrollHeight - containerHeight;
86
+ const tabCenter = tab.offsetTop + tab.offsetHeight / 2;
57
87
 
58
- setEleTranslate(list, newX);
88
+ const newScrollTop = Math.max(
89
+ 0,
90
+ Math.min(maxOffset, Math.round(tabCenter - containerHeight / 2)),
91
+ );
92
+ if (Math.abs(newScrollTop - container.scrollTop) > 0.5) {
93
+ container.scrollTop = newScrollTop;
94
+ moved = true;
95
+ }
59
96
  } else {
60
97
  const tabTop = tab.offsetTop;
61
98
  const tabHeight = tab.offsetHeight;
@@ -66,19 +103,24 @@ export function useNavListMove({
66
103
  if (tabTop + tabHeight - scrollTop >= containerHeight) {
67
104
  const overflow = tabTop + tabHeight - scrollTop - containerHeight;
68
105
  container.scrollTop = scrollTop + overflow;
106
+ moved = true;
69
107
  }
70
108
  // Tab overflows above
71
109
  else if (tabTop < scrollTop) {
72
110
  const overflow = scrollTop - tabTop;
73
111
  container.scrollTop = scrollTop - overflow;
112
+ moved = true;
74
113
  }
75
114
  }
76
115
 
77
- onMoved?.();
116
+ if (moved) {
117
+ onMoved?.();
118
+ }
78
119
  }, [
79
120
  activeKey,
80
121
  enabled,
81
122
  direction,
123
+ align,
82
124
  activetab,
83
125
  navList,
84
126
  navContainer,
@@ -43,6 +43,12 @@ export function useTabScroll({
43
43
 
44
44
  const debouncedUpdateShadow = useDebounceFunction(updateShadow, 200);
45
45
 
46
+ // Refresh dropdown range and shadows after the nav list is moved programmatically
47
+ const refresh = useCallback(() => {
48
+ setRefreshKey((prev) => prev + 1);
49
+ debouncedUpdateShadow();
50
+ }, [debouncedUpdateShadow]);
51
+
46
52
  useEffect(() => {
47
53
  const container = navContainer.current;
48
54
  const list = navList.current;
@@ -99,5 +105,5 @@ export function useTabScroll({
99
105
  updateShadow,
100
106
  ]);
101
107
 
102
- return { refreshKey, updateShadow: debouncedUpdateShadow };
108
+ return { refreshKey, refresh, updateShadow: debouncedUpdateShadow };
103
109
  }
@@ -19,43 +19,45 @@ Tabs make it easy to switch between different views.
19
19
  <code src="./demo/type.tsx" title="Type" description="Different types of tabs"></code>
20
20
  <code src="./demo/editable.tsx" title="Editable" description="Add and remove tabs"></code>
21
21
  <code src="./demo/extra.tsx" title="Extra Content" description="Extra content in tabs"></code>
22
+ <code src="./demo/overflow-center.tsx" title="Center Active Tab on Overflow" description="When tabs overflow into a dropdown, activeAlign=&quot;center&quot; scrolls the tab selected from the dropdown to the middle of the header"></code>
22
23
  <code src="./demo/controlled.tsx" title="Controlled" description="Controlled tabs"></code>
23
24
 
24
25
  ## API
25
26
 
26
27
  ### TabsProps
27
28
 
28
- | Property | Type | Description | Default |
29
- | --- | --- | --- | --- |
30
- | prefixCls | `string` | Custom class name prefix | - |
31
- | className | `string` | Custom root class name | - |
32
- | style | `React.CSSProperties` | Custom root style | - |
33
- | classNames | `Partial<Record<SemanticDOM, string>>` | Custom structure class names | - |
34
- | styles | `Partial<Record<SemanticDOM, React.CSSProperties>>` | Custom structure styles | - |
35
- | type | `'card' \| 'editable-card'` | Tab style | - |
36
- | activeKey | `string \| number` | Current active tab key | - |
37
- | defaultActiveKey | `string \| number` | Default active tab key | - |
38
- | items | `Array<TabItemType>` | Tab items | - |
39
- | position | `'top' \| 'bottom' \| 'left' \| 'right'` | Tab position | `top` |
40
- | headerExtra | `{ prefix?: React.ReactNode \| (() => React.ReactNode); suffix?: React.ReactNode \| (() => React.ReactNode) }` | Extra content in tab bar | - |
41
- | children | `Array<React.ReactElement>` | Children elements | - |
42
- | lazy | `boolean` | Lazy load content | - |
43
- | fixedHeader | `boolean` | Whether to fix header area | `false` |
44
- | onEdit | `(type: 'add' \| 'remove', key?: number \| string) => void` | Edit callback | - |
45
- | onChange | `(key: string \| number) => void` | Change callback | - |
46
- | onTabClick | `(key: string \| number) => void \| boolean` | Tab click callback | - |
47
- | data-* | `string` | - | Supports all data-* attributes, forwarded to root DOM element |
29
+ | Property | Type | Description | Default |
30
+ | ---------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
31
+ | prefixCls | `string` | Custom class name prefix | - |
32
+ | className | `string` | Custom root class name | - |
33
+ | style | `React.CSSProperties` | Custom root style | - |
34
+ | classNames | `Partial<Record<SemanticDOM, string>>` | Custom structure class names | - |
35
+ | styles | `Partial<Record<SemanticDOM, React.CSSProperties>>` | Custom structure styles | - |
36
+ | type | `'card' \| 'editable-card'` | Tab style | - |
37
+ | activeKey | `string \| number` | Current active tab key | - |
38
+ | defaultActiveKey | `string \| number` | Default active tab key | - |
39
+ | items | `Array<TabItemType>` | Tab items | - |
40
+ | position | `'top' \| 'bottom' \| 'left' \| 'right'` | Tab position | `top` |
41
+ | headerExtra | `{ prefix?: React.ReactNode \| (() => React.ReactNode); suffix?: React.ReactNode \| (() => React.ReactNode) }` | Extra content in tab bar | - |
42
+ | children | `Array<React.ReactElement>` | Children elements | - |
43
+ | lazy | `boolean` | Lazy load content | - |
44
+ | fixedHeader | `boolean` | Whether to fix header area | `false` |
45
+ | activeAlign | `'auto' \| 'center'` | How the active tab is aligned when the tab list overflows: `auto` scrolls minimally to the edge (no scrolling when already fully visible); `center` always scrolls it to the middle of the header, including tabs clicked directly on the header | `auto` |
46
+ | onEdit | `(type: 'add' \| 'remove', key?: number \| string) => void` | Edit callback | - |
47
+ | onChange | `(key: string \| number) => void` | Change callback | - |
48
+ | onTabClick | `(key: string \| number) => void \| boolean` | Tab click callback | - |
49
+ | data-\* | `string` | - | Supports all data-\* attributes, forwarded to root DOM element |
48
50
 
49
51
  ### TabItemType
50
52
 
51
- | Property | Type | Description | Default |
52
- | --- | --- | --- | --- |
53
- | key | `string \| number` | Unique key | - |
54
- | label | `React.ReactNode` | Tab label | - |
55
- | icon | `React.ReactNode` | Tab icon | - |
56
- | disabled | `boolean` | Disabled state | - |
57
- | closable | `boolean` | Whether closable (effective when type="editable-card") | `true` |
58
- | badge | `number` | Badge number | - |
59
- | items | `Array<TabItemType>` | Sub tabs | - |
60
- | children | `React.ReactNode` | Tab content | - |
61
- | data-* | `string` | - | Supports all data-* attributes, forwarded to root DOM element |
53
+ | Property | Type | Description | Default |
54
+ | -------- | -------------------- | ------------------------------------------------------ | -------------------------------------------------------------- |
55
+ | key | `string \| number` | Unique key | - |
56
+ | label | `React.ReactNode` | Tab label | - |
57
+ | icon | `React.ReactNode` | Tab icon | - |
58
+ | disabled | `boolean` | Disabled state | - |
59
+ | closable | `boolean` | Whether closable (effective when type="editable-card") | `true` |
60
+ | badge | `number` | Badge number | - |
61
+ | items | `Array<TabItemType>` | Sub tabs | - |
62
+ | children | `React.ReactNode` | Tab content | - |
63
+ | data-\* | `string` | - | Supports all data-\* attributes, forwarded to root DOM element |
@@ -19,43 +19,45 @@ toc: 'content'
19
19
  <code src="./demo/type.tsx" title="类型" description="不同类型的标签页"></code>
20
20
  <code src="./demo/editable.tsx" title="可编辑" description="可添加和删除标签页"></code>
21
21
  <code src="./demo/extra.tsx" title="额外内容" description="标签页中的额外内容"></code>
22
+ <code src="./demo/overflow-center.tsx" title="溢出时激活Tab居中" description="Tab较多出现下拉时,activeAlign=&quot;center&quot; 让从下拉选中的Tab滚动到Header中间"></code>
22
23
  <code src="./demo/controlled.tsx" title="受控" description="受控的标签页"></code>
23
24
 
24
25
  ## API
25
26
 
26
27
  ### TabsProps
27
28
 
28
- | 属性名 | 类型 | 描述 | 默认值 |
29
- | --- | --- | --- | --- |
30
- | prefixCls | `string` | 自定义类名前缀 | - |
31
- | className | `string` | 自定义根类名 | - |
32
- | style | `React.CSSProperties` | 自定义根行内样式 | - |
33
- | classNames | `Partial<Record<SemanticDOM, string>>` | 自定义结构化类名 | - |
34
- | styles | `Partial<Record<SemanticDOM, React.CSSProperties>>` | 自定义结构化样式 | - |
35
- | type | `'card' \| 'editable-card'` | 页签样式 | - |
36
- | activeKey | `string \| number` | 当前激活tab面板的key | - |
37
- | defaultActiveKey | `string \| number` | 默认激活的tab面板的key | - |
38
- | items | `Array<TabItemType>` | tab选项卡内容 | - |
39
- | position | `'top' \| 'bottom' \| 'left' \| 'right'` | 页签位置 | `top` |
40
- | headerExtra | `{ prefix?: React.ReactNode \| (() => React.ReactNode); suffix?: React.ReactNode \| (() => React.ReactNode) }` | tab额外内容 | - |
41
- | children | `Array<React.ReactElement>` | 子元素 | - |
42
- | lazy | `boolean` | 是否懒加载,只有当切换到该tab时才将对应的content加载到dom中 | - |
43
- | fixedHeader | `boolean` | 是否固定Header区域 | `false` |
44
- | onEdit | `(type: 'add' \| 'remove', key?: number \| string) => void` | 编辑Tab时的回调 | - |
45
- | onChange | `(key: string \| number) => void` | Tab改变时的回调 | - |
46
- | onTabClick | `(key: string \| number) => void \| boolean` | 点击Tab时的回调,当返回false时,不会切换Tab, 不会触发onChange | - |
47
- | data-* | `string` | - | 支持所有 data-* 属性,透传到组件根 DOM 元素 |
29
+ | 属性名 | 类型 | 描述 | 默认值 |
30
+ | ---------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
31
+ | prefixCls | `string` | 自定义类名前缀 | - |
32
+ | className | `string` | 自定义根类名 | - |
33
+ | style | `React.CSSProperties` | 自定义根行内样式 | - |
34
+ | classNames | `Partial<Record<SemanticDOM, string>>` | 自定义结构化类名 | - |
35
+ | styles | `Partial<Record<SemanticDOM, React.CSSProperties>>` | 自定义结构化样式 | - |
36
+ | type | `'card' \| 'editable-card'` | 页签样式 | - |
37
+ | activeKey | `string \| number` | 当前激活tab面板的key | - |
38
+ | defaultActiveKey | `string \| number` | 默认激活的tab面板的key | - |
39
+ | items | `Array<TabItemType>` | tab选项卡内容 | - |
40
+ | position | `'top' \| 'bottom' \| 'left' \| 'right'` | 页签位置 | `top` |
41
+ | headerExtra | `{ prefix?: React.ReactNode \| (() => React.ReactNode); suffix?: React.ReactNode \| (() => React.ReactNode) }` | tab额外内容 | - |
42
+ | children | `Array<React.ReactElement>` | 子元素 | - |
43
+ | lazy | `boolean` | 是否懒加载,只有当切换到该tab时才将对应的content加载到dom中 | - |
44
+ | fixedHeader | `boolean` | 是否固定Header区域 | `false` |
45
+ | activeAlign | `'auto' \| 'center'` | Tab溢出时激活tab的对齐方式:`auto` 贴边最小滚动(完全可见时不滚动);`center` 始终滚动到header中间居中,包括直接点击header上可见的tab | `auto` |
46
+ | onEdit | `(type: 'add' \| 'remove', key?: number \| string) => void` | 编辑Tab时的回调 | - |
47
+ | onChange | `(key: string \| number) => void` | Tab改变时的回调 | - |
48
+ | onTabClick | `(key: string \| number) => void \| boolean` | 点击Tab时的回调,当返回false时,不会切换Tab, 不会触发onChange | - |
49
+ | data-\* | `string` | - | 支持所有 data-\* 属性,透传到组件根 DOM 元素 |
48
50
 
49
51
  ### TabItemType
50
52
 
51
- | 属性名 | 类型 | 描述 | 默认值 |
52
- | --- | --- | --- | --- |
53
- | key | `string \| number` | 唯一key对应activeKey | - |
54
- | label | `React.ReactNode` | 选项卡显示内容 | - |
55
- | icon | `React.ReactNode` | 选项卡显示图标 | - |
56
- | disabled | `boolean` | 是否禁用 | - |
57
- | closable | `boolean` | 是否可关闭,当type="editable-card"时生效 | `true` |
58
- | badge | `number` | 角标 | - |
59
- | items | `Array<TabItemType>` | 子tab | - |
60
- | children | `React.ReactNode` | 显示内容 | - |
61
- | data-* | `string` | - | 支持所有 data-* 属性,透传到组件根 DOM 元素 |
53
+ | 属性名 | 类型 | 描述 | 默认值 |
54
+ | -------- | -------------------- | ---------------------------------------- | -------------------------------------------- |
55
+ | key | `string \| number` | 唯一key对应activeKey | - |
56
+ | label | `React.ReactNode` | 选项卡显示内容 | - |
57
+ | icon | `React.ReactNode` | 选项卡显示图标 | - |
58
+ | disabled | `boolean` | 是否禁用 | - |
59
+ | closable | `boolean` | 是否可关闭,当type="editable-card"时生效 | `true` |
60
+ | badge | `number` | 角标 | - |
61
+ | items | `Array<TabItemType>` | 子tab | - |
62
+ | children | `React.ReactNode` | 显示内容 | - |
63
+ | data-\* | `string` | - | 支持所有 data-\* 属性,透传到组件根 DOM 元素 |
@@ -124,4 +124,13 @@ export interface TabsProps extends DataAttributeProps {
124
124
  * @default false
125
125
  */
126
126
  fixedHeader?: boolean;
127
+ /**
128
+ * How the active tab is aligned when the tab list overflows
129
+ * - 'auto': scroll minimally so the active tab is visible at the edge;
130
+ * no scrolling when it is already fully visible
131
+ * - 'center': always scroll so the active tab is centered in the header,
132
+ * including tabs clicked directly on the header
133
+ * @default 'auto'
134
+ */
135
+ activeAlign?: 'auto' | 'center';
127
136
  }
@@ -439,6 +439,10 @@
439
439
  height: var(--yee-tabs-nav-operations-height);
440
440
  }
441
441
 
442
+ @{pls}-nav-more {
443
+ margin-left: 4px;
444
+ }
445
+
442
446
  // 下拉框
443
447
  @{pls}-operation-container {
444
448
  max-height: var(--yee-tabs-operation-container-max-height);
@@ -452,6 +456,7 @@
452
456
 
453
457
  @{pls}-tab {
454
458
  height: var(--yee-tabs-operation-tab-height);
459
+ margin: 0;
455
460
  padding: var(--yee-tabs-operation-tab-padding);
456
461
  cursor: pointer;
457
462
 
@@ -38,6 +38,7 @@ export interface TabHeaderProps {
38
38
  prefixCls: string;
39
39
  type?: 'card' | 'editable-card';
40
40
  position?: 'top' | 'bottom' | 'left' | 'right';
41
+ activeAlign?: 'auto' | 'center';
41
42
  headerExtra?: {
42
43
  prefix?: React.ReactNode | (() => React.ReactNode);
43
44
  suffix?: React.ReactNode | (() => React.ReactNode);
@@ -53,6 +54,7 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
53
54
  prefixCls,
54
55
  type,
55
56
  position = 'top',
57
+ activeAlign = 'auto',
56
58
  headerExtra,
57
59
  classNames,
58
60
  styles,
@@ -80,7 +82,7 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
80
82
  });
81
83
 
82
84
  // Scroll handling
83
- const { refreshKey } = useTabScroll({
85
+ const { refreshKey, refresh: refreshNav } = useTabScroll({
84
86
  direction,
85
87
  navContainer,
86
88
  navList,
@@ -97,15 +99,22 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
97
99
  enabled: !type, // Only show active bar for non-card types
98
100
  });
99
101
 
102
+ // Recompute dropdown visibility, dropdown range and shadows after the list is moved
103
+ const handleNavMoved = useCallback(() => {
104
+ checkVisibility();
105
+ refreshNav();
106
+ }, [checkVisibility, refreshNav]);
107
+
100
108
  // Navigation list movement
101
109
  useNavListMove({
102
110
  direction,
103
111
  activeKey,
104
112
  enabled: moreOpen,
113
+ align: activeAlign,
105
114
  activetab,
106
115
  navList,
107
116
  navContainer,
108
- onMoved: checkVisibility,
117
+ onMoved: handleNavMoved,
109
118
  });
110
119
 
111
120
  // Listen for container size changes
@@ -213,6 +222,7 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
213
222
  <DropDownTabs visible={moreOpen} items={items || []} range={range}>
214
223
  <Button
215
224
  className={`${prefixCls}-nav-more`}
225
+ size="small"
216
226
  icon={<Ellipsis size={14} strokeWidth={1.5} />}
217
227
  type="text"
218
228
  aria-label="More tabs"
package/src/Tabs/tabs.tsx CHANGED
@@ -39,6 +39,7 @@ const Tabs: FC<TabsProps> = (baseprops) => {
39
39
  activeKey,
40
40
  defaultActiveKey,
41
41
  fixedHeader,
42
+ activeAlign,
42
43
  onChange,
43
44
  onEdit,
44
45
  onTabClick,
@@ -117,6 +118,7 @@ const Tabs: FC<TabsProps> = (baseprops) => {
117
118
  <TabHeader
118
119
  {...omit(props, ['children', 'className', 'style'])}
119
120
  prefixCls={prefixCls}
121
+ activeAlign={activeAlign}
120
122
  />
121
123
  <TabContent
122
124
  prefixCls={prefixCls}
@@ -255,7 +255,7 @@ const TreeSelect = <T extends Record<string, unknown> = any>(
255
255
  onOpenChange={handleOpenChange}
256
256
  >
257
257
  <Selector
258
- {...omit(rest, ['showLine'])}
258
+ {...omit(rest, ['showLine', 'forceRender'])}
259
259
  className={`${prefixCls}-selector`}
260
260
  mode={mode}
261
261
  value={searchValue}
@@ -0,0 +1,35 @@
1
+ import {
2
+ Button,
3
+ Upload,
4
+ type UploadFile,
5
+ type UploadInstance,
6
+ } from '@rainbow-oh/yee-c';
7
+ import React, { useRef, useState } from 'react';
8
+
9
+ export default () => {
10
+ const uploadRef = useRef<UploadInstance>(null);
11
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
12
+ const readyCount = fileList.filter((file) => file.status === 'ready').length;
13
+
14
+ return (
15
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
16
+ <Upload
17
+ ref={uploadRef}
18
+ autoUpload={false}
19
+ multiple
20
+ action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
21
+ fileList={fileList}
22
+ onChange={({ fileList: nextFileList }) => setFileList(nextFileList)}
23
+ />
24
+ <div>
25
+ <Button
26
+ type="primary"
27
+ disabled={readyCount === 0}
28
+ onClick={() => uploadRef.current?.upload()}
29
+ >
30
+ Start upload ({readyCount})
31
+ </Button>
32
+ </div>
33
+ </div>
34
+ );
35
+ };