@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
@@ -5,6 +5,7 @@ import {
5
5
  KeyboardEvent,
6
6
  useContext,
7
7
  useId,
8
+ useRef,
8
9
  useState,
9
10
  } from "react";
10
11
 
@@ -41,6 +42,7 @@ export function Tabs({
41
42
  }: TabsProps) {
42
43
  const [internalValue, setInternalValue] = useState(defaultValue);
43
44
  const idPrefix = useId();
45
+ const listRef = useRef<HTMLDivElement | null>(null);
44
46
 
45
47
  const isControlled = value !== undefined;
46
48
  const currentValue = isControlled ? value : internalValue;
@@ -60,6 +62,7 @@ export function Tabs({
60
62
  value={{
61
63
  value: currentValue,
62
64
  setValue,
65
+ listRef,
63
66
  orientation,
64
67
  size,
65
68
  variant,
@@ -76,10 +79,11 @@ export function Tabs({
76
79
  }
77
80
 
78
81
  export function TabsList({ children, className, ...props }: TabsListProps) {
79
- const { orientation, size } = useTabs();
82
+ const { orientation, size, listRef } = useTabs();
80
83
 
81
84
  return (
82
85
  <div
86
+ ref={listRef}
83
87
  role="tablist"
84
88
  aria-orientation={orientation}
85
89
  className={cn(tabsListVariants({ orientation, size }), className)}
@@ -100,6 +104,8 @@ export function TabsTrigger({
100
104
  const {
101
105
  value: activeValue,
102
106
  setValue,
107
+ listRef,
108
+ orientation,
103
109
  tabTriggerId,
104
110
  tabPanelId,
105
111
  size,
@@ -110,30 +116,84 @@ export function TabsTrigger({
110
116
  const isActive = activeValue === value;
111
117
 
112
118
  const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
113
- const triggers = Array.from(
114
- document.querySelectorAll('[role="tab"]'),
115
- ) as HTMLElement[];
119
+ const list = listRef.current;
120
+ const triggers =
121
+ list === null
122
+ ? []
123
+ : Array.from(list.querySelectorAll<HTMLButtonElement>('[role="tab"]'));
124
+
125
+ const nextKeys =
126
+ orientation === "vertical" ? ["ArrowDown"] : ["ArrowRight"];
127
+ const prevKeys = orientation === "vertical" ? ["ArrowUp"] : ["ArrowLeft"];
116
128
 
117
129
  const index = triggers.findIndex((el) => el === e.currentTarget);
130
+ if (index === -1) {
131
+ return;
132
+ }
118
133
 
119
- if (e.key === "ArrowRight" || e.key === "ArrowDown") {
134
+ const findEnabledIndex = (
135
+ start: number,
136
+ direction: 1 | -1,
137
+ ): number | undefined => {
138
+ const n = triggers.length;
139
+ if (n === 0) {
140
+ return undefined;
141
+ }
142
+ let i = start;
143
+ for (let step = 0; step < n; step += 1) {
144
+ i = (i + direction + n) % n;
145
+ if (triggers[i]?.disabled !== true) {
146
+ return i;
147
+ }
148
+ }
149
+ return undefined;
150
+ };
151
+
152
+ const focusAt = (i: number) => {
153
+ const target = triggers[i];
154
+ if (target !== undefined && target.disabled !== true) {
155
+ target.focus();
156
+ }
157
+ };
158
+
159
+ const isNext = nextKeys.includes(e.key);
160
+ const isPrev = prevKeys.includes(e.key);
161
+
162
+ if (isNext) {
120
163
  e.preventDefault();
121
- triggers[index + 1]?.focus();
164
+ const nextIdx = findEnabledIndex(index, 1);
165
+ if (nextIdx !== undefined) {
166
+ focusAt(nextIdx);
167
+ }
168
+ return;
122
169
  }
123
170
 
124
- if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
171
+ if (isPrev) {
125
172
  e.preventDefault();
126
- triggers[index - 1]?.focus();
173
+ const prevIdx = findEnabledIndex(index, -1);
174
+ if (prevIdx !== undefined) {
175
+ focusAt(prevIdx);
176
+ }
177
+ return;
127
178
  }
128
179
 
129
180
  if (e.key === "Home") {
130
181
  e.preventDefault();
131
- triggers[0]?.focus();
182
+ const firstEnabledIndex = triggers.findIndex((btn) => !btn.disabled);
183
+ if (firstEnabledIndex !== -1) {
184
+ triggers[firstEnabledIndex]?.focus();
185
+ }
186
+ return;
132
187
  }
133
188
 
134
189
  if (e.key === "End") {
135
190
  e.preventDefault();
136
- triggers[triggers.length - 1]?.focus();
191
+ for (let i = triggers.length - 1; i >= 0; i -= 1) {
192
+ if (!triggers[i]?.disabled) {
193
+ triggers[i]?.focus();
194
+ break;
195
+ }
196
+ }
137
197
  }
138
198
  };
139
199
 
@@ -146,6 +206,7 @@ export function TabsTrigger({
146
206
  aria-selected={isActive}
147
207
  aria-controls={tabPanelId(value)}
148
208
  disabled={disabled}
209
+ tabIndex={activeValue === undefined ? undefined : isActive ? 0 : -1}
149
210
  onClick={() => setValue(value)}
150
211
  onKeyDown={handleKeyDown}
151
212
  className={cn(
@@ -1,4 +1,4 @@
1
- import type { ElementType, HTMLAttributes, ReactNode } from "react";
1
+ import type { ElementType, HTMLAttributes, ReactNode, RefObject } from "react";
2
2
 
3
3
  export type TabsValue = string;
4
4
 
@@ -59,6 +59,7 @@ export type TabsContentProps = {
59
59
  export type TabsContextType = {
60
60
  value: TabsValue | undefined;
61
61
  setValue: (value: TabsValue) => void;
62
+ listRef: RefObject<HTMLDivElement | null>;
62
63
  orientation: "horizontal" | "vertical";
63
64
  size?: "sm" | "md" | "lg";
64
65
  variant?: "default" | "underline" | "pills";
@@ -24,7 +24,7 @@ export const tabsListVariants = cva("flex items-center gap-1", {
24
24
  });
25
25
 
26
26
  export const tabsTriggerVariants = cva(
27
- "px-3 py-1.5 rounded-md transition-all focus:outline-none focus:ring-2 focus:ring-ring",
27
+ "px-3 py-1.5 rounded-md transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
28
28
  {
29
29
  variants: {
30
30
  appearance: {
@@ -217,7 +217,7 @@ export function ToastClose({
217
217
  data-slot="toast-close"
218
218
  aria-label="Dismiss notification"
219
219
  className={cn(
220
- "absolute right-3 top-3 inline-flex size-8 items-center justify-center rounded-md text-slate-200 transition hover:bg-white/10",
220
+ "absolute right-3 top-3 inline-flex size-8 items-center justify-center rounded-md text-slate-200 transition hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950",
221
221
  className,
222
222
  )}
223
223
  onClick={onClick}
@@ -1,12 +1,31 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useId, useState, type ReactNode } from "react";
4
4
 
5
5
  import { cn } from "../../lib/utils";
6
6
 
7
7
  import type { ToggleProps } from "./types";
8
8
  import { toggleThumbVariants, toggleTrackVariants } from "./variants";
9
9
 
10
+ function hasToggleLabelChildren(node: ReactNode): boolean {
11
+ if (node === undefined || node === null) {
12
+ return false;
13
+ }
14
+ if (typeof node === "boolean") {
15
+ return false;
16
+ }
17
+ if (typeof node === "string") {
18
+ return node.trim().length > 0;
19
+ }
20
+ if (typeof node === "number") {
21
+ return true;
22
+ }
23
+ if (Array.isArray(node)) {
24
+ return node.some(hasToggleLabelChildren);
25
+ }
26
+ return true;
27
+ }
28
+
10
29
  export function ToggleBase(props: ToggleProps) {
11
30
  const {
12
31
  className,
@@ -17,10 +36,11 @@ export function ToggleBase(props: ToggleProps) {
17
36
  onCheckedChange,
18
37
  disabled,
19
38
  ref,
20
- "aria-label": ariaLabel = "Toggle",
39
+ "aria-label": ariaLabelProp,
21
40
  children,
22
41
  ...rest
23
42
  } = props;
43
+ const toggleLabelId = useId();
24
44
  const isControlled = checked !== undefined;
25
45
  const [uncontrolled, setUncontrolled] = useState(defaultChecked);
26
46
  const resolved = isControlled ? Boolean(checked) : uncontrolled;
@@ -36,6 +56,15 @@ export function ToggleBase(props: ToggleProps) {
36
56
  );
37
57
 
38
58
  const thumbShiftPx = size === "sm" ? 14 : size === "lg" ? 26 : 20;
59
+ const labeledByChildren = hasToggleLabelChildren(children);
60
+ const labeling =
61
+ labeledByChildren ?
62
+ {
63
+ "aria-labelledby": toggleLabelId,
64
+ }
65
+ : {
66
+ "aria-label": ariaLabelProp ?? "Toggle",
67
+ };
39
68
 
40
69
  return (
41
70
  <button
@@ -44,10 +73,10 @@ export function ToggleBase(props: ToggleProps) {
44
73
  role="switch"
45
74
  data-slot="toggle"
46
75
  aria-checked={resolved}
47
- aria-label={ariaLabel}
48
76
  data-state={resolved ? "checked" : "unchecked"}
49
77
  disabled={disabled}
50
78
  className={cn(toggleTrackVariants({ size, appearance }), className)}
79
+ {...labeling}
51
80
  onClick={() => {
52
81
  if (!disabled) {
53
82
  setChecked(!resolved);
@@ -55,7 +84,11 @@ export function ToggleBase(props: ToggleProps) {
55
84
  }}
56
85
  {...rest}
57
86
  >
58
- <span className="sr-only">{children}</span>
87
+ {labeledByChildren ?
88
+ <span id={toggleLabelId} className="sr-only">
89
+ {children}
90
+ </span>
91
+ : null}
59
92
  <span
60
93
  className={cn(
61
94
  toggleThumbVariants({ size }),
@@ -1,12 +1,20 @@
1
1
  "use client";
2
2
 
3
3
  import {
4
+ Children,
5
+ cloneElement,
4
6
  createContext,
7
+ isValidElement,
5
8
  useCallback,
6
9
  useContext,
7
10
  useEffect,
11
+ useId,
8
12
  useRef,
9
13
  useState,
14
+ type KeyboardEventHandler,
15
+ type MouseEventHandler,
16
+ type FocusEventHandler,
17
+ type ReactElement,
10
18
  } from "react";
11
19
 
12
20
  import { cn } from "../../lib/utils";
@@ -29,6 +37,22 @@ export const useTooltip = () => {
29
37
  return context;
30
38
  };
31
39
 
40
+ function mergeDescribedBy(
41
+ tooltipId: string,
42
+ existing: unknown,
43
+ open: boolean,
44
+ ): string | undefined {
45
+ if (!open) {
46
+ return typeof existing === "string" ? existing : undefined;
47
+ }
48
+ const baseIds =
49
+ typeof existing === "string" && existing.trim().length > 0
50
+ ? existing.split(/\s+/).filter(Boolean)
51
+ : [];
52
+ const merged = [...new Set([...baseIds, tooltipId])];
53
+ return merged.join(" ");
54
+ }
55
+
32
56
  export const Tooltip = ({
33
57
  children,
34
58
  defaultOpen = false,
@@ -38,6 +62,7 @@ export const Tooltip = ({
38
62
  delay = 100,
39
63
  }: TooltipProps) => {
40
64
  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
65
+ const tooltipId = `${useId()}-tooltip`;
41
66
 
42
67
  const isControlled = controlledOpen !== undefined;
43
68
  const open = isControlled ? controlledOpen : uncontrolledOpen;
@@ -78,6 +103,7 @@ export const Tooltip = ({
78
103
  delay,
79
104
  scheduleDelayedOpen,
80
105
  cancelDelayedOpen,
106
+ tooltipId,
81
107
  }}
82
108
  >
83
109
  <div className="relative inline-block">{children}</div>
@@ -89,33 +115,103 @@ export const TooltipTrigger = ({
89
115
  children,
90
116
  className,
91
117
  }: TooltipTriggerProps) => {
92
- const { setOpen, scheduleDelayedOpen, cancelDelayedOpen } = useTooltip();
118
+ const { setOpen, scheduleDelayedOpen, cancelDelayedOpen, open, tooltipId } =
119
+ useTooltip();
93
120
 
94
- const triggerProps = {
95
- onMouseEnter: () => scheduleDelayedOpen(),
96
- onMouseLeave: () => {
97
- cancelDelayedOpen();
98
- setOpen(false);
99
- },
100
- onFocus: () => {
101
- cancelDelayedOpen();
102
- setOpen(true);
103
- },
104
- onBlur: () => {
121
+ const onMouseEnter: MouseEventHandler = () => scheduleDelayedOpen();
122
+ const onMouseLeave: MouseEventHandler = () => {
123
+ cancelDelayedOpen();
124
+ setOpen(false);
125
+ };
126
+ const onFocus: FocusEventHandler = () => {
127
+ cancelDelayedOpen();
128
+ setOpen(true);
129
+ };
130
+ const onBlur: FocusEventHandler = () => {
131
+ cancelDelayedOpen();
132
+ setOpen(false);
133
+ };
134
+ const onKeyDown: KeyboardEventHandler = (event) => {
135
+ if (event.key === "Escape") {
105
136
  cancelDelayedOpen();
106
137
  setOpen(false);
107
- },
108
- onKeyDown: (e: React.KeyboardEvent) => {
109
- if (e.key === "Escape") {
138
+ }
139
+ };
140
+
141
+ const childList = Children.toArray(children).filter(
142
+ (node) =>
143
+ node !== null && node !== undefined && typeof node !== "boolean",
144
+ );
145
+
146
+ const soleCandidate =
147
+ childList.length === 1 && isValidElement(childList[0])
148
+ ? (childList[0] as ReactElement<{
149
+ className?: string;
150
+ "aria-describedby"?: string;
151
+ onMouseEnter?: MouseEventHandler;
152
+ onMouseLeave?: MouseEventHandler;
153
+ onFocus?: FocusEventHandler;
154
+ onBlur?: FocusEventHandler;
155
+ onKeyDown?: KeyboardEventHandler;
156
+ }>)
157
+ : undefined;
158
+
159
+ if (soleCandidate) {
160
+ const describedBy = mergeDescribedBy(
161
+ tooltipId,
162
+ soleCandidate.props["aria-describedby"],
163
+ open,
164
+ );
165
+ return cloneElement(soleCandidate, {
166
+ onMouseEnter: (event: React.MouseEvent) => {
167
+ soleCandidate.props.onMouseEnter?.(event);
168
+ if (!event.defaultPrevented) {
169
+ scheduleDelayedOpen();
170
+ }
171
+ },
172
+ onMouseLeave: (event: React.MouseEvent) => {
173
+ soleCandidate.props.onMouseLeave?.(event);
110
174
  cancelDelayedOpen();
111
175
  setOpen(false);
112
- }
113
- },
114
- className,
115
- tabIndex: 0,
116
- };
176
+ },
177
+ onFocus: (event: React.FocusEvent) => {
178
+ soleCandidate.props.onFocus?.(event);
179
+ if (!event.defaultPrevented) {
180
+ cancelDelayedOpen();
181
+ setOpen(true);
182
+ }
183
+ },
184
+ onBlur: (event: React.FocusEvent) => {
185
+ soleCandidate.props.onBlur?.(event);
186
+ cancelDelayedOpen();
187
+ setOpen(false);
188
+ },
189
+ onKeyDown: (event: React.KeyboardEvent) => {
190
+ soleCandidate.props.onKeyDown?.(event);
191
+ if (event.key === "Escape") {
192
+ cancelDelayedOpen();
193
+ setOpen(false);
194
+ }
195
+ },
196
+ className: cn(className, soleCandidate.props.className),
197
+ "aria-describedby": describedBy,
198
+ });
199
+ }
117
200
 
118
- return <span {...triggerProps}>{children}</span>;
201
+ return (
202
+ <span
203
+ className={className}
204
+ tabIndex={0}
205
+ aria-describedby={mergeDescribedBy(tooltipId, undefined, open)}
206
+ onMouseEnter={onMouseEnter}
207
+ onMouseLeave={onMouseLeave}
208
+ onFocus={onFocus}
209
+ onBlur={onBlur}
210
+ onKeyDown={onKeyDown}
211
+ >
212
+ {children}
213
+ </span>
214
+ );
119
215
  };
120
216
 
121
217
  export const TooltipContent = ({
@@ -125,7 +221,7 @@ export const TooltipContent = ({
125
221
  size,
126
222
  width,
127
223
  }: TooltipContentProps) => {
128
- const { open, position } = useTooltip();
224
+ const { open, position, tooltipId } = useTooltip();
129
225
 
130
226
  if (!open) return null;
131
227
 
@@ -138,6 +234,7 @@ export const TooltipContent = ({
138
234
 
139
235
  return (
140
236
  <div
237
+ id={tooltipId}
141
238
  data-open={open}
142
239
  role="tooltip"
143
240
  className={cn(
@@ -9,6 +9,7 @@ export type TooltipContextType = {
9
9
  delay: number;
10
10
  scheduleDelayedOpen: () => void;
11
11
  cancelDelayedOpen: () => void;
12
+ tooltipId: string;
12
13
  };
13
14
 
14
15
  export type TooltipProps = {
@@ -9,12 +9,12 @@ export const tooltipVariants = cva(
9
9
  outline: "border bg-white text-black",
10
10
  ghost: "bg-gray-800 text-white/90",
11
11
  glass: "border border-white/15 bg-white/10 text-white backdrop-blur-md",
12
- emerald: "bg-emerald-600 text-white",
12
+ emerald: "bg-emerald-800 text-white",
13
13
  indigo: "bg-indigo-600 text-white",
14
14
  purple: "bg-purple-600 text-white",
15
15
  pink: "bg-pink-600 text-white",
16
16
  rose: "bg-rose-600 text-white",
17
- sky: "bg-sky-600 text-white",
17
+ sky: "bg-sky-700 text-white",
18
18
  teal: "bg-teal-600 text-white",
19
19
  yellow: "bg-yellow-600 text-white",
20
20
  orange: "bg-orange-600 text-white",
@@ -0,0 +1,39 @@
1
+ import { forwardRef } from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ import type { BlockquoteProps } from "./types";
6
+ import { typographyToneVariants } from "./variants";
7
+
8
+ export const BlockquoteBase = (props: BlockquoteProps) => {
9
+ const {
10
+ tone,
11
+ attribution,
12
+ className,
13
+ children,
14
+ ref,
15
+ ...rest
16
+ } = props;
17
+
18
+ return (
19
+ <blockquote
20
+ ref={ref}
21
+ data-slot="typography-blockquote"
22
+ className={cn(
23
+ typographyToneVariants({ tone }),
24
+ "border-l-4 py-1 pl-4 italic",
25
+ className,
26
+ )}
27
+ {...rest}
28
+ >
29
+ <div className="space-y-2 leading-relaxed">{children}</div>
30
+ {attribution ? (
31
+ <footer className="mt-3 text-sm not-italic">
32
+ <cite>{attribution}</cite>
33
+ </footer>
34
+ ) : null}
35
+ </blockquote>
36
+ );
37
+ };
38
+
39
+ BlockquoteBase.displayName = "Blockquote";
@@ -0,0 +1,8 @@
1
+ import { BlockquoteBase } from "./blockquote-base";
2
+ import type { BlockquoteProps } from "./types";
3
+
4
+ export const Blockquote = (props: BlockquoteProps) => {
5
+ return <BlockquoteBase {...props} />;
6
+ };
7
+
8
+ Blockquote.displayName = "Blockquote";
@@ -0,0 +1,37 @@
1
+ import { forwardRef } from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ import type { CodeBlockProps } from "./types";
6
+ import { typographyToneVariants } from "./variants";
7
+
8
+ export const CodeBlockBase = (props: CodeBlockProps) => {
9
+ const {
10
+ tone,
11
+ language,
12
+ className,
13
+ children,
14
+ ref,
15
+ ...rest
16
+ } = props;
17
+
18
+ const ariaLabel = language ? `Code sample (${language})` : "Code sample";
19
+
20
+ return (
21
+ <pre
22
+ ref={ref}
23
+ data-slot="typography-code-block"
24
+ aria-label={ariaLabel}
25
+ className={cn(
26
+ typographyToneVariants({ tone }),
27
+ "overflow-x-auto rounded-xl border border-white/10 bg-slate-950/80 p-4 text-sm leading-relaxed shadow-inner shadow-slate-950/40",
28
+ className,
29
+ )}
30
+ {...rest}
31
+ >
32
+ <code className="font-mono text-[0.95em] whitespace-pre-wrap wrap-break-word">{children}</code>
33
+ </pre>
34
+ );
35
+ };
36
+
37
+ CodeBlockBase.displayName = "CodeBlock";
@@ -0,0 +1,8 @@
1
+ import { CodeBlockBase } from "./code-block-base";
2
+ import type { CodeBlockProps } from "./types";
3
+
4
+ export const CodeBlock = (props: CodeBlockProps) => {
5
+ return <CodeBlockBase {...props} />;
6
+ };
7
+
8
+ CodeBlock.displayName = "CodeBlock";
@@ -0,0 +1,59 @@
1
+ import { forwardRef } from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ import type { HeadingProps } from "./types";
6
+ import {
7
+ headingLevelVariants,
8
+ typographyToneVariants,
9
+ } from "./variants";
10
+
11
+ const HEADING_TAGS = {
12
+ 1: "h1",
13
+ 2: "h2",
14
+ 3: "h3",
15
+ 4: "h4",
16
+ 5: "h5",
17
+ 6: "h6",
18
+ } as const;
19
+
20
+ export const HeadingBase = (props: HeadingProps) => {
21
+ const {
22
+ level,
23
+ displayLevel,
24
+ tone,
25
+ bold,
26
+ italic,
27
+ underline,
28
+ strikethrough,
29
+ ref,
30
+ className,
31
+ children,
32
+ ...rest
33
+ } = props;
34
+
35
+ const Tag = HEADING_TAGS[level];
36
+ const scale = displayLevel ?? level;
37
+
38
+ return (
39
+ <Tag
40
+ ref={ref}
41
+ data-slot="typography-heading"
42
+ data-level={level}
43
+ className={cn(
44
+ typographyToneVariants({ tone }),
45
+ headingLevelVariants({ level: scale }),
46
+ bold && "font-bold",
47
+ italic && "italic",
48
+ underline && "underline underline-offset-4",
49
+ strikethrough && "line-through",
50
+ className,
51
+ )}
52
+ {...rest}
53
+ >
54
+ {children}
55
+ </Tag>
56
+ );
57
+ };
58
+
59
+ HeadingBase.displayName = "Heading";
@@ -0,0 +1,8 @@
1
+ import { HeadingBase } from "./heading-base";
2
+ import type { HeadingProps } from "./types";
3
+
4
+ export const Heading = (props: HeadingProps) => {
5
+ return <HeadingBase {...props} />;
6
+ };
7
+
8
+ Heading.displayName = "Heading";
@@ -0,0 +1,28 @@
1
+ export { Heading } from "./heading";
2
+ export { Text } from "./text";
3
+ export { List, ListItem } from "./list";
4
+ export { Blockquote } from "./blockquote";
5
+ export { InlineCode } from "./inline-code";
6
+ export { CodeBlock } from "./code-block";
7
+
8
+ export type {
9
+ BlockquoteProps,
10
+ CodeBlockProps,
11
+ HeadingLevel,
12
+ HeadingProps,
13
+ InlineCodeProps,
14
+ ListItemProps,
15
+ ListProps,
16
+ TextElement,
17
+ TextProps,
18
+ TypographyTone,
19
+ UnorderedMarker,
20
+ } from "./types";
21
+
22
+ export {
23
+ headingLevelVariants,
24
+ orderedListVariants,
25
+ textSizeVariants,
26
+ typographyToneVariants,
27
+ unorderedListMarkerVariants,
28
+ } from "./variants";