@dashforge/tw 0.1.0-beta

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 (299) hide show
  1. package/.swcrc +29 -0
  2. package/CHANGELOG.md +131 -0
  3. package/LICENSE +21 -0
  4. package/README.md +244 -0
  5. package/dist/components/AppShell/AppShell.d.ts +32 -0
  6. package/dist/components/AppShell/AppShell.d.ts.map +1 -0
  7. package/dist/components/AppShell/appShell.types.d.ts +62 -0
  8. package/dist/components/AppShell/appShell.types.d.ts.map +1 -0
  9. package/dist/components/AppShell/appShell.variants.d.ts +65 -0
  10. package/dist/components/AppShell/appShell.variants.d.ts.map +1 -0
  11. package/dist/components/Autocomplete/Autocomplete.d.ts +32 -0
  12. package/dist/components/Autocomplete/Autocomplete.d.ts.map +1 -0
  13. package/dist/components/Autocomplete/autocomplete.types.d.ts +214 -0
  14. package/dist/components/Autocomplete/autocomplete.types.d.ts.map +1 -0
  15. package/dist/components/Autocomplete/autocomplete.variants.d.ts +214 -0
  16. package/dist/components/Autocomplete/autocomplete.variants.d.ts.map +1 -0
  17. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +23 -0
  18. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -0
  19. package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts +98 -0
  20. package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts.map +1 -0
  21. package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts +85 -0
  22. package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts.map +1 -0
  23. package/dist/components/Button/Button.d.ts +44 -0
  24. package/dist/components/Button/Button.d.ts.map +1 -0
  25. package/dist/components/Button/button.types.d.ts +66 -0
  26. package/dist/components/Button/button.types.d.ts.map +1 -0
  27. package/dist/components/Button/button.variants.d.ts +104 -0
  28. package/dist/components/Button/button.variants.d.ts.map +1 -0
  29. package/dist/components/Checkbox/Checkbox.d.ts +31 -0
  30. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
  31. package/dist/components/Checkbox/checkbox.types.d.ts +86 -0
  32. package/dist/components/Checkbox/checkbox.types.d.ts.map +1 -0
  33. package/dist/components/Checkbox/checkbox.variants.d.ts +109 -0
  34. package/dist/components/Checkbox/checkbox.variants.d.ts.map +1 -0
  35. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +38 -0
  36. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -0
  37. package/dist/components/ConfirmDialog/confirmDialog.types.d.ts +80 -0
  38. package/dist/components/ConfirmDialog/confirmDialog.types.d.ts.map +1 -0
  39. package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts +90 -0
  40. package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts.map +1 -0
  41. package/dist/components/DateTimePicker/DateTimePicker.d.ts +49 -0
  42. package/dist/components/DateTimePicker/DateTimePicker.d.ts.map +1 -0
  43. package/dist/components/DateTimePicker/dateTimePicker.types.d.ts +95 -0
  44. package/dist/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -0
  45. package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts +165 -0
  46. package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -0
  47. package/dist/components/LeftNav/LeftNav.d.ts +34 -0
  48. package/dist/components/LeftNav/LeftNav.d.ts.map +1 -0
  49. package/dist/components/LeftNav/leftNav.types.d.ts +136 -0
  50. package/dist/components/LeftNav/leftNav.types.d.ts.map +1 -0
  51. package/dist/components/LeftNav/leftNav.variants.d.ts +143 -0
  52. package/dist/components/LeftNav/leftNav.variants.d.ts.map +1 -0
  53. package/dist/components/NumberField/NumberField.d.ts +18 -0
  54. package/dist/components/NumberField/NumberField.d.ts.map +1 -0
  55. package/dist/components/NumberField/numberField.types.d.ts +79 -0
  56. package/dist/components/NumberField/numberField.types.d.ts.map +1 -0
  57. package/dist/components/NumberField/numberField.variants.d.ts +169 -0
  58. package/dist/components/NumberField/numberField.variants.d.ts.map +1 -0
  59. package/dist/components/OTPField/OTPField.d.ts +25 -0
  60. package/dist/components/OTPField/OTPField.d.ts.map +1 -0
  61. package/dist/components/OTPField/otpField.types.d.ts +74 -0
  62. package/dist/components/OTPField/otpField.types.d.ts.map +1 -0
  63. package/dist/components/OTPField/otpField.variants.d.ts +105 -0
  64. package/dist/components/OTPField/otpField.variants.d.ts.map +1 -0
  65. package/dist/components/RadioGroup/RadioGroup.d.ts +35 -0
  66. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -0
  67. package/dist/components/RadioGroup/radioGroup.types.d.ts +103 -0
  68. package/dist/components/RadioGroup/radioGroup.types.d.ts.map +1 -0
  69. package/dist/components/RadioGroup/radioGroup.variants.d.ts +166 -0
  70. package/dist/components/RadioGroup/radioGroup.variants.d.ts.map +1 -0
  71. package/dist/components/Snackbar/Snackbar.d.ts +37 -0
  72. package/dist/components/Snackbar/Snackbar.d.ts.map +1 -0
  73. package/dist/components/Snackbar/snackbar.types.d.ts +86 -0
  74. package/dist/components/Snackbar/snackbar.types.d.ts.map +1 -0
  75. package/dist/components/Snackbar/snackbar.variants.d.ts +153 -0
  76. package/dist/components/Snackbar/snackbar.variants.d.ts.map +1 -0
  77. package/dist/components/Switch/Switch.d.ts +15 -0
  78. package/dist/components/Switch/Switch.d.ts.map +1 -0
  79. package/dist/components/Switch/switch.types.d.ts +47 -0
  80. package/dist/components/Switch/switch.types.d.ts.map +1 -0
  81. package/dist/components/Switch/switch.variants.d.ts +105 -0
  82. package/dist/components/Switch/switch.variants.d.ts.map +1 -0
  83. package/dist/components/TextField/TextField.d.ts +25 -0
  84. package/dist/components/TextField/TextField.d.ts.map +1 -0
  85. package/dist/components/TextField/textField.types.d.ts +72 -0
  86. package/dist/components/TextField/textField.types.d.ts.map +1 -0
  87. package/dist/components/TextField/textField.variants.d.ts +160 -0
  88. package/dist/components/TextField/textField.variants.d.ts.map +1 -0
  89. package/dist/components/Textarea/Textarea.d.ts +16 -0
  90. package/dist/components/Textarea/Textarea.d.ts.map +1 -0
  91. package/dist/components/Textarea/textarea.types.d.ts +63 -0
  92. package/dist/components/Textarea/textarea.types.d.ts.map +1 -0
  93. package/dist/components/Textarea/textarea.variants.d.ts +197 -0
  94. package/dist/components/Textarea/textarea.variants.d.ts.map +1 -0
  95. package/dist/components/TopBar/TopBar.d.ts +33 -0
  96. package/dist/components/TopBar/TopBar.d.ts.map +1 -0
  97. package/dist/components/TopBar/topBar.types.d.ts +49 -0
  98. package/dist/components/TopBar/topBar.types.d.ts.map +1 -0
  99. package/dist/components/TopBar/topBar.variants.d.ts +79 -0
  100. package/dist/components/TopBar/topBar.variants.d.ts.map +1 -0
  101. package/dist/components/_shared/resolveValidationState.d.ts +42 -0
  102. package/dist/components/_shared/resolveValidationState.d.ts.map +1 -0
  103. package/dist/hooks/useAccessState.d.ts +37 -0
  104. package/dist/hooks/useAccessState.d.ts.map +1 -0
  105. package/dist/index.d.ts +82 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.esm.js +4958 -0
  108. package/dist/src/components/AppShell/AppShell.d.ts +32 -0
  109. package/dist/src/components/AppShell/AppShell.d.ts.map +1 -0
  110. package/dist/src/components/AppShell/appShell.types.d.ts +62 -0
  111. package/dist/src/components/AppShell/appShell.types.d.ts.map +1 -0
  112. package/dist/src/components/AppShell/appShell.variants.d.ts +65 -0
  113. package/dist/src/components/AppShell/appShell.variants.d.ts.map +1 -0
  114. package/dist/src/components/Autocomplete/Autocomplete.d.ts +32 -0
  115. package/dist/src/components/Autocomplete/Autocomplete.d.ts.map +1 -0
  116. package/dist/src/components/Autocomplete/autocomplete.types.d.ts +214 -0
  117. package/dist/src/components/Autocomplete/autocomplete.types.d.ts.map +1 -0
  118. package/dist/src/components/Autocomplete/autocomplete.variants.d.ts +214 -0
  119. package/dist/src/components/Autocomplete/autocomplete.variants.d.ts.map +1 -0
  120. package/dist/src/components/Breadcrumbs/Breadcrumbs.d.ts +23 -0
  121. package/dist/src/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -0
  122. package/dist/src/components/Breadcrumbs/breadcrumbs.types.d.ts +98 -0
  123. package/dist/src/components/Breadcrumbs/breadcrumbs.types.d.ts.map +1 -0
  124. package/dist/src/components/Breadcrumbs/breadcrumbs.variants.d.ts +85 -0
  125. package/dist/src/components/Breadcrumbs/breadcrumbs.variants.d.ts.map +1 -0
  126. package/dist/src/components/Button/Button.d.ts +44 -0
  127. package/dist/src/components/Button/Button.d.ts.map +1 -0
  128. package/dist/src/components/Button/button.types.d.ts +66 -0
  129. package/dist/src/components/Button/button.types.d.ts.map +1 -0
  130. package/dist/src/components/Button/button.variants.d.ts +104 -0
  131. package/dist/src/components/Button/button.variants.d.ts.map +1 -0
  132. package/dist/src/components/Checkbox/Checkbox.d.ts +31 -0
  133. package/dist/src/components/Checkbox/Checkbox.d.ts.map +1 -0
  134. package/dist/src/components/Checkbox/checkbox.types.d.ts +86 -0
  135. package/dist/src/components/Checkbox/checkbox.types.d.ts.map +1 -0
  136. package/dist/src/components/Checkbox/checkbox.variants.d.ts +109 -0
  137. package/dist/src/components/Checkbox/checkbox.variants.d.ts.map +1 -0
  138. package/dist/src/components/ConfirmDialog/ConfirmDialog.d.ts +38 -0
  139. package/dist/src/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -0
  140. package/dist/src/components/ConfirmDialog/confirmDialog.types.d.ts +80 -0
  141. package/dist/src/components/ConfirmDialog/confirmDialog.types.d.ts.map +1 -0
  142. package/dist/src/components/ConfirmDialog/confirmDialog.variants.d.ts +90 -0
  143. package/dist/src/components/ConfirmDialog/confirmDialog.variants.d.ts.map +1 -0
  144. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts +49 -0
  145. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts.map +1 -0
  146. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts +95 -0
  147. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -0
  148. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts +165 -0
  149. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -0
  150. package/dist/src/components/LeftNav/LeftNav.d.ts +34 -0
  151. package/dist/src/components/LeftNav/LeftNav.d.ts.map +1 -0
  152. package/dist/src/components/LeftNav/leftNav.types.d.ts +136 -0
  153. package/dist/src/components/LeftNav/leftNav.types.d.ts.map +1 -0
  154. package/dist/src/components/LeftNav/leftNav.variants.d.ts +143 -0
  155. package/dist/src/components/LeftNav/leftNav.variants.d.ts.map +1 -0
  156. package/dist/src/components/NumberField/NumberField.d.ts +18 -0
  157. package/dist/src/components/NumberField/NumberField.d.ts.map +1 -0
  158. package/dist/src/components/NumberField/numberField.types.d.ts +79 -0
  159. package/dist/src/components/NumberField/numberField.types.d.ts.map +1 -0
  160. package/dist/src/components/NumberField/numberField.variants.d.ts +169 -0
  161. package/dist/src/components/NumberField/numberField.variants.d.ts.map +1 -0
  162. package/dist/src/components/OTPField/OTPField.d.ts +25 -0
  163. package/dist/src/components/OTPField/OTPField.d.ts.map +1 -0
  164. package/dist/src/components/OTPField/otpField.types.d.ts +74 -0
  165. package/dist/src/components/OTPField/otpField.types.d.ts.map +1 -0
  166. package/dist/src/components/OTPField/otpField.variants.d.ts +105 -0
  167. package/dist/src/components/OTPField/otpField.variants.d.ts.map +1 -0
  168. package/dist/src/components/RadioGroup/RadioGroup.d.ts +35 -0
  169. package/dist/src/components/RadioGroup/RadioGroup.d.ts.map +1 -0
  170. package/dist/src/components/RadioGroup/radioGroup.types.d.ts +103 -0
  171. package/dist/src/components/RadioGroup/radioGroup.types.d.ts.map +1 -0
  172. package/dist/src/components/RadioGroup/radioGroup.variants.d.ts +166 -0
  173. package/dist/src/components/RadioGroup/radioGroup.variants.d.ts.map +1 -0
  174. package/dist/src/components/Snackbar/Snackbar.d.ts +37 -0
  175. package/dist/src/components/Snackbar/Snackbar.d.ts.map +1 -0
  176. package/dist/src/components/Snackbar/snackbar.types.d.ts +86 -0
  177. package/dist/src/components/Snackbar/snackbar.types.d.ts.map +1 -0
  178. package/dist/src/components/Snackbar/snackbar.variants.d.ts +153 -0
  179. package/dist/src/components/Snackbar/snackbar.variants.d.ts.map +1 -0
  180. package/dist/src/components/Switch/Switch.d.ts +15 -0
  181. package/dist/src/components/Switch/Switch.d.ts.map +1 -0
  182. package/dist/src/components/Switch/switch.types.d.ts +47 -0
  183. package/dist/src/components/Switch/switch.types.d.ts.map +1 -0
  184. package/dist/src/components/Switch/switch.variants.d.ts +105 -0
  185. package/dist/src/components/Switch/switch.variants.d.ts.map +1 -0
  186. package/dist/src/components/TextField/TextField.d.ts +25 -0
  187. package/dist/src/components/TextField/TextField.d.ts.map +1 -0
  188. package/dist/src/components/TextField/textField.types.d.ts +72 -0
  189. package/dist/src/components/TextField/textField.types.d.ts.map +1 -0
  190. package/dist/src/components/TextField/textField.variants.d.ts +160 -0
  191. package/dist/src/components/TextField/textField.variants.d.ts.map +1 -0
  192. package/dist/src/components/Textarea/Textarea.d.ts +16 -0
  193. package/dist/src/components/Textarea/Textarea.d.ts.map +1 -0
  194. package/dist/src/components/Textarea/textarea.types.d.ts +63 -0
  195. package/dist/src/components/Textarea/textarea.types.d.ts.map +1 -0
  196. package/dist/src/components/Textarea/textarea.variants.d.ts +197 -0
  197. package/dist/src/components/Textarea/textarea.variants.d.ts.map +1 -0
  198. package/dist/src/components/TopBar/TopBar.d.ts +33 -0
  199. package/dist/src/components/TopBar/TopBar.d.ts.map +1 -0
  200. package/dist/src/components/TopBar/topBar.types.d.ts +49 -0
  201. package/dist/src/components/TopBar/topBar.types.d.ts.map +1 -0
  202. package/dist/src/components/TopBar/topBar.variants.d.ts +79 -0
  203. package/dist/src/components/TopBar/topBar.variants.d.ts.map +1 -0
  204. package/dist/src/components/_shared/resolveValidationState.d.ts +42 -0
  205. package/dist/src/components/_shared/resolveValidationState.d.ts.map +1 -0
  206. package/dist/src/hooks/useAccessState.d.ts +37 -0
  207. package/dist/src/hooks/useAccessState.d.ts.map +1 -0
  208. package/dist/src/index.d.ts +82 -0
  209. package/dist/src/index.d.ts.map +1 -0
  210. package/dist/src/utils/cn.d.ts +26 -0
  211. package/dist/src/utils/cn.d.ts.map +1 -0
  212. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  213. package/dist/utils/cn.d.ts +26 -0
  214. package/dist/utils/cn.d.ts.map +1 -0
  215. package/eslint.config.mjs +47 -0
  216. package/package.json +37 -0
  217. package/project.json +15 -0
  218. package/rollup.config.cjs +16 -0
  219. package/src/components/AppShell/AppShell.test.tsx +223 -0
  220. package/src/components/AppShell/AppShell.tsx +114 -0
  221. package/src/components/AppShell/appShell.types.ts +49 -0
  222. package/src/components/AppShell/appShell.variants.ts +48 -0
  223. package/src/components/Autocomplete/Autocomplete.perf.test.tsx +225 -0
  224. package/src/components/Autocomplete/Autocomplete.test.tsx +556 -0
  225. package/src/components/Autocomplete/Autocomplete.tsx +996 -0
  226. package/src/components/Autocomplete/autocomplete.types.ts +198 -0
  227. package/src/components/Autocomplete/autocomplete.variants.ts +135 -0
  228. package/src/components/Breadcrumbs/Breadcrumbs.perf.test.tsx +125 -0
  229. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +231 -0
  230. package/src/components/Breadcrumbs/Breadcrumbs.tsx +203 -0
  231. package/src/components/Breadcrumbs/breadcrumbs.types.ts +87 -0
  232. package/src/components/Breadcrumbs/breadcrumbs.variants.ts +49 -0
  233. package/src/components/Button/Button.test.tsx +183 -0
  234. package/src/components/Button/Button.tsx +139 -0
  235. package/src/components/Button/button.types.ts +74 -0
  236. package/src/components/Button/button.variants.ts +159 -0
  237. package/src/components/Checkbox/Checkbox.test.tsx +146 -0
  238. package/src/components/Checkbox/Checkbox.tsx +228 -0
  239. package/src/components/Checkbox/checkbox.types.ts +88 -0
  240. package/src/components/Checkbox/checkbox.variants.ts +68 -0
  241. package/src/components/ConfirmDialog/ConfirmDialog.perf.test.tsx +172 -0
  242. package/src/components/ConfirmDialog/ConfirmDialog.test.tsx +311 -0
  243. package/src/components/ConfirmDialog/ConfirmDialog.tsx +252 -0
  244. package/src/components/ConfirmDialog/confirmDialog.types.ts +70 -0
  245. package/src/components/ConfirmDialog/confirmDialog.variants.ts +76 -0
  246. package/src/components/DateTimePicker/DateTimePicker.perf.test.tsx +153 -0
  247. package/src/components/DateTimePicker/DateTimePicker.test.tsx +204 -0
  248. package/src/components/DateTimePicker/DateTimePicker.tsx +319 -0
  249. package/src/components/DateTimePicker/dateTimePicker.types.ts +83 -0
  250. package/src/components/DateTimePicker/dateTimePicker.variants.ts +84 -0
  251. package/src/components/LeftNav/LeftNav.perf.test.tsx +172 -0
  252. package/src/components/LeftNav/LeftNav.test.tsx +302 -0
  253. package/src/components/LeftNav/LeftNav.tsx +330 -0
  254. package/src/components/LeftNav/leftNav.types.ts +120 -0
  255. package/src/components/LeftNav/leftNav.variants.ts +96 -0
  256. package/src/components/NumberField/NumberField.test.tsx +83 -0
  257. package/src/components/NumberField/NumberField.tsx +293 -0
  258. package/src/components/NumberField/numberField.types.ts +72 -0
  259. package/src/components/NumberField/numberField.variants.ts +94 -0
  260. package/src/components/OTPField/OTPField.test.tsx +121 -0
  261. package/src/components/OTPField/OTPField.tsx +250 -0
  262. package/src/components/OTPField/otpField.types.ts +58 -0
  263. package/src/components/OTPField/otpField.variants.ts +72 -0
  264. package/src/components/RadioGroup/RadioGroup.test.tsx +140 -0
  265. package/src/components/RadioGroup/RadioGroup.tsx +261 -0
  266. package/src/components/RadioGroup/radioGroup.types.ts +85 -0
  267. package/src/components/RadioGroup/radioGroup.variants.ts +87 -0
  268. package/src/components/Snackbar/Snackbar.perf.test.tsx +144 -0
  269. package/src/components/Snackbar/Snackbar.test.tsx +327 -0
  270. package/src/components/Snackbar/Snackbar.tsx +298 -0
  271. package/src/components/Snackbar/snackbar.types.ts +83 -0
  272. package/src/components/Snackbar/snackbar.variants.ts +72 -0
  273. package/src/components/Switch/Switch.test.tsx +131 -0
  274. package/src/components/Switch/Switch.tsx +158 -0
  275. package/src/components/Switch/switch.types.ts +36 -0
  276. package/src/components/Switch/switch.variants.ts +65 -0
  277. package/src/components/TextField/TextField.test.tsx +194 -0
  278. package/src/components/TextField/TextField.tsx +216 -0
  279. package/src/components/TextField/textField.types.ts +70 -0
  280. package/src/components/TextField/textField.variants.ts +87 -0
  281. package/src/components/Textarea/Textarea.test.tsx +75 -0
  282. package/src/components/Textarea/Textarea.tsx +190 -0
  283. package/src/components/Textarea/textarea.types.ts +60 -0
  284. package/src/components/Textarea/textarea.variants.ts +85 -0
  285. package/src/components/TopBar/TopBar.test.tsx +129 -0
  286. package/src/components/TopBar/TopBar.tsx +66 -0
  287. package/src/components/TopBar/topBar.types.ts +42 -0
  288. package/src/components/TopBar/topBar.variants.ts +38 -0
  289. package/src/components/_shared/resolveValidationState.ts +60 -0
  290. package/src/hooks/useAccessState.spec.tsx +161 -0
  291. package/src/hooks/useAccessState.ts +81 -0
  292. package/src/index.ts +191 -0
  293. package/src/utils/cn.spec.ts +16 -0
  294. package/src/utils/cn.ts +29 -0
  295. package/tsconfig.json +13 -0
  296. package/tsconfig.lib.json +55 -0
  297. package/tsconfig.spec.json +34 -0
  298. package/vite.config.ts +25 -0
  299. package/vitest.config.mts +22 -0
@@ -0,0 +1,68 @@
1
+ import { tv, type VariantProps } from 'tailwind-variants';
2
+
3
+ /**
4
+ * Tailwind-variants recipe for `<Checkbox>` with slots.
5
+ *
6
+ * Slots:
7
+ * - `root` — outer flex wrapper enclosing the control + label
8
+ * - `control` — the visible square (Radix `Checkbox.Root`)
9
+ * - `indicator` — checkmark SVG container (Radix `Checkbox.Indicator`)
10
+ * - `label` — text label associated with the control
11
+ * - `helperText` — descriptive line below
12
+ * - `errorText` — error message line (semantic counterpart of helperText)
13
+ *
14
+ * Variants:
15
+ * - `size` : sm / md / lg (drives control box dimensions + label text size)
16
+ * - `error` : bool — swaps borders + helper-text color to danger tones
17
+ */
18
+ export const checkboxVariants = tv({
19
+ slots: {
20
+ root: 'inline-flex items-start gap-2',
21
+ control: [
22
+ 'inline-flex items-center justify-center shrink-0',
23
+ // bg-neutral-50 inverts — dark-mode aware via the dashforge preset.
24
+ 'rounded border bg-neutral-50',
25
+ 'border-neutral-300',
26
+ 'transition-colors',
27
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-primary-500',
28
+ // Radix data-state hooks
29
+ 'data-[state=checked]:bg-primary-500 data-[state=checked]:border-primary-500',
30
+ 'data-[state=indeterminate]:bg-primary-500 data-[state=indeterminate]:border-primary-500',
31
+ 'data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed',
32
+ ],
33
+ indicator: 'flex items-center justify-center text-white',
34
+ label: 'select-none cursor-pointer text-neutral-900',
35
+ helperText: 'text-sm text-neutral-600 mt-1',
36
+ errorText: 'text-sm text-danger-600 mt-1',
37
+ },
38
+ variants: {
39
+ size: {
40
+ sm: {
41
+ control: 'h-4 w-4',
42
+ label: 'text-sm',
43
+ indicator: 'h-3 w-3',
44
+ },
45
+ md: {
46
+ control: 'h-5 w-5',
47
+ label: 'text-base',
48
+ indicator: 'h-3.5 w-3.5',
49
+ },
50
+ lg: {
51
+ control: 'h-6 w-6',
52
+ label: 'text-lg',
53
+ indicator: 'h-4 w-4',
54
+ },
55
+ },
56
+ error: {
57
+ true: {
58
+ control: 'border-danger-500 focus-visible:ring-danger-500',
59
+ },
60
+ },
61
+ },
62
+ defaultVariants: {
63
+ size: 'md',
64
+ error: false,
65
+ },
66
+ });
67
+
68
+ export type CheckboxVariants = VariantProps<typeof checkboxVariants>;
@@ -0,0 +1,172 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * Performance + re-render budget tests for `<ConfirmDialogProvider>`.
4
+ *
5
+ * Guardrails:
6
+ *
7
+ * 1. Provider mount is cheap — it lives near the top of the tree, so
8
+ * it must not chase any expensive setup paths.
9
+ *
10
+ * 2. `confirm()` reference is stable across provider re-renders so
11
+ * consumers can pass it through `useEffect` deps without thrashing.
12
+ *
13
+ * 3. Children do NOT re-render every time a confirm() opens/closes —
14
+ * state lives inside the provider, but a memoized context value
15
+ * plus React's bail-out on identical context means the children
16
+ * subtree stays put.
17
+ *
18
+ * 4. Dismissing the dialog with the queue drained does not allocate
19
+ * a new array (cheap idle path).
20
+ */
21
+ import * as React from 'react';
22
+ import { describe, it, expect, afterEach, beforeAll } from 'vitest';
23
+ import {
24
+ render,
25
+ screen,
26
+ fireEvent,
27
+ cleanup,
28
+ act,
29
+ } from '@testing-library/react';
30
+ import {
31
+ ConfirmDialogProvider,
32
+ useConfirm,
33
+ } from './ConfirmDialog.js';
34
+
35
+ void React;
36
+ afterEach(() => cleanup());
37
+
38
+ beforeAll(() => {
39
+ if (typeof HTMLDialogElement === 'undefined') return;
40
+ const proto = HTMLDialogElement.prototype as unknown as {
41
+ showModal?: () => void;
42
+ close?: (returnValue?: string) => void;
43
+ };
44
+ if (typeof proto.showModal !== 'function') {
45
+ proto.showModal = function (this: HTMLDialogElement) {
46
+ this.setAttribute('open', '');
47
+ };
48
+ }
49
+ if (typeof proto.close !== 'function') {
50
+ proto.close = function (this: HTMLDialogElement) {
51
+ this.removeAttribute('open');
52
+ this.dispatchEvent(new Event('close'));
53
+ };
54
+ }
55
+ });
56
+
57
+ describe('<ConfirmDialogProvider> performance', () => {
58
+ it('mounts under 200ms with empty children (cold-start safe budget)', () => {
59
+ const t0 = performance.now();
60
+ render(
61
+ <ConfirmDialogProvider>
62
+ <div />
63
+ </ConfirmDialogProvider>
64
+ );
65
+ const t1 = performance.now();
66
+ // Generous bound to accommodate cold-start variance under full
67
+ // workspace test run; warm path is in single-digit ms.
68
+ expect(t1 - t0).toBeLessThan(200);
69
+ });
70
+
71
+ it('opens + closes a dialog in under 30ms', async () => {
72
+ const Harness = () => {
73
+ const confirm = useConfirm();
74
+ return (
75
+ <button
76
+ data-testid="t"
77
+ onClick={async () => {
78
+ await confirm({ title: 'X' });
79
+ }}
80
+ >
81
+ go
82
+ </button>
83
+ );
84
+ };
85
+ render(
86
+ <ConfirmDialogProvider>
87
+ <Harness />
88
+ </ConfirmDialogProvider>
89
+ );
90
+ const t0 = performance.now();
91
+ await act(async () => {
92
+ fireEvent.click(screen.getByTestId('t'));
93
+ });
94
+ await act(async () => {
95
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
96
+ });
97
+ const t1 = performance.now();
98
+ // Generous budget to absorb cold-start variance when run alongside
99
+ // the rest of the workspace test suite.
100
+ expect(t1 - t0).toBeLessThan(200);
101
+ });
102
+ });
103
+
104
+ describe('<ConfirmDialogProvider> re-render budget', () => {
105
+ it('does NOT re-render unrelated children when confirm() opens', async () => {
106
+ const sibCount = { count: 0 };
107
+ const Sibling = () => {
108
+ sibCount.count++;
109
+ return <div data-testid="sib" />;
110
+ };
111
+ const Trigger = () => {
112
+ const confirm = useConfirm();
113
+ return (
114
+ <button data-testid="t" onClick={() => void confirm({ title: 'X' })}>
115
+ go
116
+ </button>
117
+ );
118
+ };
119
+ render(
120
+ <ConfirmDialogProvider>
121
+ <Sibling />
122
+ <Trigger />
123
+ </ConfirmDialogProvider>
124
+ );
125
+ const baseline = sibCount.count;
126
+ await act(async () => {
127
+ fireEvent.click(screen.getByTestId('t'));
128
+ });
129
+ // The provider re-renders to inflate the dialog, which causes one
130
+ // children-pass re-render at the parent level — Sibling re-renders
131
+ // once. Budget: ≤ 1 extra.
132
+ expect(sibCount.count - baseline).toBeLessThanOrEqual(1);
133
+ });
134
+
135
+ it('confirm() reference is stable across provider re-renders', () => {
136
+ const refs: Array<unknown> = [];
137
+ const Capture = () => {
138
+ const confirm = useConfirm();
139
+ refs.push(confirm);
140
+ return null;
141
+ };
142
+ const Outer = ({ x }: { x: number }) => (
143
+ <ConfirmDialogProvider>
144
+ <div data-x={x} />
145
+ <Capture />
146
+ </ConfirmDialogProvider>
147
+ );
148
+ const { rerender } = render(<Outer x={1} />);
149
+ rerender(<Outer x={2} />);
150
+ rerender(<Outer x={3} />);
151
+ // Three renders → confirm reference must be identical each time.
152
+ expect(refs.length).toBeGreaterThanOrEqual(3);
153
+ expect(refs[0]).toBe(refs[1]);
154
+ expect(refs[1]).toBe(refs[2]);
155
+ });
156
+
157
+ it('renders at most 2 times on mount', () => {
158
+ let count = 0;
159
+ const Counted = (
160
+ props: React.ComponentProps<typeof ConfirmDialogProvider>
161
+ ) => {
162
+ count++;
163
+ return <ConfirmDialogProvider {...props} />;
164
+ };
165
+ render(
166
+ <Counted>
167
+ <div />
168
+ </Counted>
169
+ );
170
+ expect(count).toBeLessThanOrEqual(2);
171
+ });
172
+ });
@@ -0,0 +1,311 @@
1
+ // @vitest-environment jsdom
2
+ import * as React from 'react';
3
+ import { describe, it, expect, vi, afterEach, beforeAll } from 'vitest';
4
+ import {
5
+ render,
6
+ screen,
7
+ fireEvent,
8
+ cleanup,
9
+ act,
10
+ waitFor,
11
+ } from '@testing-library/react';
12
+ import {
13
+ ConfirmDialogProvider,
14
+ useConfirm,
15
+ } from './ConfirmDialog.js';
16
+
17
+ void React;
18
+ afterEach(() => cleanup());
19
+
20
+ // jsdom 22 doesn't implement HTMLDialogElement open/close. Shim the bits
21
+ // our component touches BEFORE any test runs — without this the very
22
+ // first `dlg.showModal()` call throws "not implemented".
23
+ beforeAll(() => {
24
+ if (typeof HTMLDialogElement === 'undefined') return;
25
+ const proto = HTMLDialogElement.prototype as unknown as {
26
+ showModal?: () => void;
27
+ close?: (returnValue?: string) => void;
28
+ open?: boolean;
29
+ };
30
+ if (typeof proto.showModal !== 'function') {
31
+ proto.showModal = function (this: HTMLDialogElement) {
32
+ this.setAttribute('open', '');
33
+ // Mirror native dispatch order (open is sync, no 'open' event).
34
+ };
35
+ }
36
+ if (typeof proto.close !== 'function') {
37
+ proto.close = function (this: HTMLDialogElement) {
38
+ this.removeAttribute('open');
39
+ this.dispatchEvent(new Event('close'));
40
+ };
41
+ }
42
+ });
43
+
44
+ /**
45
+ * Test harness — exposes the imperative `confirm()` to the test via a
46
+ * `data-` attribute on a click target. Each click triggers a NEW
47
+ * confirm call, and `last result` is recorded on the harness for
48
+ * assertions.
49
+ */
50
+ function Harness({
51
+ capture,
52
+ options,
53
+ }: {
54
+ capture: { last?: boolean | null };
55
+ options?: Parameters<ReturnType<typeof useConfirm>>[0];
56
+ }) {
57
+ const confirm = useConfirm();
58
+ return (
59
+ <button
60
+ type="button"
61
+ data-testid="trigger"
62
+ onClick={async () => {
63
+ capture.last = null;
64
+ const ok = await confirm(options);
65
+ capture.last = ok;
66
+ }}
67
+ >
68
+ Open
69
+ </button>
70
+ );
71
+ }
72
+
73
+ describe('<ConfirmDialogProvider> + useConfirm()', () => {
74
+ describe('rendering', () => {
75
+ it('renders only its children when no request is active', () => {
76
+ render(
77
+ <ConfirmDialogProvider>
78
+ <div data-testid="child">app</div>
79
+ </ConfirmDialogProvider>
80
+ );
81
+ expect(screen.getByTestId('child')).toBeTruthy();
82
+ // The <dialog> exists (always mounted) but has no `open` attribute.
83
+ const dlg = document.querySelector('dialog');
84
+ expect(dlg).toBeTruthy();
85
+ expect(dlg?.hasAttribute('open')).toBe(false);
86
+ });
87
+
88
+ it('opens the native dialog when confirm() is called', async () => {
89
+ const capture: { last?: boolean | null } = {};
90
+ render(
91
+ <ConfirmDialogProvider>
92
+ <Harness capture={capture} options={{ title: 'T', body: 'B' }} />
93
+ </ConfirmDialogProvider>
94
+ );
95
+ await act(async () => {
96
+ fireEvent.click(screen.getByTestId('trigger'));
97
+ });
98
+ const dlg = document.querySelector('dialog') as HTMLDialogElement;
99
+ expect(dlg.hasAttribute('open')).toBe(true);
100
+ expect(screen.getByText('T')).toBeTruthy();
101
+ expect(screen.getByText('B')).toBeTruthy();
102
+ });
103
+ });
104
+
105
+ describe('event resolution', () => {
106
+ it('resolves true on Confirm click', async () => {
107
+ const capture: { last?: boolean | null } = {};
108
+ render(
109
+ <ConfirmDialogProvider>
110
+ <Harness capture={capture} />
111
+ </ConfirmDialogProvider>
112
+ );
113
+ await act(async () => {
114
+ fireEvent.click(screen.getByTestId('trigger'));
115
+ });
116
+ await act(async () => {
117
+ fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
118
+ });
119
+ await waitFor(() => expect(capture.last).toBe(true));
120
+ });
121
+
122
+ it('resolves false on Cancel click', async () => {
123
+ const capture: { last?: boolean | null } = {};
124
+ render(
125
+ <ConfirmDialogProvider>
126
+ <Harness capture={capture} />
127
+ </ConfirmDialogProvider>
128
+ );
129
+ await act(async () => {
130
+ fireEvent.click(screen.getByTestId('trigger'));
131
+ });
132
+ await act(async () => {
133
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
134
+ });
135
+ await waitFor(() => expect(capture.last).toBe(false));
136
+ });
137
+
138
+ it('resolves false on backdrop click', async () => {
139
+ const capture: { last?: boolean | null } = {};
140
+ render(
141
+ <ConfirmDialogProvider>
142
+ <Harness capture={capture} />
143
+ </ConfirmDialogProvider>
144
+ );
145
+ await act(async () => {
146
+ fireEvent.click(screen.getByTestId('trigger'));
147
+ });
148
+ const dlg = document.querySelector('dialog') as HTMLDialogElement;
149
+ // Backdrop click: event target equals the dialog element itself.
150
+ await act(async () => {
151
+ fireEvent.click(dlg);
152
+ });
153
+ await waitFor(() => expect(capture.last).toBe(false));
154
+ });
155
+
156
+ it('does NOT close on backdrop when disableBackdropClose=true', async () => {
157
+ const capture: { last?: boolean | null } = {};
158
+ render(
159
+ <ConfirmDialogProvider>
160
+ <Harness
161
+ capture={capture}
162
+ options={{ disableBackdropClose: true }}
163
+ />
164
+ </ConfirmDialogProvider>
165
+ );
166
+ await act(async () => {
167
+ fireEvent.click(screen.getByTestId('trigger'));
168
+ });
169
+ const dlg = document.querySelector('dialog') as HTMLDialogElement;
170
+ await act(async () => {
171
+ fireEvent.click(dlg);
172
+ });
173
+ // No resolution yet — dialog stays open.
174
+ expect(capture.last).toBeNull();
175
+ expect(dlg.hasAttribute('open')).toBe(true);
176
+ });
177
+ });
178
+
179
+ describe('useConfirm() outside provider', () => {
180
+ it('throws a helpful error', () => {
181
+ const Lone = () => {
182
+ useConfirm();
183
+ return null;
184
+ };
185
+ // Suppress React's console.error noise for this expected throw.
186
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
187
+ expect(() => render(<Lone />)).toThrow(/useConfirm/);
188
+ spy.mockRestore();
189
+ });
190
+ });
191
+
192
+ describe('severity', () => {
193
+ it('applies the danger color class on the confirm button', async () => {
194
+ const capture: { last?: boolean | null } = {};
195
+ render(
196
+ <ConfirmDialogProvider>
197
+ <Harness capture={capture} options={{ severity: 'danger' }} />
198
+ </ConfirmDialogProvider>
199
+ );
200
+ await act(async () => {
201
+ fireEvent.click(screen.getByTestId('trigger'));
202
+ });
203
+ const btn = screen.getByRole('button', { name: 'Confirm' });
204
+ expect(btn.className).toMatch(/danger/);
205
+ });
206
+ });
207
+
208
+ describe('queueing', () => {
209
+ it('processes concurrent confirm() calls FIFO', async () => {
210
+ const captures: Array<{ last?: boolean | null }> = [{}, {}];
211
+ function Multi() {
212
+ const confirm = useConfirm();
213
+ return (
214
+ <>
215
+ <button
216
+ data-testid="t1"
217
+ onClick={async () => {
218
+ captures[0].last = await confirm({ title: 'First' });
219
+ }}
220
+ >
221
+ t1
222
+ </button>
223
+ <button
224
+ data-testid="t2"
225
+ onClick={async () => {
226
+ captures[1].last = await confirm({ title: 'Second' });
227
+ }}
228
+ >
229
+ t2
230
+ </button>
231
+ </>
232
+ );
233
+ }
234
+ render(
235
+ <ConfirmDialogProvider>
236
+ <Multi />
237
+ </ConfirmDialogProvider>
238
+ );
239
+ await act(async () => {
240
+ fireEvent.click(screen.getByTestId('t1'));
241
+ fireEvent.click(screen.getByTestId('t2'));
242
+ });
243
+ // First request is showing.
244
+ expect(screen.getByText('First')).toBeTruthy();
245
+ // Resolve the first one — second should pop up.
246
+ await act(async () => {
247
+ fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
248
+ });
249
+ await waitFor(() => expect(captures[0].last).toBe(true));
250
+ await waitFor(() => expect(screen.getByText('Second')).toBeTruthy());
251
+ // Resolve the second one.
252
+ await act(async () => {
253
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
254
+ });
255
+ await waitFor(() => expect(captures[1].last).toBe(false));
256
+ });
257
+ });
258
+
259
+ describe('custom labels', () => {
260
+ it('renders confirmLabel + cancelLabel from options', async () => {
261
+ const capture: { last?: boolean | null } = {};
262
+ render(
263
+ <ConfirmDialogProvider>
264
+ <Harness
265
+ capture={capture}
266
+ options={{ confirmLabel: 'Delete', cancelLabel: 'Keep' }}
267
+ />
268
+ </ConfirmDialogProvider>
269
+ );
270
+ await act(async () => {
271
+ fireEvent.click(screen.getByTestId('trigger'));
272
+ });
273
+ expect(screen.getByRole('button', { name: 'Delete' })).toBeTruthy();
274
+ expect(screen.getByRole('button', { name: 'Keep' })).toBeTruthy();
275
+ });
276
+ });
277
+
278
+ describe('a11y wiring', () => {
279
+ it('sets aria-labelledby when a title is provided', async () => {
280
+ const capture: { last?: boolean | null } = {};
281
+ render(
282
+ <ConfirmDialogProvider>
283
+ <Harness capture={capture} options={{ title: 'Heads up' }} />
284
+ </ConfirmDialogProvider>
285
+ );
286
+ await act(async () => {
287
+ fireEvent.click(screen.getByTestId('trigger'));
288
+ });
289
+ const dlg = document.querySelector('dialog') as HTMLDialogElement;
290
+ const labelledBy = dlg.getAttribute('aria-labelledby');
291
+ expect(labelledBy).toBeTruthy();
292
+ expect(
293
+ labelledBy ? document.getElementById(labelledBy)?.textContent : null
294
+ ).toBe('Heads up');
295
+ });
296
+
297
+ it('does NOT set aria-labelledby when no title', async () => {
298
+ const capture: { last?: boolean | null } = {};
299
+ render(
300
+ <ConfirmDialogProvider>
301
+ <Harness capture={capture} options={{ body: 'no title' }} />
302
+ </ConfirmDialogProvider>
303
+ );
304
+ await act(async () => {
305
+ fireEvent.click(screen.getByTestId('trigger'));
306
+ });
307
+ const dlg = document.querySelector('dialog') as HTMLDialogElement;
308
+ expect(dlg.getAttribute('aria-labelledby')).toBeNull();
309
+ });
310
+ });
311
+ });