@zentauri-ui/zentauri-components 1.4.51 → 1.4.62

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 (314) hide show
  1. package/README.md +63 -1
  2. package/cli/registry.json +2 -1
  3. package/dist/{chunk-UXGHUBNJ.mjs → chunk-2PJF7DLJ.mjs} +3 -3
  4. package/dist/{chunk-UXGHUBNJ.mjs.map → chunk-2PJF7DLJ.mjs.map} +1 -1
  5. package/dist/{chunk-WDCIZHXY.mjs → chunk-45FCOQ63.mjs} +5 -3
  6. package/dist/chunk-45FCOQ63.mjs.map +1 -0
  7. package/dist/{chunk-RDSPHBHK.mjs → chunk-4ANBTJ5G.mjs} +49 -6
  8. package/dist/chunk-4ANBTJ5G.mjs.map +1 -0
  9. package/dist/chunk-4E66ICIR.mjs +158 -0
  10. package/dist/chunk-4E66ICIR.mjs.map +1 -0
  11. package/dist/{chunk-XLAFQ24R.js → chunk-4U6FOCFK.js} +22 -14
  12. package/dist/chunk-4U6FOCFK.js.map +1 -0
  13. package/dist/{chunk-XWM2S6VV.mjs → chunk-EQSSYK27.mjs} +12 -10
  14. package/dist/chunk-EQSSYK27.mjs.map +1 -0
  15. package/dist/{chunk-5QB2KNZQ.js → chunk-FGGYDAX3.js} +5 -3
  16. package/dist/chunk-FGGYDAX3.js.map +1 -0
  17. package/dist/{chunk-7HL3A4YF.mjs → chunk-IK75NHRX.mjs} +63 -14
  18. package/dist/chunk-IK75NHRX.mjs.map +1 -0
  19. package/dist/{chunk-BORK3BJO.mjs → chunk-J56L4ZQ3.mjs} +10 -10
  20. package/dist/{chunk-BORK3BJO.mjs.map → chunk-J56L4ZQ3.mjs.map} +1 -1
  21. package/dist/{chunk-PGH27VTL.mjs → chunk-JF3FKUUP.mjs} +21 -13
  22. package/dist/chunk-JF3FKUUP.mjs.map +1 -0
  23. package/dist/{chunk-WZKGRU3U.js → chunk-MQZB5EPD.js} +92 -27
  24. package/dist/chunk-MQZB5EPD.js.map +1 -0
  25. package/dist/{chunk-N4NO3SYL.js → chunk-NX3IHMT7.js} +22 -14
  26. package/dist/chunk-NX3IHMT7.js.map +1 -0
  27. package/dist/{chunk-BVXTOEBI.mjs → chunk-OG2WM5YK.mjs} +45 -17
  28. package/dist/chunk-OG2WM5YK.mjs.map +1 -0
  29. package/dist/{chunk-IXDJ3IPG.mjs → chunk-OXS6UJUG.mjs} +21 -13
  30. package/dist/chunk-OXS6UJUG.mjs.map +1 -0
  31. package/dist/{chunk-PCK6LX3K.js → chunk-PFOV3U7W.js} +3 -3
  32. package/dist/{chunk-PCK6LX3K.js.map → chunk-PFOV3U7W.js.map} +1 -1
  33. package/dist/{chunk-2PQEXQVR.js → chunk-THCNTPPL.js} +62 -13
  34. package/dist/chunk-THCNTPPL.js.map +1 -0
  35. package/dist/chunk-UP6S75V5.js +160 -0
  36. package/dist/chunk-UP6S75V5.js.map +1 -0
  37. package/dist/{chunk-P5HUBXUX.js → chunk-V2IWLR4O.js} +48 -5
  38. package/dist/chunk-V2IWLR4O.js.map +1 -0
  39. package/dist/{chunk-3OR47XMY.js → chunk-VSKL5LOB.js} +45 -17
  40. package/dist/chunk-VSKL5LOB.js.map +1 -0
  41. package/dist/{chunk-E3DZNJAD.js → chunk-Y4EDWZKH.js} +12 -10
  42. package/dist/chunk-Y4EDWZKH.js.map +1 -0
  43. package/dist/{chunk-YNCD6TKE.mjs → chunk-Y4IFVO46.mjs} +93 -28
  44. package/dist/chunk-Y4IFVO46.mjs.map +1 -0
  45. package/dist/{chunk-BITDSQMR.js → chunk-ZNDHS5OK.js} +10 -10
  46. package/dist/{chunk-BITDSQMR.js.map → chunk-ZNDHS5OK.js.map} +1 -1
  47. package/dist/hooks/useFocusManagement/useFocusManagement.d.ts +5 -14
  48. package/dist/hooks/useFocusManagement/useFocusManagement.d.ts.map +1 -1
  49. package/dist/hooks/useFocusManagement.js +2 -2
  50. package/dist/hooks/useFocusManagement.mjs +1 -1
  51. package/dist/ui/badge/animated.js +2 -2
  52. package/dist/ui/badge/animated.mjs +1 -1
  53. package/dist/ui/badge/badge-base.d.ts +1 -1
  54. package/dist/ui/badge/badge-base.d.ts.map +1 -1
  55. package/dist/ui/badge/types.d.ts +1 -0
  56. package/dist/ui/badge/types.d.ts.map +1 -1
  57. package/dist/ui/badge/variants.d.ts +7 -7
  58. package/dist/ui/badge.js +4 -4
  59. package/dist/ui/badge.mjs +2 -2
  60. package/dist/ui/buttons/animated.js +3 -3
  61. package/dist/ui/buttons/animated.mjs +1 -1
  62. package/dist/ui/buttons.js +4 -4
  63. package/dist/ui/buttons.mjs +2 -2
  64. package/dist/ui/drawer/animated/drawer-content-animated.d.ts.map +1 -1
  65. package/dist/ui/drawer/animated.js +17 -18
  66. package/dist/ui/drawer/animated.js.map +1 -1
  67. package/dist/ui/drawer/animated.mjs +8 -9
  68. package/dist/ui/drawer/animated.mjs.map +1 -1
  69. package/dist/ui/drawer/drawer-base.d.ts +1 -1
  70. package/dist/ui/drawer/drawer-base.d.ts.map +1 -1
  71. package/dist/ui/drawer/types.d.ts +1 -0
  72. package/dist/ui/drawer/types.d.ts.map +1 -1
  73. package/dist/ui/drawer.js +12 -12
  74. package/dist/ui/drawer.mjs +2 -2
  75. package/dist/ui/dropdown/dropdown.d.ts +1 -1
  76. package/dist/ui/dropdown/dropdown.d.ts.map +1 -1
  77. package/dist/ui/dropdown/types.d.ts +1 -0
  78. package/dist/ui/dropdown/types.d.ts.map +1 -1
  79. package/dist/ui/dropdown/variants.d.ts +1 -1
  80. package/dist/ui/dropdown.js +25 -7
  81. package/dist/ui/dropdown.js.map +1 -1
  82. package/dist/ui/dropdown.mjs +26 -8
  83. package/dist/ui/dropdown.mjs.map +1 -1
  84. package/dist/ui/empty-state/animated.js +2 -2
  85. package/dist/ui/empty-state/animated.mjs +1 -1
  86. package/dist/ui/empty-state/empty-state-base.d.ts.map +1 -1
  87. package/dist/ui/empty-state/types.d.ts +1 -0
  88. package/dist/ui/empty-state/types.d.ts.map +1 -1
  89. package/dist/ui/empty-state.js +10 -10
  90. package/dist/ui/empty-state.mjs +2 -2
  91. package/dist/ui/file-upload/file-upload.d.ts.map +1 -1
  92. package/dist/ui/file-upload.js +1 -3
  93. package/dist/ui/file-upload.js.map +1 -1
  94. package/dist/ui/file-upload.mjs +1 -3
  95. package/dist/ui/file-upload.mjs.map +1 -1
  96. package/dist/ui/inputs/input-base.d.ts.map +1 -1
  97. package/dist/ui/inputs/types.d.ts +3 -1
  98. package/dist/ui/inputs/types.d.ts.map +1 -1
  99. package/dist/ui/inputs.js +46 -2
  100. package/dist/ui/inputs.js.map +1 -1
  101. package/dist/ui/inputs.mjs +46 -2
  102. package/dist/ui/inputs.mjs.map +1 -1
  103. package/dist/ui/modal/animated/modal-content-animated.d.ts.map +1 -1
  104. package/dist/ui/modal/animated.js +10 -11
  105. package/dist/ui/modal/animated.js.map +1 -1
  106. package/dist/ui/modal/animated.mjs +7 -8
  107. package/dist/ui/modal/animated.mjs.map +1 -1
  108. package/dist/ui/modal/modal-base.d.ts +4 -2
  109. package/dist/ui/modal/modal-base.d.ts.map +1 -1
  110. package/dist/ui/modal.js +13 -13
  111. package/dist/ui/modal.mjs +3 -3
  112. package/dist/ui/pagination.js +6 -6
  113. package/dist/ui/pagination.js.map +1 -1
  114. package/dist/ui/pagination.mjs +3 -3
  115. package/dist/ui/pagination.mjs.map +1 -1
  116. package/dist/ui/progress/animated/progress-animated.d.ts.map +1 -1
  117. package/dist/ui/progress/animated.js +49 -11
  118. package/dist/ui/progress/animated.js.map +1 -1
  119. package/dist/ui/progress/animated.mjs +44 -6
  120. package/dist/ui/progress/animated.mjs.map +1 -1
  121. package/dist/ui/progress/progress-base.d.ts.map +1 -1
  122. package/dist/ui/progress/types.d.ts +3 -0
  123. package/dist/ui/progress/types.d.ts.map +1 -1
  124. package/dist/ui/progress.js +9 -9
  125. package/dist/ui/progress.mjs +2 -2
  126. package/dist/ui/search/search-bar.d.ts +1 -1
  127. package/dist/ui/search/search-bar.d.ts.map +1 -1
  128. package/dist/ui/search/search-suggestion-list.d.ts.map +1 -1
  129. package/dist/ui/search.js +12 -6
  130. package/dist/ui/search.js.map +1 -1
  131. package/dist/ui/search.mjs +12 -6
  132. package/dist/ui/search.mjs.map +1 -1
  133. package/dist/ui/select/select.d.ts +1 -1
  134. package/dist/ui/select/select.d.ts.map +1 -1
  135. package/dist/ui/select/types.d.ts +1 -0
  136. package/dist/ui/select/types.d.ts.map +1 -1
  137. package/dist/ui/select/variants.d.ts +1 -1
  138. package/dist/ui/select/variants.d.ts.map +1 -1
  139. package/dist/ui/select.js +121 -39
  140. package/dist/ui/select.js.map +1 -1
  141. package/dist/ui/select.mjs +122 -40
  142. package/dist/ui/select.mjs.map +1 -1
  143. package/dist/ui/skeleton/variants.d.ts +1 -1
  144. package/dist/ui/slider/slider.d.ts.map +1 -1
  145. package/dist/ui/slider/types.d.ts +8 -2
  146. package/dist/ui/slider/types.d.ts.map +1 -1
  147. package/dist/ui/slider.js +43 -7
  148. package/dist/ui/slider.js.map +1 -1
  149. package/dist/ui/slider.mjs +43 -7
  150. package/dist/ui/slider.mjs.map +1 -1
  151. package/dist/ui/spinner/animated/spinner.d.ts.map +1 -1
  152. package/dist/ui/spinner/animated.js +62 -50
  153. package/dist/ui/spinner/animated.js.map +1 -1
  154. package/dist/ui/spinner/animated.mjs +63 -51
  155. package/dist/ui/spinner/animated.mjs.map +1 -1
  156. package/dist/ui/stepper/stepper.d.ts +2 -7
  157. package/dist/ui/stepper/stepper.d.ts.map +1 -1
  158. package/dist/ui/stepper/types.d.ts +3 -3
  159. package/dist/ui/stepper/types.d.ts.map +1 -1
  160. package/dist/ui/stepper/variants.d.ts +1 -1
  161. package/dist/ui/stepper.js +7 -5
  162. package/dist/ui/stepper.js.map +1 -1
  163. package/dist/ui/stepper.mjs +7 -5
  164. package/dist/ui/stepper.mjs.map +1 -1
  165. package/dist/ui/table/animated.js +8 -8
  166. package/dist/ui/table/animated.mjs +2 -2
  167. package/dist/ui/table/table-base.d.ts +1 -1
  168. package/dist/ui/table/table-base.d.ts.map +1 -1
  169. package/dist/ui/table/types.d.ts +5 -1
  170. package/dist/ui/table/types.d.ts.map +1 -1
  171. package/dist/ui/table.js +14 -14
  172. package/dist/ui/table.mjs +1 -1
  173. package/dist/ui/tabs/animated.js +2 -2
  174. package/dist/ui/tabs/animated.mjs +1 -1
  175. package/dist/ui/tabs/tabs-base.d.ts.map +1 -1
  176. package/dist/ui/tabs/types.d.ts +2 -1
  177. package/dist/ui/tabs/types.d.ts.map +1 -1
  178. package/dist/ui/tabs.js +9 -9
  179. package/dist/ui/tabs.mjs +1 -1
  180. package/dist/ui/toast/animated.js +7 -7
  181. package/dist/ui/toast/animated.mjs +1 -1
  182. package/dist/ui/toast.js +12 -12
  183. package/dist/ui/toast.mjs +1 -1
  184. package/dist/ui/toggle/toggle-base.d.ts.map +1 -1
  185. package/dist/ui/toggle.js +28 -3
  186. package/dist/ui/toggle.js.map +1 -1
  187. package/dist/ui/toggle.mjs +29 -4
  188. package/dist/ui/toggle.mjs.map +1 -1
  189. package/dist/ui/tooltip/animated.js +3 -3
  190. package/dist/ui/tooltip/animated.mjs +1 -1
  191. package/dist/ui/tooltip/tooltip-base.d.ts.map +1 -1
  192. package/dist/ui/tooltip/types.d.ts +1 -0
  193. package/dist/ui/tooltip/types.d.ts.map +1 -1
  194. package/dist/ui/tooltip.js +7 -7
  195. package/dist/ui/tooltip.mjs +1 -1
  196. package/dist/ui/typography/blockquote-base.d.ts +6 -0
  197. package/dist/ui/typography/blockquote-base.d.ts.map +1 -0
  198. package/dist/ui/typography/blockquote.d.ts +6 -0
  199. package/dist/ui/typography/blockquote.d.ts.map +1 -0
  200. package/dist/ui/typography/code-block-base.d.ts +6 -0
  201. package/dist/ui/typography/code-block-base.d.ts.map +1 -0
  202. package/dist/ui/typography/code-block.d.ts +6 -0
  203. package/dist/ui/typography/code-block.d.ts.map +1 -0
  204. package/dist/ui/typography/heading-base.d.ts +6 -0
  205. package/dist/ui/typography/heading-base.d.ts.map +1 -0
  206. package/dist/ui/typography/heading.d.ts +6 -0
  207. package/dist/ui/typography/heading.d.ts.map +1 -0
  208. package/dist/ui/typography/index.d.ts +9 -0
  209. package/dist/ui/typography/index.d.ts.map +1 -0
  210. package/dist/ui/typography/inline-code-base.d.ts +6 -0
  211. package/dist/ui/typography/inline-code-base.d.ts.map +1 -0
  212. package/dist/ui/typography/inline-code.d.ts +6 -0
  213. package/dist/ui/typography/inline-code.d.ts.map +1 -0
  214. package/dist/ui/typography/list-base.d.ts +10 -0
  215. package/dist/ui/typography/list-base.d.ts.map +1 -0
  216. package/dist/ui/typography/list.d.ts +12 -0
  217. package/dist/ui/typography/list.d.ts.map +1 -0
  218. package/dist/ui/typography/text-base.d.ts +6 -0
  219. package/dist/ui/typography/text-base.d.ts.map +1 -0
  220. package/dist/ui/typography/text.d.ts +6 -0
  221. package/dist/ui/typography/text.d.ts.map +1 -0
  222. package/dist/ui/typography/types.d.ts +56 -0
  223. package/dist/ui/typography/types.d.ts.map +1 -0
  224. package/dist/ui/typography/variants.d.ts +16 -0
  225. package/dist/ui/typography/variants.d.ts.map +1 -0
  226. package/dist/ui/typography.js +334 -0
  227. package/dist/ui/typography.js.map +1 -0
  228. package/dist/ui/typography.mjs +321 -0
  229. package/dist/ui/typography.mjs.map +1 -0
  230. package/package.json +1 -1
  231. package/src/hooks/useFocusManagement/useFocusManagement.test.tsx +8 -0
  232. package/src/hooks/useFocusManagement/useFocusManagement.ts +162 -33
  233. package/src/ui/badge/badge-base.tsx +4 -1
  234. package/src/ui/badge/types.ts +1 -0
  235. package/src/ui/badge/variants.ts +7 -7
  236. package/src/ui/buttons/button.test.tsx +1 -1
  237. package/src/ui/buttons/variants.ts +8 -8
  238. package/src/ui/drawer/animated/drawer-content-animated.tsx +4 -5
  239. package/src/ui/drawer/drawer-base.tsx +16 -8
  240. package/src/ui/drawer/types.ts +1 -0
  241. package/src/ui/dropdown/dropdown.test.tsx +1 -3
  242. package/src/ui/dropdown/dropdown.tsx +23 -5
  243. package/src/ui/dropdown/types.ts +1 -0
  244. package/src/ui/dropdown/variants.ts +2 -2
  245. package/src/ui/empty-state/empty-state-base.tsx +9 -1
  246. package/src/ui/empty-state/types.ts +1 -0
  247. package/src/ui/file-upload/file-upload.tsx +0 -2
  248. package/src/ui/inputs/input-base.tsx +60 -6
  249. package/src/ui/inputs/types.ts +3 -1
  250. package/src/ui/modal/animated/modal-content-animated.tsx +4 -5
  251. package/src/ui/modal/modal-base.tsx +19 -9
  252. package/src/ui/modal/modal.test.tsx +38 -0
  253. package/src/ui/pagination/pagination.tsx +2 -2
  254. package/src/ui/progress/animated/progress-animated.tsx +42 -3
  255. package/src/ui/progress/progress-base.tsx +59 -3
  256. package/src/ui/progress/types.ts +3 -0
  257. package/src/ui/search/search-bar.tsx +6 -1
  258. package/src/ui/search/search-suggestion-list.tsx +14 -6
  259. package/src/ui/select/select.tsx +97 -6
  260. package/src/ui/select/types.ts +1 -0
  261. package/src/ui/select/variants.ts +5 -3
  262. package/src/ui/slider/slider.test.tsx +25 -1
  263. package/src/ui/slider/slider.tsx +45 -4
  264. package/src/ui/slider/types.ts +8 -2
  265. package/src/ui/spinner/animated/spinner.tsx +4 -0
  266. package/src/ui/stepper/stepper.test.tsx +6 -7
  267. package/src/ui/stepper/stepper.tsx +11 -10
  268. package/src/ui/stepper/types.ts +7 -3
  269. package/src/ui/table/table-base.tsx +32 -6
  270. package/src/ui/table/types.ts +8 -1
  271. package/src/ui/tabs/tabs-base.tsx +71 -10
  272. package/src/ui/tabs/types.ts +2 -1
  273. package/src/ui/tabs/variants.ts +1 -1
  274. package/src/ui/toast/toast-base.tsx +1 -1
  275. package/src/ui/toggle/toggle-base.tsx +37 -4
  276. package/src/ui/tooltip/tooltip-base.tsx +119 -22
  277. package/src/ui/tooltip/types.ts +1 -0
  278. package/src/ui/tooltip/variants.ts +2 -2
  279. package/src/ui/typography/blockquote-base.tsx +39 -0
  280. package/src/ui/typography/blockquote.tsx +8 -0
  281. package/src/ui/typography/code-block-base.tsx +37 -0
  282. package/src/ui/typography/code-block.tsx +8 -0
  283. package/src/ui/typography/heading-base.tsx +59 -0
  284. package/src/ui/typography/heading.tsx +8 -0
  285. package/src/ui/typography/index.ts +28 -0
  286. package/src/ui/typography/inline-code-base.tsx +27 -0
  287. package/src/ui/typography/inline-code.tsx +8 -0
  288. package/src/ui/typography/list-base.tsx +88 -0
  289. package/src/ui/typography/list.tsx +15 -0
  290. package/src/ui/typography/text-base.tsx +43 -0
  291. package/src/ui/typography/text.tsx +8 -0
  292. package/src/ui/typography/types.ts +90 -0
  293. package/src/ui/typography/typography.test.tsx +80 -0
  294. package/src/ui/typography/variants.ts +72 -0
  295. package/dist/chunk-2PQEXQVR.js.map +0 -1
  296. package/dist/chunk-3OR47XMY.js.map +0 -1
  297. package/dist/chunk-5QB2KNZQ.js.map +0 -1
  298. package/dist/chunk-7HL3A4YF.mjs.map +0 -1
  299. package/dist/chunk-BVXTOEBI.mjs.map +0 -1
  300. package/dist/chunk-E3DZNJAD.js.map +0 -1
  301. package/dist/chunk-IXDJ3IPG.mjs.map +0 -1
  302. package/dist/chunk-N4NO3SYL.js.map +0 -1
  303. package/dist/chunk-P5HUBXUX.js.map +0 -1
  304. package/dist/chunk-PGH27VTL.mjs.map +0 -1
  305. package/dist/chunk-RDSPHBHK.mjs.map +0 -1
  306. package/dist/chunk-WDCIZHXY.mjs.map +0 -1
  307. package/dist/chunk-WL5I7RVS.mjs +0 -54
  308. package/dist/chunk-WL5I7RVS.mjs.map +0 -1
  309. package/dist/chunk-WZKGRU3U.js.map +0 -1
  310. package/dist/chunk-XLAFQ24R.js.map +0 -1
  311. package/dist/chunk-XWM2S6VV.mjs.map +0 -1
  312. package/dist/chunk-YNCD6TKE.mjs.map +0 -1
  313. package/dist/chunk-YPLVTUYL.js +0 -56
  314. package/dist/chunk-YPLVTUYL.js.map +0 -1
@@ -8,6 +8,7 @@ type EmptyStateVariantProps = VariantProps<typeof emptyStateVariants>;
8
8
  export type EmptyStateProps = EmptyStateVariantProps &
9
9
  (Omit<ComponentPropsWithRef<"section">, "children"> & {
10
10
  children?: ReactNode;
11
+ liveRegion?: false | true | "polite" | "assertive";
11
12
  as?: ElementType;
12
13
  });
13
14
 
@@ -70,8 +70,6 @@ export function FileUpload({
70
70
  multiple={multiple}
71
71
  disabled={disabled}
72
72
  onChange={handleChange}
73
- aria-hidden
74
- tabIndex={-1}
75
73
  />
76
74
  <label
77
75
  htmlFor={inputId}
@@ -7,6 +7,18 @@ import { cn } from "../../lib/utils";
7
7
  import type { InputProps } from "./types";
8
8
  import { inputVariants } from "./variants";
9
9
 
10
+ function mergeDescribedByIds(
11
+ user: string | undefined,
12
+ ...generated: (string | undefined)[]
13
+ ): string | undefined {
14
+ const ids = [
15
+ ...(user ?? "").split(/\s+/).filter(Boolean),
16
+ ...generated.filter((id): id is string => Boolean(id)),
17
+ ];
18
+ const unique = [...new Set(ids)];
19
+ return unique.length > 0 ? unique.join(" ") : undefined;
20
+ }
21
+
10
22
  export const InputBase = (props: InputProps) => {
11
23
  const generatedId = useId();
12
24
 
@@ -19,13 +31,17 @@ export const InputBase = (props: InputProps) => {
19
31
  ref,
20
32
  "aria-invalid": ariaInvalidProp,
21
33
  errorMessage,
34
+ hint,
35
+ label,
22
36
  id,
23
37
  as,
38
+ "aria-describedby": ariaDescribedByUser,
24
39
  ...rest
25
40
  } = props;
26
41
 
27
42
  const controlId = id ?? generatedId;
28
43
  const errorId = `${controlId}-error`;
44
+ const hintId = `${controlId}-hint`;
29
45
  const ariaInvalid =
30
46
  ariaInvalidProp !== undefined
31
47
  ? ariaInvalidProp
@@ -33,8 +49,27 @@ export const InputBase = (props: InputProps) => {
33
49
  ? true
34
50
  : undefined;
35
51
 
52
+ const describedBy = mergeDescribedByIds(
53
+ ariaDescribedByUser,
54
+ hint !== undefined ? hintId : undefined,
55
+ errorMessage && appearance === "error" ? errorId : undefined,
56
+ );
57
+
36
58
  return (
37
59
  <>
60
+ {label !== undefined && (
61
+ <label
62
+ htmlFor={controlId}
63
+ className="mb-1 block text-sm font-medium text-slate-200"
64
+ >
65
+ {label}
66
+ </label>
67
+ )}
68
+ {hint !== undefined && (
69
+ <p id={hintId} className="mb-1 text-xs text-slate-400">
70
+ {hint}
71
+ </p>
72
+ )}
38
73
  <textarea
39
74
  ref={ref}
40
75
  id={controlId}
@@ -44,9 +79,7 @@ export const InputBase = (props: InputProps) => {
44
79
  className,
45
80
  )}
46
81
  aria-invalid={ariaInvalid}
47
- aria-describedby={
48
- errorMessage && appearance === "error" ? errorId : undefined
49
- }
82
+ aria-describedby={describedBy}
50
83
  {...rest}
51
84
  />
52
85
  {errorMessage && appearance === "error" && (
@@ -69,13 +102,17 @@ export const InputBase = (props: InputProps) => {
69
102
  ref,
70
103
  "aria-invalid": ariaInvalidProp,
71
104
  errorMessage,
105
+ hint,
106
+ label,
72
107
  id,
73
108
  as,
109
+ "aria-describedby": ariaDescribedByUser,
74
110
  ...rest
75
111
  } = props;
76
112
 
77
113
  const controlId = id ?? generatedId;
78
114
  const errorId = `${controlId}-error`;
115
+ const hintId = `${controlId}-hint`;
79
116
  const ariaInvalid =
80
117
  ariaInvalidProp !== undefined
81
118
  ? ariaInvalidProp
@@ -83,8 +120,27 @@ export const InputBase = (props: InputProps) => {
83
120
  ? true
84
121
  : undefined;
85
122
 
123
+ const describedBy = mergeDescribedByIds(
124
+ ariaDescribedByUser,
125
+ hint !== undefined ? hintId : undefined,
126
+ errorMessage && appearance === "error" ? errorId : undefined,
127
+ );
128
+
86
129
  return (
87
130
  <>
131
+ {label !== undefined && (
132
+ <label
133
+ htmlFor={controlId}
134
+ className="mb-1 block text-sm font-medium text-slate-200"
135
+ >
136
+ {label}
137
+ </label>
138
+ )}
139
+ {hint !== undefined && (
140
+ <p id={hintId} className="mb-1 text-xs text-slate-400">
141
+ {hint}
142
+ </p>
143
+ )}
88
144
  <input
89
145
  ref={ref}
90
146
  id={controlId}
@@ -94,9 +150,7 @@ export const InputBase = (props: InputProps) => {
94
150
  className,
95
151
  )}
96
152
  aria-invalid={ariaInvalid}
97
- aria-describedby={
98
- errorMessage && appearance === "error" ? errorId : undefined
99
- }
153
+ aria-describedby={describedBy}
100
154
  {...rest}
101
155
  />
102
156
  {errorMessage && appearance === "error" && (
@@ -1,10 +1,12 @@
1
1
  import type { VariantProps } from "class-variance-authority";
2
- import type { ComponentPropsWithRef } from "react";
2
+ import type { ComponentPropsWithRef, ReactNode } from "react";
3
3
 
4
4
  import type { inputVariants } from "./variants";
5
5
 
6
6
  export type InputSharedProps = Omit<VariantProps<typeof inputVariants>, "as"> & {
7
7
  errorMessage?: string;
8
+ hint?: ReactNode;
9
+ label?: ReactNode;
8
10
  };
9
11
 
10
12
  export type InputProps =
@@ -22,7 +22,7 @@ export function ModalContentAnimated({
22
22
  id,
23
23
  style,
24
24
  }: ModalContentAnimatedProps) {
25
- const { open, setOpen, titleId, descriptionId, contentRef } =
25
+ const { open, setOpen, titleId, descriptionId, contentRef, triggerRef } =
26
26
  useModalContext("ModalContent");
27
27
  const reduceMotion = useReducedMotion();
28
28
  const overlayMotion =
@@ -34,6 +34,7 @@ export function ModalContentAnimated({
34
34
  open,
35
35
  setOpen,
36
36
  contentRef,
37
+ triggerRef,
37
38
  });
38
39
 
39
40
  const portalTarget = typeof document !== "undefined" ? document.body : null;
@@ -46,10 +47,8 @@ export function ModalContentAnimated({
46
47
  <AnimatePresence>
47
48
  {open ? (
48
49
  <div className="fixed inset-0 z-50" data-slot="modal-portal">
49
- <motion.button
50
- type="button"
51
- aria-hidden
52
- tabIndex={-1}
50
+ <motion.div
51
+ role="presentation"
53
52
  data-slot="modal-overlay"
54
53
  className={modalOverlayVariants()}
55
54
  onClick={() => setOpen(false)}
@@ -8,6 +8,7 @@ import {
8
8
  useMemo,
9
9
  useRef,
10
10
  useState,
11
+ type RefObject,
11
12
  } from "react";
12
13
  import { createPortal } from "react-dom";
13
14
 
@@ -31,7 +32,8 @@ type ModalCtx = {
31
32
  setOpen: (next: boolean) => void;
32
33
  titleId: string;
33
34
  descriptionId: string;
34
- contentRef: React.RefObject<HTMLDivElement | null>;
35
+ contentRef: RefObject<HTMLDivElement | null>;
36
+ triggerRef: RefObject<HTMLElement | null>;
35
37
  };
36
38
 
37
39
  const ModalContext = createContext<ModalCtx | null>(null);
@@ -68,6 +70,7 @@ export function Modal({
68
70
  const titleId = `${baseId}-title`;
69
71
  const descriptionId = `${baseId}-description`;
70
72
  const contentRef = useRef<HTMLDivElement | null>(null);
73
+ const triggerRef = useRef<HTMLElement | null>(null);
71
74
 
72
75
  const ctx = useMemo(
73
76
  () => ({
@@ -76,6 +79,7 @@ export function Modal({
76
79
  titleId,
77
80
  descriptionId,
78
81
  contentRef,
82
+ triggerRef,
79
83
  }),
80
84
  [descriptionId, resolvedOpen, setOpen, titleId],
81
85
  );
@@ -90,13 +94,20 @@ export function ModalTrigger({
90
94
  children,
91
95
  appearance,
92
96
  onClick,
93
- ref,
97
+ ref: refProp,
94
98
  ...rest
95
99
  }: ModalTriggerProps) {
96
- const { setOpen } = useModalContext("ModalTrigger");
100
+ const { setOpen, triggerRef } = useModalContext("ModalTrigger");
97
101
  return (
98
102
  <button
99
- ref={ref}
103
+ ref={(node) => {
104
+ triggerRef.current = node;
105
+ if (typeof refProp === "function") {
106
+ refProp(node);
107
+ } else if (refProp) {
108
+ (refProp as RefObject<HTMLButtonElement | null>).current = node;
109
+ }
110
+ }}
100
111
  type="button"
101
112
  data-slot="modal-trigger"
102
113
  className={cn(modalTriggerVariants({ appearance }), className)}
@@ -126,13 +137,14 @@ export function ModalContent({
126
137
  id,
127
138
  style,
128
139
  }: ModalContentProps) {
129
- const { open, setOpen, titleId, descriptionId, contentRef } =
140
+ const { open, setOpen, titleId, descriptionId, contentRef, triggerRef } =
130
141
  useModalContext("ModalContent");
131
142
 
132
143
  useFocusManagement({
133
144
  open,
134
145
  setOpen,
135
146
  contentRef,
147
+ triggerRef,
136
148
  });
137
149
 
138
150
  const portalTarget = typeof document !== "undefined" ? document.body : null;
@@ -144,10 +156,8 @@ export function ModalContent({
144
156
  return createPortal(
145
157
  open ? (
146
158
  <div className="fixed inset-0 z-50" data-slot="modal-portal">
147
- <button
148
- type="button"
149
- aria-hidden
150
- tabIndex={-1}
159
+ <div
160
+ role="presentation"
151
161
  data-slot="modal-overlay"
152
162
  className={modalOverlayVariants()}
153
163
  onClick={() => setOpen(false)}
@@ -77,6 +77,21 @@ describe("Modal", () => {
77
77
  });
78
78
  });
79
79
 
80
+ it("should render overlay as non-focusable presentation surface", async () => {
81
+ render(
82
+ <Modal defaultOpen>
83
+ <ModalContent>
84
+ <ModalTitle>T</ModalTitle>
85
+ </ModalContent>
86
+ </Modal>,
87
+ );
88
+ await waitFor(() => expect(screen.getByRole("dialog")).toBeInTheDocument());
89
+ const overlay = document.querySelector('[data-slot="modal-overlay"]');
90
+ expect(overlay?.tagName.toLowerCase()).toBe("div");
91
+ expect(overlay).toHaveAttribute("role", "presentation");
92
+ expect((overlay as HTMLElement).tabIndex).toBe(-1);
93
+ });
94
+
80
95
  it("should close when Escape is pressed", async () => {
81
96
  const user = userEvent.setup();
82
97
  render(
@@ -126,4 +141,27 @@ describe("Modal", () => {
126
141
  await user.keyboard("{Escape}");
127
142
  await waitFor(() => expect(handleChange).toHaveBeenLastCalledWith(false));
128
143
  });
144
+
145
+ it("should restore focus to the trigger after the dialog closes", async () => {
146
+ const user = userEvent.setup();
147
+ render(
148
+ <Modal>
149
+ <ModalTrigger>Open</ModalTrigger>
150
+ <ModalContent>
151
+ <ModalTitle>T</ModalTitle>
152
+ </ModalContent>
153
+ </Modal>,
154
+ );
155
+ const trigger = screen.getByRole("button", { name: "Open" });
156
+ trigger.focus();
157
+ await user.click(trigger);
158
+ await waitFor(() =>
159
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
160
+ );
161
+ await user.keyboard("{Escape}");
162
+ await waitFor(() =>
163
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument(),
164
+ );
165
+ expect(trigger).toHaveFocus();
166
+ });
129
167
  });
@@ -192,9 +192,9 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
192
192
  {item.type === "ellipsis" ? (
193
193
  <span
194
194
  data-slot="pagination-ellipsis"
195
- aria-hidden="true"
196
- title={ellipsisLabel}
197
195
  className={paginationEllipsisVariants({ size })}
196
+ role="img"
197
+ aria-label={ellipsisLabel}
198
198
  >
199
199
 
200
200
  </span>
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useMemo } from "react";
3
+ import { useCallback, useId, useMemo, useRef, useState } from "react";
4
4
  import { motion } from "framer-motion";
5
5
 
6
6
  import { cn, clamp } from "../../../lib/utils";
@@ -39,6 +39,32 @@ export function ProgressAnimated({
39
39
  }: ProgressAnimatedProps) {
40
40
  const clamped = clamp(value, min, max);
41
41
  const percent = max === min ? 0 : ((clamped - min) / (max - min)) * 100;
42
+ const labelSlotId = `${useId()}-progress-label`;
43
+ const labelSlotCountRef = useRef(0);
44
+ const [labelSlotMounted, setLabelSlotMounted] = useState(false);
45
+ const registerProgressLabel = useCallback(() => {
46
+ labelSlotCountRef.current += 1;
47
+ if (labelSlotCountRef.current === 1) {
48
+ setLabelSlotMounted(true);
49
+ }
50
+ return () => {
51
+ labelSlotCountRef.current -= 1;
52
+ if (labelSlotCountRef.current === 0) {
53
+ setLabelSlotMounted(false);
54
+ }
55
+ };
56
+ }, []);
57
+ const hasInlineLabelProp = Boolean(label?.trim().length);
58
+
59
+ const labelingProps = useMemo(() => {
60
+ if (hasInlineLabelProp) {
61
+ return { "aria-label": label?.trim() ?? "Progress" };
62
+ }
63
+ if (labelSlotMounted) {
64
+ return { "aria-labelledby": labelSlotId };
65
+ }
66
+ return { "aria-label": "Progress" };
67
+ }, [hasInlineLabelProp, label, labelSlotId, labelSlotMounted]);
42
68
 
43
69
  const ctx = useMemo<ProgressCtx>(
44
70
  () => ({
@@ -50,8 +76,21 @@ export function ProgressAnimated({
50
76
  striped: Boolean(striped),
51
77
  animated: Boolean(animated),
52
78
  appearance: appearance ?? "default",
79
+ labelSlotId,
80
+ registerProgressLabel,
53
81
  }),
54
- [animated, appearance, clamped, max, min, shape, size, striped],
82
+ [
83
+ animated,
84
+ appearance,
85
+ clamped,
86
+ labelSlotId,
87
+ max,
88
+ min,
89
+ registerProgressLabel,
90
+ shape,
91
+ size,
92
+ striped,
93
+ ],
55
94
  );
56
95
 
57
96
  const motionProps = progressAnimationPresets[animation];
@@ -65,8 +104,8 @@ export function ProgressAnimated({
65
104
  aria-valuemin={min}
66
105
  aria-valuemax={max}
67
106
  aria-valuenow={clamped}
68
- aria-label={label}
69
107
  aria-busy={busy ? true : undefined}
108
+ {...labelingProps}
70
109
  className={cn(
71
110
  progressVariants({ appearance, size, shape, striped, animated }),
72
111
  className,
@@ -1,6 +1,15 @@
1
1
  "use client";
2
2
 
3
- import { createContext, useContext, useMemo } from "react";
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useId,
10
+ useRef,
11
+ useState,
12
+ } from "react";
4
13
 
5
14
  import { cn, clamp } from "../../lib/utils";
6
15
 
@@ -40,6 +49,32 @@ export function ProgressBase(props: ProgressProps) {
40
49
  } = props;
41
50
  const clamped = clamp(value, min, max);
42
51
  const percent = max === min ? 0 : ((clamped - min) / (max - min)) * 100;
52
+ const labelSlotId = `${useId()}-progress-label`;
53
+ const labelSlotCountRef = useRef(0);
54
+ const [labelSlotMounted, setLabelSlotMounted] = useState(false);
55
+ const registerProgressLabel = useCallback(() => {
56
+ labelSlotCountRef.current += 1;
57
+ if (labelSlotCountRef.current === 1) {
58
+ setLabelSlotMounted(true);
59
+ }
60
+ return () => {
61
+ labelSlotCountRef.current -= 1;
62
+ if (labelSlotCountRef.current === 0) {
63
+ setLabelSlotMounted(false);
64
+ }
65
+ };
66
+ }, []);
67
+ const hasInlineLabelProp = Boolean(label?.trim().length);
68
+
69
+ const labelingProps = useMemo(() => {
70
+ if (hasInlineLabelProp) {
71
+ return { "aria-label": label?.trim() ?? "Progress" };
72
+ }
73
+ if (labelSlotMounted) {
74
+ return { "aria-labelledby": labelSlotId };
75
+ }
76
+ return { "aria-label": "Progress" };
77
+ }, [hasInlineLabelProp, label, labelSlotId, labelSlotMounted]);
43
78
 
44
79
  const ctx = useMemo(
45
80
  () => ({
@@ -51,8 +86,21 @@ export function ProgressBase(props: ProgressProps) {
51
86
  striped: Boolean(striped),
52
87
  animated: Boolean(animated),
53
88
  appearance: appearance ?? "default",
89
+ labelSlotId,
90
+ registerProgressLabel,
54
91
  }),
55
- [animated, appearance, clamped, max, min, shape, size, striped],
92
+ [
93
+ animated,
94
+ appearance,
95
+ clamped,
96
+ labelSlotId,
97
+ max,
98
+ min,
99
+ registerProgressLabel,
100
+ shape,
101
+ size,
102
+ striped,
103
+ ],
56
104
  );
57
105
 
58
106
  return (
@@ -64,7 +112,7 @@ export function ProgressBase(props: ProgressProps) {
64
112
  aria-valuemin={min}
65
113
  aria-valuemax={max}
66
114
  aria-valuenow={clamped}
67
- aria-label={label}
115
+ {...labelingProps}
68
116
  className={cn(
69
117
  progressVariants({ appearance, size, shape, striped, animated }),
70
118
  className,
@@ -121,8 +169,16 @@ export function ProgressBar({
121
169
  ProgressBar.displayName = "ProgressBar";
122
170
 
123
171
  export function ProgressLabel({ className, children }: ProgressSectionProps) {
172
+ const { labelSlotId, registerProgressLabel } =
173
+ useProgressContext("ProgressLabel");
174
+
175
+ useEffect(() => {
176
+ return registerProgressLabel();
177
+ }, [registerProgressLabel]);
178
+
124
179
  return (
125
180
  <div
181
+ id={labelSlotId}
126
182
  data-slot="progress-label"
127
183
  className={cn("mb-2 font-medium text-slate-200", className)}
128
184
  >
@@ -30,4 +30,7 @@ export type ProgressCtx = {
30
30
  striped: boolean;
31
31
  animated: boolean;
32
32
  appearance: NonNullable<ProgressProps["appearance"]>;
33
+ labelSlotId: string;
34
+ /** Increments the label mount count; returned cleanup decrements. Multiple labels are supported. */
35
+ registerProgressLabel: () => () => void;
33
36
  };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { forwardRef, useId } from "react";
3
+ import { useId } from "react";
4
4
 
5
5
  import { cn } from "../../lib/utils";
6
6
  import { inputVariants } from "../inputs/variants";
@@ -24,6 +24,7 @@ export const SearchBar = function SearchBar(
24
24
  comboboxListboxId,
25
25
  comboboxActiveOptionId,
26
26
  comboboxExpanded,
27
+ "aria-label": ariaLabel,
27
28
  ref,
28
29
  ...rest
29
30
  }: SearchBarProps,
@@ -53,6 +54,10 @@ export const SearchBar = function SearchBar(
53
54
  spellCheck={false}
54
55
  disabled={disabled}
55
56
  value={value}
57
+ aria-label={
58
+ ariaLabel ??
59
+ (combobox ? undefined : "Search")
60
+ }
56
61
  data-slot="search-bar-input"
57
62
  className={cn(
58
63
  inputVariants({ appearance, size: inputSize, ring, as: "input" }),
@@ -31,9 +31,21 @@ export function SearchSuggestionList({
31
31
  );
32
32
  }
33
33
 
34
- let lastGroup: string | undefined;
35
34
  const useListbox = Boolean(listboxId);
36
35
 
36
+ const rows: Array<{
37
+ item: (typeof items)[number];
38
+ showGroup: boolean;
39
+ }> = [];
40
+ let lastGroupSeen: string | undefined;
41
+ for (const item of items) {
42
+ const showGroup = Boolean(item.group && item.group !== lastGroupSeen);
43
+ if (item.group) {
44
+ lastGroupSeen = item.group;
45
+ }
46
+ rows.push({ item, showGroup });
47
+ }
48
+
37
49
  return (
38
50
  <nav
39
51
  data-slot="search-suggestion-list"
@@ -49,11 +61,7 @@ export function SearchSuggestionList({
49
61
  : {})}
50
62
  className={cn("flex flex-col gap-0.5", listClassName)}
51
63
  >
52
- {items.map((item) => {
53
- const showGroup = item.group && item.group !== lastGroup;
54
- if (item.group) {
55
- lastGroup = item.group;
56
- }
64
+ {rows.map(({ item, showGroup }) => {
57
65
  const isActive = activeId === item.id;
58
66
  const optionDomId =
59
67
  useListbox && listboxId ? searchSuggestionOptionDomId(listboxId, item.id) : undefined;