@rainbow-oh/yee-c 0.8.0 → 0.8.2

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 (188) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +40 -25
  3. package/dist/cjs/Card/card.js +7 -3
  4. package/dist/cjs/Carousel/carousel-item.js +7 -2
  5. package/dist/cjs/Carousel/carousel.js +31 -4
  6. package/dist/cjs/Cascader/cascader.js +38 -30
  7. package/dist/cjs/Cascader/column.js +17 -9
  8. package/dist/cjs/Cascader/node.js +16 -4
  9. package/dist/cjs/Cascader/search-list.js +31 -14
  10. package/dist/cjs/Cascader/utils/index.js +1 -5
  11. package/dist/cjs/Checkbox/checkbox-group.js +23 -14
  12. package/dist/cjs/Checkbox/checkbox.js +6 -3
  13. package/dist/cjs/Collapse/panel.js +10 -1
  14. package/dist/cjs/Dialog/dialog.js +22 -7
  15. package/dist/cjs/Dialog/interface.d.ts +1 -1
  16. package/dist/cjs/Dialog/style/index.less +2 -1
  17. package/dist/cjs/Drawer/drawer.js +22 -5
  18. package/dist/cjs/Form/Group.js +1 -1
  19. package/dist/cjs/Form/demo/tooltip.js +1 -3
  20. package/dist/cjs/List/hooks/useListKeyboard.js +3 -1
  21. package/dist/cjs/Menu/menu-item.js +14 -5
  22. package/dist/cjs/Menu/menu.js +26 -2
  23. package/dist/cjs/Pagination/items.js +60 -27
  24. package/dist/cjs/Pagination/pagination.js +33 -11
  25. package/dist/cjs/PickerPanel/panels/DateTimePanel/index.d.ts +1 -1
  26. package/dist/cjs/PickerPanel/panels/MonthPanel/MonthHeader.js +7 -1
  27. package/dist/cjs/Radio/radio-group.js +6 -5
  28. package/dist/cjs/Radio/radio.js +39 -29
  29. package/dist/cjs/Rate/rate.js +6 -1
  30. package/dist/cjs/Rate/star.js +31 -3
  31. package/dist/cjs/Segmented/demo/variant.js +15 -1
  32. package/dist/cjs/Segmented/index.d.ts +1 -1
  33. package/dist/cjs/Segmented/segmented.js +12 -15
  34. package/dist/cjs/Select/option.js +8 -1
  35. package/dist/cjs/Select/options.js +9 -1
  36. package/dist/cjs/Select/select.js +3 -1
  37. package/dist/cjs/Steps/step.js +19 -5
  38. package/dist/cjs/Steps/steps.js +11 -1
  39. package/dist/cjs/Table/demo/components-override.js +31 -2
  40. package/dist/cjs/Table/demo/grouping.js +7 -1
  41. package/dist/cjs/Table/hooks/useColumns.d.ts +3 -3
  42. package/dist/cjs/Table/hooks/useColumns.js +11 -14
  43. package/dist/cjs/Table/index.d.ts +1 -1
  44. package/dist/cjs/Table/style/index.less +2 -4
  45. package/dist/cjs/Table/tbody/row.js +1 -4
  46. package/dist/cjs/Table/theader/index.js +12 -4
  47. package/dist/cjs/Tabs/tab-content.js +3 -1
  48. package/dist/cjs/Tabs/tab-header/index.js +3 -1
  49. package/dist/cjs/Tabs/tab-header/tab-header-title.js +3 -1
  50. package/dist/cjs/Tour/index.d.ts +1 -1
  51. package/dist/cjs/Tree/style/index.less +1 -5
  52. package/dist/cjs/Tree/tree.js +38 -35
  53. package/dist/cjs/hooks/useDelayState.js +3 -2
  54. package/dist/cjs/hooks/useEvent.js +1 -4
  55. package/dist/cjs/hooks/useFocusManage.d.ts +1 -1
  56. package/dist/cjs/hooks/useFocusManage.js +27 -22
  57. package/dist/cjs/style/size.css +2 -0
  58. package/dist/cjs/style/themes.css +1 -1
  59. package/dist/cjs/style/variables.css +3 -1
  60. package/dist/cjs/utils/focusStack.d.ts +6 -2
  61. package/dist/cjs/utils/focusStack.js +40 -11
  62. package/dist/cjs/version.d.ts +1 -1
  63. package/dist/cjs/version.js +1 -1
  64. package/dist/esm/Card/card.js +7 -3
  65. package/dist/esm/Carousel/carousel-item.js +7 -2
  66. package/dist/esm/Carousel/carousel.js +31 -4
  67. package/dist/esm/Cascader/cascader.js +3 -1
  68. package/dist/esm/Cascader/column.js +2 -1
  69. package/dist/esm/Cascader/node.js +18 -4
  70. package/dist/esm/Cascader/search-list.js +18 -4
  71. package/dist/esm/Checkbox/checkbox-group.js +3 -1
  72. package/dist/esm/Checkbox/checkbox.js +6 -3
  73. package/dist/esm/Collapse/panel.js +11 -2
  74. package/dist/esm/Dialog/dialog.js +28 -11
  75. package/dist/esm/Dialog/interface.d.ts +1 -1
  76. package/dist/esm/Dialog/style/index.less +2 -1
  77. package/dist/esm/Drawer/drawer.js +16 -7
  78. package/dist/esm/Form/Group.js +1 -1
  79. package/dist/esm/Menu/menu-item.js +16 -5
  80. package/dist/esm/Menu/menu.js +11 -5
  81. package/dist/esm/Pagination/items.js +14 -5
  82. package/dist/esm/Pagination/pagination.js +16 -4
  83. package/dist/esm/PickerPanel/panels/DateTimePanel/index.d.ts +1 -1
  84. package/dist/esm/Radio/radio-group.js +5 -4
  85. package/dist/esm/Radio/radio.js +5 -4
  86. package/dist/esm/Rate/rate.js +6 -1
  87. package/dist/esm/Rate/star.js +12 -4
  88. package/dist/esm/Segmented/index.d.ts +1 -1
  89. package/dist/esm/Segmented/segmented.js +1 -1
  90. package/dist/esm/Steps/step.js +12 -6
  91. package/dist/esm/Steps/steps.js +3 -1
  92. package/dist/esm/Table/demo/components-override.js +1 -0
  93. package/dist/esm/Table/hooks/useColumns.d.ts +3 -3
  94. package/dist/esm/Table/hooks/useColumns.js +13 -16
  95. package/dist/esm/Table/index.d.ts +1 -1
  96. package/dist/esm/Table/style/index.less +2 -4
  97. package/dist/esm/Tabs/tab-content.js +3 -1
  98. package/dist/esm/Tabs/tab-header/index.js +3 -1
  99. package/dist/esm/Tabs/tab-header/tab-header-title.js +3 -1
  100. package/dist/esm/Tour/index.d.ts +1 -1
  101. package/dist/esm/Tree/style/index.less +1 -5
  102. package/dist/esm/Tree/tree.js +1 -1
  103. package/dist/esm/hooks/useDelayState.js +5 -2
  104. package/dist/esm/hooks/useFocusManage.d.ts +1 -1
  105. package/dist/esm/hooks/useFocusManage.js +31 -13
  106. package/dist/esm/style/size.css +2 -0
  107. package/dist/esm/style/themes.css +1 -1
  108. package/dist/esm/style/variables.css +3 -1
  109. package/dist/esm/utils/focusStack.d.ts +6 -2
  110. package/dist/esm/utils/focusStack.js +37 -14
  111. package/dist/esm/version.d.ts +1 -1
  112. package/dist/esm/version.js +1 -1
  113. package/dist/umd/yee-c.min.css +2 -2
  114. package/dist/umd/yee-c.min.js +1 -1
  115. package/package.json +10 -6
  116. package/src/Anchor/anchor.tsx +2 -2
  117. package/src/Card/card.tsx +5 -1
  118. package/src/Carousel/carousel-item.tsx +7 -1
  119. package/src/Carousel/carousel.tsx +29 -3
  120. package/src/Cascader/cascader.tsx +5 -1
  121. package/src/Cascader/column.tsx +5 -1
  122. package/src/Cascader/node.tsx +14 -2
  123. package/src/Cascader/search-list.tsx +19 -3
  124. package/src/Cascader/utils/index.ts +3 -6
  125. package/src/Checkbox/checkbox-group.tsx +6 -1
  126. package/src/Checkbox/checkbox.tsx +3 -0
  127. package/src/Collapse/panel.tsx +11 -1
  128. package/src/Dialog/dialog.tsx +35 -6
  129. package/src/Dialog/interface.ts +1 -1
  130. package/src/Dialog/style/index.less +2 -1
  131. package/src/Drawer/drawer.tsx +18 -3
  132. package/src/Form/Group.tsx +2 -4
  133. package/src/Form/demo/tooltip.tsx +2 -11
  134. package/src/Form/utils/validate.ts +1 -4
  135. package/src/List/hooks/useListKeyboard.ts +3 -3
  136. package/src/List/list.tsx +2 -1
  137. package/src/Menu/menu-item.tsx +12 -3
  138. package/src/Menu/menu.tsx +22 -5
  139. package/src/Pagination/items.tsx +21 -4
  140. package/src/Pagination/pagination.tsx +24 -4
  141. package/src/PickerPanel/panels/DatePanel/DateBody.tsx +1 -1
  142. package/src/PickerPanel/panels/DateTimePanel/index.tsx +1 -1
  143. package/src/PickerPanel/panels/MonthPanel/MonthHeader.tsx +7 -2
  144. package/src/PickerPanel/panels/PanelBody.tsx +1 -1
  145. package/src/PickerPanel/panels/QuarterPanel/QuarterHeader.tsx +1 -1
  146. package/src/PickerPanel/panels/RangePanel/index.tsx +1 -1
  147. package/src/PickerPanel/panels/TimePanel/TimeBody.tsx +1 -1
  148. package/src/PickerPanel/panels/YearPanel/YearHeader.tsx +1 -1
  149. package/src/PickerPanel/utils/pickerUtils.ts +2 -1
  150. package/src/Radio/radio-group.tsx +2 -1
  151. package/src/Radio/radio.tsx +7 -2
  152. package/src/RangePicker/range-picker-mobile.tsx +1 -3
  153. package/src/Rate/rate.tsx +5 -0
  154. package/src/Rate/star.tsx +20 -4
  155. package/src/Segmented/demo/variant.tsx +10 -2
  156. package/src/Segmented/index.ts +2 -2
  157. package/src/Segmented/segmented.tsx +4 -11
  158. package/src/Select/interface.ts +0 -1
  159. package/src/Select/option.tsx +8 -2
  160. package/src/Select/options.tsx +11 -4
  161. package/src/Select/select.tsx +4 -4
  162. package/src/Steps/step.tsx +23 -5
  163. package/src/Steps/steps.tsx +8 -1
  164. package/src/Table/demo/components-override.tsx +34 -7
  165. package/src/Table/demo/grouping.tsx +8 -4
  166. package/src/Table/hooks/useColumns.ts +14 -18
  167. package/src/Table/index.ts +3 -3
  168. package/src/Table/style/index.less +2 -4
  169. package/src/Table/table.tsx +2 -4
  170. package/src/Table/tbody/row.tsx +1 -3
  171. package/src/Table/theader/index.tsx +9 -10
  172. package/src/Tabs/tab-content.tsx +2 -0
  173. package/src/Tabs/tab-header/index.tsx +2 -0
  174. package/src/Tabs/tab-header/tab-header-title.tsx +3 -1
  175. package/src/Tour/demo/placement.tsx +2 -1
  176. package/src/Tour/index.ts +1 -1
  177. package/src/Tree/style/index.less +1 -5
  178. package/src/Tree/tree.tsx +16 -11
  179. package/src/Tree/utils/tree-tools.ts +4 -1
  180. package/src/hooks/useDelayState.ts +4 -2
  181. package/src/hooks/useEvent.ts +1 -3
  182. package/src/hooks/useFocusManage.ts +33 -17
  183. package/src/locale/context.tsx +5 -1
  184. package/src/locale/interface.ts +3 -1
  185. package/src/style/size.css +2 -0
  186. package/src/utils/debounce.ts +1 -3
  187. package/src/utils/focusStack.ts +55 -12
  188. 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.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "yee basic components",
5
5
  "keywords": [
6
6
  "react",
@@ -10,13 +10,13 @@
10
10
  "design-system",
11
11
  "yee"
12
12
  ],
13
- "homepage": "https://github.com/InsureMO/oh-yee/tree/main/packages/yee-c#readme",
13
+ "homepage": "https://github.com/insureMO/oh-yee/tree/main/packages/yee-c#readme",
14
14
  "bugs": {
15
- "url": "https://github.com/InsureMO/oh-yee/issues"
15
+ "url": "https://github.com/insureMO/oh-yee/issues"
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/InsureMO/oh-yee.git",
19
+ "url": "https://github.com/insureMO/oh-yee.git",
20
20
  "directory": "packages/yee-c"
21
21
  },
22
22
  "license": "MIT",
@@ -94,6 +94,7 @@
94
94
  "devDependencies": {
95
95
  "@commitlint/cli": "^17.1.2",
96
96
  "@commitlint/config-conventional": "^17.1.0",
97
+ "@testing-library/react": "16.3.2",
97
98
  "@types/react": "^19.2.7",
98
99
  "@types/react-dom": "^19.2.3",
99
100
  "@types/react-syntax-highlighter": "^15.5.13",
@@ -102,6 +103,7 @@
102
103
  "eslint": "^8.23.0",
103
104
  "father": "^4.1.0",
104
105
  "husky": "^8.0.1",
106
+ "jsdom": "25.0.1",
105
107
  "lint-staged": "^13.0.3",
106
108
  "prettier": "^3.6.2",
107
109
  "prettier-plugin-organize-imports": "^4.3.0",
@@ -109,7 +111,8 @@
109
111
  "react": "^19.2.0",
110
112
  "react-dom": "^19.2.0",
111
113
  "stylelint": "^14.16.1",
112
- "stylelint-order": "^6.0.4"
114
+ "stylelint-order": "^6.0.4",
115
+ "vitest": "3.2.6"
113
116
  },
114
117
  "peerDependencies": {
115
118
  "react": ">=18.0.0",
@@ -120,7 +123,7 @@
120
123
  "registry": "https://registry.npmjs.org/"
121
124
  },
122
125
  "authors": [
123
- "InsureMO"
126
+ "insureMO"
124
127
  ],
125
128
  "scripts": {
126
129
  "build": "node scripts/generate-version.js && rm -rf dist && father build",
@@ -138,6 +141,7 @@
138
141
  "lint:css": "stylelint \"{src,test}/**/*.{css,less}\" --ignore-pattern \"src/style/color.*.css\"",
139
142
  "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
140
143
  "start": "npm run dev",
144
+ "test": "vitest run",
141
145
  "watch": "father dev"
142
146
  }
143
147
  }
@@ -139,8 +139,8 @@ const Anchor = forwardRef<HTMLDivElement, AnchorProps>((baseprops, ref) => {
139
139
 
140
140
  // Scrolled above the first anchor — reset to the top item.
141
141
  if (nextActiveKey === undefined) {
142
- const first = currentItems.find(
143
- (item) => document.getElementById(item.key),
142
+ const first = currentItems.find((item) =>
143
+ document.getElementById(item.key),
144
144
  );
145
145
  nextActiveKey = first?.key;
146
146
  }
package/src/Card/card.tsx CHANGED
@@ -73,12 +73,12 @@ const Card: FC<CardProps> = (baseprops) => {
73
73
  )}
74
74
  style={styles?.expandIcon}
75
75
  aria-expanded={mergedExpanded}
76
+ aria-label={mergedExpanded ? 'Collapse content' : 'Expand content'}
76
77
  icon={
77
78
  icon ? (
78
79
  icon
79
80
  ) : (
80
81
  <ChevronUp
81
- aria-label="expanded"
82
82
  strokeWidth={1.5}
83
83
  size={16}
84
84
  style={{
@@ -134,6 +134,7 @@ const Card: FC<CardProps> = (baseprops) => {
134
134
  onKeyDown={handleKeyDown}
135
135
  tabIndex={headerClickable ? 0 : undefined}
136
136
  role={headerClickable ? 'button' : undefined}
137
+ aria-expanded={headerClickable ? mergedExpanded : undefined}
137
138
  >
138
139
  {iconPosition === 'left' ? renderExpandIcon() : null}
139
140
  {title ? (
@@ -154,6 +155,9 @@ const Card: FC<CardProps> = (baseprops) => {
154
155
  animate={mergedExpanded ? { height: 'auto' } : { height: 0 }}
155
156
  exit={{ height: 0, display: 'none' }}
156
157
  transition={{ duration: animationDuration }}
158
+ role="region"
159
+ aria-labelledby={title ? headerId : undefined}
160
+ aria-hidden={!mergedExpanded}
157
161
  >
158
162
  {children}
159
163
  </motion.div>
@@ -7,17 +7,23 @@ const CarouselItem = (props: CarouselItemProps) => {
7
7
  const { style, className, children, index } = props;
8
8
  const { prefixCls, current } = React.useContext(CarouselCtx);
9
9
 
10
+ const isActive = index === current - 1;
11
+
10
12
  return (
11
13
  <div
12
14
  className={clsx(
13
15
  `${prefixCls}-item`,
14
16
  {
15
- [`${prefixCls}-item-active`]: index === current - 1,
17
+ [`${prefixCls}-item-active`]: isActive,
16
18
  },
17
19
  className,
18
20
  )}
19
21
  data-index={index}
20
22
  style={style}
23
+ role="tabpanel"
24
+ aria-roledescription="slide"
25
+ aria-label={`Slide ${(index ?? 0) + 1}`}
26
+ aria-hidden={!isActive}
21
27
  >
22
28
  {children}
23
29
  </div>
@@ -173,6 +173,9 @@ const Carousel = React.forwardRef(
173
173
  { [`${prefixCls}-arrows-visible`]: showArrow },
174
174
  className,
175
175
  ),
176
+ role: 'region' as const,
177
+ 'aria-roledescription': 'carousel',
178
+ 'aria-live': autoplay ? ('off' as const) : ('polite' as const),
176
179
  onMouseEnter: handleMouseEnter,
177
180
  onMouseLeave: handleMouseLeave,
178
181
  };
@@ -198,6 +201,15 @@ const Carousel = React.forwardRef(
198
201
  )}
199
202
  style={styles?.prev}
200
203
  onClick={prev}
204
+ role="button"
205
+ aria-label="Previous slide"
206
+ tabIndex={0}
207
+ onKeyDown={(e) => {
208
+ if (e.key === 'Enter' || e.key === ' ') {
209
+ e.preventDefault();
210
+ prev();
211
+ }
212
+ }}
201
213
  >
202
214
  {direction === 'vertical' ? <ChevronUp /> : <ChevronLeft />}
203
215
  </div>
@@ -211,6 +223,15 @@ const Carousel = React.forwardRef(
211
223
  )}
212
224
  style={styles?.next}
213
225
  onClick={next}
226
+ role="button"
227
+ aria-label="Next slide"
228
+ tabIndex={0}
229
+ onKeyDown={(e) => {
230
+ if (e.key === 'Enter' || e.key === ' ') {
231
+ e.preventDefault();
232
+ next();
233
+ }
234
+ }}
214
235
  >
215
236
  {direction === 'vertical' ? <ChevronDown /> : <ChevronRight />}
216
237
  </div>
@@ -228,17 +249,22 @@ const Carousel = React.forwardRef(
228
249
  classNames?.dot,
229
250
  )}
230
251
  style={styles?.dot}
252
+ role="tablist"
253
+ aria-label="Slide controls"
231
254
  >
232
255
  {new Array(React.Children.count(children))
233
256
  .fill(0)
234
257
  .map((i, index: number) => {
258
+ const isActive =
259
+ current === index + 1 || (current === 0 && index === count - 1);
235
260
  return (
236
261
  <li
237
262
  className={clsx({
238
- active:
239
- current === index + 1 ||
240
- (current === 0 && index === count),
263
+ active: isActive,
241
264
  })}
265
+ role="tab"
266
+ aria-selected={isActive}
267
+ aria-label={`Slide ${index + 1}`}
242
268
  tabIndex={0}
243
269
  onClick={() => handleIndicator(index + 1, 'click')}
244
270
  onKeyDown={(e) => {
@@ -446,7 +446,11 @@ const Cascader = (baseprops: CascaderProps) => {
446
446
 
447
447
  const renderPopup = () => {
448
448
  return (
449
- <div className={clsx(prefixCls)}>
449
+ <div
450
+ className={clsx(prefixCls)}
451
+ role="group"
452
+ aria-label="Cascader options"
453
+ >
450
454
  {isFilter ? (
451
455
  <SearchList
452
456
  prefixCls={prefixCls}
@@ -11,7 +11,11 @@ export interface ColumnProps {
11
11
  const Column = (props: ColumnProps) => {
12
12
  const { prefixCls, data, index } = props;
13
13
  return (
14
- <ul className={`${prefixCls}-menu`} role="menu">
14
+ <ul
15
+ className={`${prefixCls}-menu`}
16
+ role="listbox"
17
+ aria-label={`Level ${index + 1} options`}
18
+ >
15
19
  {Array.isArray(data) &&
16
20
  data.map((item, idx: number) => {
17
21
  return (
@@ -107,12 +107,24 @@ const Node = (props: NodeProps) => {
107
107
  className={clsName}
108
108
  onMouseEnter={handleMouseEnter}
109
109
  onClick={(e: React.MouseEvent<HTMLElement>) => handleClick(false, e)}
110
+ role="option"
111
+ aria-disabled={disabled || undefined}
112
+ aria-selected={checked || expandedPath[level]?.key === uid}
113
+ aria-expanded={isParent ? expandedPath[level]?.key === uid : undefined}
114
+ tabIndex={disabled ? -1 : 0}
115
+ onKeyDown={(e) => {
116
+ if (e.key === 'Enter' || e.key === ' ') {
117
+ e.preventDefault();
118
+ handleClick(false, e as unknown as React.MouseEvent<HTMLElement>);
119
+ }
120
+ }}
110
121
  >
111
122
  {multiple && (
112
123
  <Checkbox
113
124
  checked={isParent ? checkedAll : checked}
114
125
  onChange={isParent ? () => handleClick(true) : undefined}
115
126
  indeterminate={indeterminate}
127
+ aria-label={`Select ${typeof label === 'string' ? label : String(label)}`}
116
128
  />
117
129
  )}
118
130
  <span
@@ -125,9 +137,9 @@ const Node = (props: NodeProps) => {
125
137
  </span>
126
138
 
127
139
  {load === 1 ? (
128
- <Spin size="small" />
140
+ <Spin size="small" aria-label="Loading" />
129
141
  ) : isLeaf === false ? (
130
- <ChevronRight size={20} />
142
+ <ChevronRight size={20} aria-hidden="true" />
131
143
  ) : null}
132
144
  </li>
133
145
  );
@@ -32,10 +32,15 @@ const SearchList = (props: SearchListProps) => {
32
32
  const { prefixCls, items, onClick } = props;
33
33
 
34
34
  return (
35
- <ul className={`${prefixCls}-filter-menu`}>
35
+ <ul
36
+ className={`${prefixCls}-filter-menu`}
37
+ role="listbox"
38
+ aria-label="Search results"
39
+ >
36
40
  {items.length ? (
37
41
  items.map((item) => {
38
- const title = item.data.$source?.title || item.data.labelPath.join(' / ');
42
+ const title =
43
+ item.data.$source?.title || item.data.labelPath.join(' / ');
39
44
 
40
45
  return (
41
46
  <li
@@ -43,13 +48,24 @@ const SearchList = (props: SearchListProps) => {
43
48
  title={typeof title === 'string' ? title : String(title)}
44
49
  onClick={() => onClick(item.data)}
45
50
  key={item.data.uid}
51
+ role="option"
52
+ aria-disabled={item.data.$source?.disabled || undefined}
53
+ tabIndex={0}
54
+ onKeyDown={(e) => {
55
+ if (e.key === 'Enter' || e.key === ' ') {
56
+ e.preventDefault();
57
+ onClick(item.data);
58
+ }
59
+ }}
46
60
  >
47
61
  {renderHighlightText(item.matches)}
48
62
  </li>
49
63
  );
50
64
  })
51
65
  ) : (
52
- <p>{'No result'}</p>
66
+ <li role="option" aria-disabled="true">
67
+ {'No result'}
68
+ </li>
53
69
  )}
54
70
  </ul>
55
71
  );
@@ -340,7 +340,8 @@ export const changeToView = (
340
340
  * conversions share one collision-safe format.
341
341
  * @param val - The path array
342
342
  */
343
- export const formatKey = (val: Array<string | number>): string => pathToUid(val);
343
+ export const formatKey = (val: Array<string | number>): string =>
344
+ pathToUid(val);
344
345
 
345
346
  /**
346
347
  * Check if a path has the same prefix as another path
@@ -441,11 +442,7 @@ export const flattenOptions = (
441
442
  });
442
443
  if (hasChild(node, children)) {
443
444
  seen.add(node);
444
- dfs(
445
- node[children] as Array<Record<string, unknown>>,
446
- path,
447
- labelPath,
448
- );
445
+ dfs(node[children] as Array<Record<string, unknown>>, path, labelPath);
449
446
  seen.delete(node);
450
447
  }
451
448
  });
@@ -63,7 +63,12 @@ const CheckboxGroup = (baseprops: CheckboxGroupProps) => {
63
63
  };
64
64
 
65
65
  return (
66
- <div className={cls} style={style}>
66
+ <div
67
+ className={cls}
68
+ style={style}
69
+ role="group"
70
+ aria-disabled={disabled || undefined}
71
+ >
67
72
  <CheckboxGroupCtx.Provider
68
73
  value={{ name, buttonStyle, onChange: handleChange }}
69
74
  >
@@ -51,6 +51,7 @@ const Checkbox = (baseprops: CheckboxProps) => {
51
51
  className,
52
52
  )}
53
53
  style={style}
54
+ aria-disabled={disabled || undefined}
54
55
  >
55
56
  <span
56
57
  className={clsx(`${prefixCls}`, {
@@ -68,10 +69,12 @@ const Checkbox = (baseprops: CheckboxProps) => {
68
69
  disabled={disabled}
69
70
  checked={mergedChecked}
70
71
  onChange={handleChange}
72
+ aria-checked={indeterminate ? 'mixed' : mergedChecked}
71
73
  />
72
74
  <span
73
75
  className={clsx(`${prefixCls}-inner`, classNames?.inner)}
74
76
  style={styles?.inner}
77
+ aria-hidden="true"
75
78
  ></span>
76
79
  </span>
77
80
  {children ? (
@@ -1,7 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import { ChevronRight } from 'lucide-react';
3
3
  import { AnimatePresence, motion } from 'motion/react';
4
- import React, { useContext } from 'react';
4
+ import React, { useContext, useId } from 'react';
5
5
  import Button from '../Button';
6
6
  import { CollapseContext } from './collapse';
7
7
  import type { PanelProps } from './interface';
@@ -12,6 +12,10 @@ const Panel: React.FC<PanelProps & { id: string | number }> = (props) => {
12
12
 
13
13
  const { prefixCls, activeKey, onExpand } = useContext(CollapseContext);
14
14
 
15
+ const panelId = useId();
16
+ const headerId = `${panelId}-header`;
17
+ const contentId = `${panelId}-content`;
18
+
15
19
  const expanded = activeKey.includes(id);
16
20
 
17
21
  const itemClassName = clsx(
@@ -28,7 +32,9 @@ const Panel: React.FC<PanelProps & { id: string | number }> = (props) => {
28
32
  className={clsx(`${prefixCls}-header`, classNames?.header)}
29
33
  role="button"
30
34
  style={styles?.header}
35
+ id={headerId}
31
36
  aria-expanded={expanded}
37
+ aria-controls={contentId}
32
38
  aria-disabled="false"
33
39
  tabIndex={0}
34
40
  onClick={() => onExpand(id)}
@@ -44,6 +50,7 @@ const Panel: React.FC<PanelProps & { id: string | number }> = (props) => {
44
50
  style={styles?.expandIcon}
45
51
  animate={{ rotate: expanded ? 90 : 0 }}
46
52
  transition={{ duration: 0.15 }}
53
+ aria-hidden="true"
47
54
  >
48
55
  <Button icon={<ChevronRight size={16} />} type="text" />
49
56
  </motion.div>
@@ -69,6 +76,9 @@ const Panel: React.FC<PanelProps & { id: string | number }> = (props) => {
69
76
  <motion.div
70
77
  className={clsx(`${prefixCls}-content`, classNames?.content)}
71
78
  style={styles?.content}
79
+ id={contentId}
80
+ role="region"
81
+ aria-labelledby={headerId}
72
82
  initial={{ height: 0, opacity: 0 }}
73
83
  animate={{ height: 'auto', opacity: 1 }}
74
84
  exit={{ height: 0, opacity: 0 }}
@@ -1,7 +1,14 @@
1
1
  import clsx from 'clsx';
2
2
  import { X } from 'lucide-react';
3
3
  import { motion } from 'motion/react';
4
- import React, { useContext, useEffect, useRef, useState } from 'react';
4
+ import React, {
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useRef,
10
+ useState,
11
+ } from 'react';
5
12
  import Button from '../Button';
6
13
  import { GlobalContext } from '../Config-Provider';
7
14
  import useEsc from '../hooks/useEsc';
@@ -55,7 +62,17 @@ const Dialog = (baseprops: DialogProps) => {
55
62
  } = props;
56
63
 
57
64
  const [delayOpen, setDelayOpen] = useState(open);
65
+ const [contentElement, setContentElement] = useState<HTMLDivElement | null>(
66
+ null,
67
+ );
58
68
  const contentRef = useRef<HTMLDivElement>(null);
69
+ const setContentRef = useCallback((element: HTMLDivElement | null) => {
70
+ contentRef.current = element;
71
+ setContentElement(element);
72
+ }, []);
73
+ const dialogId = useId();
74
+ const titleId = `${dialogId}-title`;
75
+ const bodyId = `${dialogId}-body`;
59
76
 
60
77
  // When open changes and openResetLocation is true, reset position
61
78
  useEffect(() => {
@@ -71,7 +88,7 @@ const Dialog = (baseprops: DialogProps) => {
71
88
  });
72
89
 
73
90
  // lock focus in the dialog
74
- useLockFocus(contentRef?.current ?? null, delayOpen);
91
+ useLockFocus(contentElement, delayOpen);
75
92
 
76
93
  // enable esc to close dialog
77
94
  useEsc({
@@ -81,8 +98,8 @@ const Dialog = (baseprops: DialogProps) => {
81
98
  },
82
99
  });
83
100
 
84
- // manage focus
85
- useFocusManage(contentRef.current as HTMLElement, delayOpen);
101
+ // manage focus once per open cycle
102
+ useFocusManage(contentElement, open);
86
103
 
87
104
  const cls = clsx(prefixCls, {
88
105
  [`${prefixCls}-open`]: delayOpen,
@@ -143,6 +160,7 @@ const Dialog = (baseprops: DialogProps) => {
143
160
  <div
144
161
  className={clsx(`${prefixCls}-mask`, classNames?.mask)}
145
162
  style={styles?.mask}
163
+ aria-hidden="true"
146
164
  ></div>
147
165
  )}
148
166
  <div
@@ -154,7 +172,11 @@ const Dialog = (baseprops: DialogProps) => {
154
172
  style={{ width: fullscreen ? '100%' : width, ...styles?.content }}
155
173
  tabIndex={0}
156
174
  onClick={(e) => e.stopPropagation()}
157
- ref={contentRef}
175
+ ref={setContentRef}
176
+ role="dialog"
177
+ aria-modal={showMask ? true : undefined}
178
+ aria-labelledby={title ? titleId : undefined}
179
+ aria-describedby={bodyId}
158
180
  >
159
181
  {closable ? (
160
182
  <Button
@@ -175,12 +197,19 @@ const Dialog = (baseprops: DialogProps) => {
175
197
  className={clsx(`${prefixCls}-header`, classNames?.header)}
176
198
  style={styles?.header}
177
199
  >
178
- <div className={`${prefixCls}-title`}>{title}</div>
200
+ {typeof title === 'string' ? (
201
+ <div className={`${prefixCls}-title`} id={titleId}>
202
+ {title}
203
+ </div>
204
+ ) : (
205
+ title
206
+ )}
179
207
  </div>
180
208
  ) : null}
181
209
  <div
182
210
  className={clsx(`${prefixCls}-body`, classNames?.body)}
183
211
  style={styles?.body}
212
+ id={bodyId}
184
213
  >
185
214
  {children}
186
215
  </div>
@@ -54,7 +54,7 @@ export interface DialogProps extends DataAttributeProps {
54
54
  /**
55
55
  * Title
56
56
  */
57
- title?: string;
57
+ title?: string | React.ReactNode;
58
58
  /**
59
59
  * Cancel button text
60
60
  * @default Cancel
@@ -37,7 +37,8 @@
37
37
  }
38
38
 
39
39
  &-title {
40
- font-weight: 600;
40
+ font-size: var(--yee-dialog-title-font-size);
41
+ font-weight: var(--yee-dialog-title-font-weight);
41
42
  }
42
43
 
43
44
  &-content {
@@ -1,7 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import { X } from 'lucide-react';
3
3
  import { motion } from 'motion/react';
4
- import React, { useContext, useRef, useState } from 'react';
4
+ import React, { useContext, useId, useRef, useState } from 'react';
5
5
  import Button from '../Button';
6
6
  import { GlobalContext } from '../Config-Provider';
7
7
  import Portal from '../Portal';
@@ -81,6 +81,9 @@ const Drawer = (baseprops: DrawerProps) => {
81
81
 
82
82
  const [delayOpen, setDelayOpen] = useState(open);
83
83
  const contentRef = useRef<HTMLDivElement>(null);
84
+ const drawerId = useId();
85
+ const titleId = `${drawerId}-title`;
86
+ const bodyId = `${drawerId}-body`;
84
87
 
85
88
  useLockFocus(contentRef.current, delayOpen);
86
89
 
@@ -149,7 +152,7 @@ const Drawer = (baseprops: DrawerProps) => {
149
152
  const renderNode = () => {
150
153
  const { initial, animate, exit } = getAnimate(placement, open);
151
154
  return (
152
- <div {...rest} className={cls} style={style} role="modal">
155
+ <div {...rest} className={cls} style={style}>
153
156
  {showMask && delayOpen && (
154
157
  <motion.div
155
158
  className={clsx(`${prefixCls}-mask`, classNames?.mask)}
@@ -159,6 +162,7 @@ const Drawer = (baseprops: DrawerProps) => {
159
162
  animate={open ? { opacity: 1 } : { opacity: 0 }}
160
163
  exit={{ opacity: 0 }}
161
164
  transition={{ duration: ANIMATION_DURATION }}
165
+ aria-hidden="true"
162
166
  ></motion.div>
163
167
  )}
164
168
  <motion.div
@@ -191,6 +195,10 @@ const Drawer = (baseprops: DrawerProps) => {
191
195
  tabIndex={0}
192
196
  style={styles?.content}
193
197
  ref={contentRef}
198
+ role="dialog"
199
+ aria-modal={showMask ? true : undefined}
200
+ aria-labelledby={title ? titleId : undefined}
201
+ aria-describedby={bodyId}
194
202
  >
195
203
  {renderClose()}
196
204
  {title ? (
@@ -198,12 +206,19 @@ const Drawer = (baseprops: DrawerProps) => {
198
206
  className={clsx(`${prefixCls}-header`, classNames?.header)}
199
207
  style={styles?.header}
200
208
  >
201
- <div className={clsx(`${prefixCls}-title`, classNames?.title)} style={styles?.title}>{title}</div>
209
+ <div
210
+ className={clsx(`${prefixCls}-title`, classNames?.title)}
211
+ style={styles?.title}
212
+ id={titleId}
213
+ >
214
+ {title}
215
+ </div>
202
216
  </div>
203
217
  ) : null}
204
218
  <div
205
219
  className={clsx(`${prefixCls}-body`, classNames?.body)}
206
220
  style={styles?.body}
221
+ id={bodyId}
207
222
  >
208
223
  {children}
209
224
  </div>
@@ -9,10 +9,10 @@ import React, {
9
9
  useRef,
10
10
  } from 'react';
11
11
  import Grid from '../Grid';
12
- import { runValidator } from './utils/validate';
13
12
  import FieldContext from './FieldContext';
14
13
  import FormContext from './FormContext';
15
14
  import type { FieldGroupProps, NamePath, ValidateMessage } from './interface';
15
+ import { runValidator } from './utils/validate';
16
16
 
17
17
  const Group: React.FC<FieldGroupProps> = (props) => {
18
18
  const { label, required, rules, cols, children, style, className } = props;
@@ -39,9 +39,7 @@ const Group: React.FC<FieldGroupProps> = (props) => {
39
39
  const [, forceValidate] = useReducer((x) => x + 1, 0); // eslint-disable-line @typescript-eslint/no-unused-vars
40
40
 
41
41
  // Group validation
42
- const validate = async (
43
- force: boolean,
44
- ): Promise<ValidateMessage[]> => {
42
+ const validate = async (force: boolean): Promise<ValidateMessage[]> => {
45
43
  if (!required && (!rules || rules.length === 0)) return [];
46
44
 
47
45
  // Skip if not touched and not forced validation
@@ -1,11 +1,4 @@
1
- import {
2
- Button,
3
- Form,
4
- Grid,
5
- Input,
6
- Space,
7
- TextArea,
8
- } from '@rainbow-oh/yee-c';
1
+ import { Button, Form, Grid, Input, Space, TextArea } from '@rainbow-oh/yee-c';
9
2
  import { Info } from 'lucide-react';
10
3
  import React from 'react';
11
4
 
@@ -29,9 +22,7 @@ export default () => {
29
22
  name="username"
30
23
  label="Username"
31
24
  tooltip="Used for login. 4-20 characters."
32
- rules={[
33
- { required: true, message: 'Please input your username!' },
34
- ]}
25
+ rules={[{ required: true, message: 'Please input your username!' }]}
35
26
  >
36
27
  <Input placeholder="Please input your username" />
37
28
  </Form.Field>
@@ -134,10 +134,7 @@ export async function runValidator(
134
134
  * Returns { passed, message? }. Sync rules short-circuit; the custom validator
135
135
  * is awaited so async validators are supported.
136
136
  */
137
- async function validate(
138
- rule: Rule,
139
- value: unknown,
140
- ): Promise<ValidateResult> {
137
+ async function validate(rule: Rule, value: unknown): Promise<ValidateResult> {
141
138
  if (rule.required && isEmpty(value)) {
142
139
  return { passed: false };
143
140
  }