@hypoth-ui/cli 0.0.1 → 0.1.1

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 (375) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -115
  3. package/dist/{add-PDBC4JTE.js → add-V5PW73GC.js} +29 -17
  4. package/dist/{chunk-5LTQ2XVL.js → chunk-27CLUUVC.js} +0 -2
  5. package/dist/{chunk-YPKFYE45.js → chunk-NWIRSZUQ.js} +6 -13
  6. package/dist/{chunk-GJ6JOQ3Q.js → chunk-PBK72SJJ.js} +1 -1
  7. package/dist/{diff-BQEXG7HU.js → diff-776UATCA.js} +2 -2
  8. package/dist/index.js +5 -5
  9. package/dist/{init-7AZXYAPJ.js → init-GDU2PW7K.js} +10 -13
  10. package/dist/{list-X6ZLM2NQ.js → list-XDP5I537.js} +3 -3
  11. package/package.json +16 -12
  12. package/registry/components.json +1820 -206
  13. package/templates/accordion/index.tsx +266 -0
  14. package/templates/accordion/wc/accordion-content.ts +113 -0
  15. package/templates/accordion/wc/accordion-item.ts +111 -0
  16. package/templates/accordion/wc/accordion-trigger.ts +105 -0
  17. package/templates/accordion/wc/accordion.ts +213 -0
  18. package/templates/accordion/wc/index.ts +12 -0
  19. package/templates/alert/index.tsx +177 -0
  20. package/templates/alert/wc/alert.ts +167 -0
  21. package/templates/alert/wc/index.ts +1 -0
  22. package/templates/alert-dialog/index.tsx +360 -0
  23. package/templates/alert-dialog/wc/alert-dialog-action.ts +43 -0
  24. package/templates/alert-dialog/wc/alert-dialog-cancel.ts +43 -0
  25. package/templates/alert-dialog/wc/alert-dialog-content.ts +42 -0
  26. package/templates/alert-dialog/wc/alert-dialog-description.ts +34 -0
  27. package/templates/alert-dialog/wc/alert-dialog-footer.ts +25 -0
  28. package/templates/alert-dialog/wc/alert-dialog-header.ts +25 -0
  29. package/templates/alert-dialog/wc/alert-dialog-title.ts +34 -0
  30. package/templates/alert-dialog/wc/alert-dialog-trigger.ts +46 -0
  31. package/templates/alert-dialog/wc/alert-dialog.ts +302 -0
  32. package/templates/alert-dialog/wc/index.ts +13 -0
  33. package/templates/aspect-ratio/index.tsx +50 -0
  34. package/templates/aspect-ratio/wc/aspect-ratio.ts +78 -0
  35. package/templates/aspect-ratio/wc/index.ts +5 -0
  36. package/templates/avatar/avatar-group.tsx +88 -0
  37. package/templates/avatar/avatar.tsx +124 -0
  38. package/templates/avatar/index.tsx +33 -0
  39. package/templates/avatar/wc/avatar-group.ts +112 -0
  40. package/templates/avatar/wc/avatar.ts +184 -0
  41. package/templates/avatar/wc/index.ts +5 -0
  42. package/templates/badge/index.tsx +140 -0
  43. package/templates/badge/wc/badge.ts +119 -0
  44. package/templates/badge/wc/index.ts +9 -0
  45. package/templates/breadcrumb/index.tsx +157 -0
  46. package/templates/breadcrumb/wc/breadcrumb-item.ts +30 -0
  47. package/templates/breadcrumb/wc/breadcrumb-link.ts +70 -0
  48. package/templates/breadcrumb/wc/breadcrumb-list.ts +30 -0
  49. package/templates/breadcrumb/wc/breadcrumb-page.ts +32 -0
  50. package/templates/breadcrumb/wc/breadcrumb-separator.ts +31 -0
  51. package/templates/breadcrumb/wc/breadcrumb.ts +55 -0
  52. package/templates/breadcrumb/wc/index.ts +10 -0
  53. package/templates/button/button.tsx +119 -0
  54. package/templates/button/index.ts +1 -0
  55. package/templates/button/wc/button.ts +169 -0
  56. package/templates/calendar/index.tsx +149 -0
  57. package/templates/calendar/wc/calendar.ts +316 -0
  58. package/templates/calendar/wc/index.ts +4 -0
  59. package/templates/card/index.tsx +108 -0
  60. package/templates/card/wc/card-content.ts +25 -0
  61. package/templates/card/wc/card-footer.ts +25 -0
  62. package/templates/card/wc/card-header.ts +25 -0
  63. package/templates/card/wc/card.ts +43 -0
  64. package/templates/card/wc/index.ts +8 -0
  65. package/templates/checkbox/checkbox.tsx +85 -0
  66. package/templates/checkbox/wc/checkbox.ts +247 -0
  67. package/templates/collapsible/index.tsx +172 -0
  68. package/templates/collapsible/wc/collapsible-content.ts +97 -0
  69. package/templates/collapsible/wc/collapsible-trigger.ts +39 -0
  70. package/templates/collapsible/wc/collapsible.ts +143 -0
  71. package/templates/collapsible/wc/index.ts +7 -0
  72. package/templates/combobox/combobox-content.tsx +141 -0
  73. package/templates/combobox/combobox-context.ts +36 -0
  74. package/templates/combobox/combobox-empty.tsx +38 -0
  75. package/templates/combobox/combobox-input.tsx +159 -0
  76. package/templates/combobox/combobox-loading.tsx +38 -0
  77. package/templates/combobox/combobox-option.tsx +99 -0
  78. package/templates/combobox/combobox-root.tsx +207 -0
  79. package/templates/combobox/combobox-tag.tsx +62 -0
  80. package/templates/combobox/index.ts +62 -0
  81. package/templates/combobox/wc/combobox-content.ts +97 -0
  82. package/templates/combobox/wc/combobox-input.ts +134 -0
  83. package/templates/combobox/wc/combobox-option.ts +111 -0
  84. package/templates/combobox/wc/combobox-tag.ts +103 -0
  85. package/templates/combobox/wc/combobox.ts +981 -0
  86. package/templates/combobox/wc/index.ts +5 -0
  87. package/templates/command/index.tsx +279 -0
  88. package/templates/command/wc/command-empty.ts +24 -0
  89. package/templates/command/wc/command-group.ts +60 -0
  90. package/templates/command/wc/command-input.ts +136 -0
  91. package/templates/command/wc/command-item.ts +78 -0
  92. package/templates/command/wc/command-list.ts +103 -0
  93. package/templates/command/wc/command-loading.ts +24 -0
  94. package/templates/command/wc/command-separator.ts +23 -0
  95. package/templates/command/wc/command.ts +176 -0
  96. package/templates/context-menu/index.tsx +262 -0
  97. package/templates/context-menu/wc/context-menu-content.ts +41 -0
  98. package/templates/context-menu/wc/context-menu-item.ts +83 -0
  99. package/templates/context-menu/wc/context-menu-label.ts +30 -0
  100. package/templates/context-menu/wc/context-menu-separator.ts +28 -0
  101. package/templates/context-menu/wc/context-menu.ts +324 -0
  102. package/templates/context-menu/wc/index.ts +9 -0
  103. package/templates/data-table/index.tsx +263 -0
  104. package/templates/data-table/wc/data-table.ts +405 -0
  105. package/templates/data-table/wc/index.ts +10 -0
  106. package/templates/date-picker/date-picker-calendar.tsx +352 -0
  107. package/templates/date-picker/date-picker-content.tsx +121 -0
  108. package/templates/date-picker/date-picker-context.ts +46 -0
  109. package/templates/date-picker/date-picker-root.tsx +201 -0
  110. package/templates/date-picker/date-picker-trigger.tsx +95 -0
  111. package/templates/date-picker/index.ts +44 -0
  112. package/templates/date-picker/wc/date-picker-calendar.ts +457 -0
  113. package/templates/date-picker/wc/date-picker.ts +592 -0
  114. package/templates/date-picker/wc/date-utils.ts +467 -0
  115. package/templates/date-picker/wc/index.ts +3 -0
  116. package/templates/dialog/dialog-close.tsx +57 -0
  117. package/templates/dialog/dialog-content.tsx +106 -0
  118. package/templates/dialog/dialog-context.ts +24 -0
  119. package/templates/dialog/dialog-description.tsx +51 -0
  120. package/templates/dialog/dialog-root.tsx +104 -0
  121. package/templates/dialog/dialog-title.tsx +38 -0
  122. package/templates/dialog/dialog-trigger.tsx +94 -0
  123. package/templates/dialog/index.ts +52 -0
  124. package/templates/dialog/wc/dialog-content.ts +59 -0
  125. package/templates/dialog/wc/dialog-description.ts +58 -0
  126. package/templates/dialog/wc/dialog-title.ts +56 -0
  127. package/templates/dialog/wc/dialog.ts +411 -0
  128. package/templates/drawer/index.tsx +263 -0
  129. package/templates/drawer/wc/drawer-content.ts +150 -0
  130. package/templates/drawer/wc/drawer-description.ts +34 -0
  131. package/templates/drawer/wc/drawer-footer.ts +25 -0
  132. package/templates/drawer/wc/drawer-header.ts +25 -0
  133. package/templates/drawer/wc/drawer-title.ts +34 -0
  134. package/templates/drawer/wc/drawer.ts +348 -0
  135. package/templates/drawer/wc/index.ts +10 -0
  136. package/templates/dropdown-menu/index.tsx +454 -0
  137. package/templates/dropdown-menu/wc/dropdown-menu-checkbox-item.ts +93 -0
  138. package/templates/dropdown-menu/wc/dropdown-menu-content.ts +43 -0
  139. package/templates/dropdown-menu/wc/dropdown-menu-item.ts +85 -0
  140. package/templates/dropdown-menu/wc/dropdown-menu-label.ts +31 -0
  141. package/templates/dropdown-menu/wc/dropdown-menu-radio-group.ts +80 -0
  142. package/templates/dropdown-menu/wc/dropdown-menu-radio-item.ts +101 -0
  143. package/templates/dropdown-menu/wc/dropdown-menu-separator.ts +28 -0
  144. package/templates/dropdown-menu/wc/dropdown-menu.ts +358 -0
  145. package/templates/dropdown-menu/wc/index.ts +12 -0
  146. package/templates/field/field-description.tsx +39 -0
  147. package/templates/field/field-error.tsx +37 -0
  148. package/templates/field/field.tsx +46 -0
  149. package/templates/field/index.ts +4 -0
  150. package/templates/field/label.tsx +40 -0
  151. package/templates/field/wc/field-description.ts +42 -0
  152. package/templates/field/wc/field-error.ts +46 -0
  153. package/templates/field/wc/field.ts +210 -0
  154. package/templates/field/wc/label.ts +54 -0
  155. package/templates/file-upload/file-upload-context.ts +26 -0
  156. package/templates/file-upload/file-upload-dropzone.tsx +111 -0
  157. package/templates/file-upload/file-upload-input.tsx +86 -0
  158. package/templates/file-upload/file-upload-item.tsx +105 -0
  159. package/templates/file-upload/file-upload-root.tsx +115 -0
  160. package/templates/file-upload/index.ts +50 -0
  161. package/templates/file-upload/wc/file-upload.ts +380 -0
  162. package/templates/file-upload/wc/index.ts +1 -0
  163. package/templates/hover-card/index.tsx +203 -0
  164. package/templates/hover-card/wc/hover-card-content.ts +50 -0
  165. package/templates/hover-card/wc/hover-card.ts +382 -0
  166. package/templates/hover-card/wc/index.ts +6 -0
  167. package/templates/icon/icon.tsx +76 -0
  168. package/templates/icon/wc/icon-adapter.ts +108 -0
  169. package/templates/icon/wc/icon.ts +161 -0
  170. package/templates/input/input.tsx +130 -0
  171. package/templates/input/wc/input.ts +216 -0
  172. package/templates/layout/app-shell.tsx +177 -0
  173. package/templates/layout/box.tsx +53 -0
  174. package/templates/layout/center.tsx +42 -0
  175. package/templates/layout/container.tsx +43 -0
  176. package/templates/layout/flow.tsx +83 -0
  177. package/templates/layout/grid.tsx +79 -0
  178. package/templates/layout/index.ts +33 -0
  179. package/templates/layout/inline.tsx +16 -0
  180. package/templates/layout/page.tsx +43 -0
  181. package/templates/layout/section.tsx +39 -0
  182. package/templates/layout/spacer.tsx +30 -0
  183. package/templates/layout/split.tsx +47 -0
  184. package/templates/layout/stack.tsx +16 -0
  185. package/templates/layout/wc/app-shell.ts +58 -0
  186. package/templates/layout/wc/box.ts +117 -0
  187. package/templates/layout/wc/center.ts +78 -0
  188. package/templates/layout/wc/container.ts +77 -0
  189. package/templates/layout/wc/flow.ts +149 -0
  190. package/templates/layout/wc/footer.ts +57 -0
  191. package/templates/layout/wc/grid.ts +142 -0
  192. package/templates/layout/wc/header.ts +57 -0
  193. package/templates/layout/wc/index.ts +41 -0
  194. package/templates/layout/wc/main.ts +46 -0
  195. package/templates/layout/wc/page.ts +81 -0
  196. package/templates/layout/wc/section.ts +65 -0
  197. package/templates/layout/wc/spacer.ts +77 -0
  198. package/templates/layout/wc/split.ts +94 -0
  199. package/templates/layout/wc/wrap.ts +93 -0
  200. package/templates/layout/wrap.tsx +46 -0
  201. package/templates/link/link.tsx +109 -0
  202. package/templates/link/wc/link.ts +124 -0
  203. package/templates/list/index.tsx +55 -0
  204. package/templates/list/list-item.tsx +117 -0
  205. package/templates/list/list.tsx +115 -0
  206. package/templates/list/wc/index.ts +5 -0
  207. package/templates/list/wc/list-item.ts +127 -0
  208. package/templates/list/wc/list.ts +114 -0
  209. package/templates/menu/index.ts +49 -0
  210. package/templates/menu/menu-content.tsx +109 -0
  211. package/templates/menu/menu-context.ts +17 -0
  212. package/templates/menu/menu-item.tsx +108 -0
  213. package/templates/menu/menu-label.tsx +32 -0
  214. package/templates/menu/menu-root.tsx +108 -0
  215. package/templates/menu/menu-separator.tsx +24 -0
  216. package/templates/menu/menu-trigger.tsx +104 -0
  217. package/templates/menu/wc/menu-content.ts +67 -0
  218. package/templates/menu/wc/menu-item.ts +109 -0
  219. package/templates/menu/wc/menu.ts +449 -0
  220. package/templates/navigation-menu/index.tsx +328 -0
  221. package/templates/navigation-menu/wc/index.ts +12 -0
  222. package/templates/navigation-menu/wc/navigation-menu-content.ts +30 -0
  223. package/templates/navigation-menu/wc/navigation-menu-indicator.ts +30 -0
  224. package/templates/navigation-menu/wc/navigation-menu-item.ts +60 -0
  225. package/templates/navigation-menu/wc/navigation-menu-link.ts +97 -0
  226. package/templates/navigation-menu/wc/navigation-menu-list.ts +30 -0
  227. package/templates/navigation-menu/wc/navigation-menu-trigger.ts +110 -0
  228. package/templates/navigation-menu/wc/navigation-menu-viewport.ts +85 -0
  229. package/templates/navigation-menu/wc/navigation-menu.ts +272 -0
  230. package/templates/number-input/index.ts +46 -0
  231. package/templates/number-input/number-input-context.ts +38 -0
  232. package/templates/number-input/number-input-decrement.tsx +53 -0
  233. package/templates/number-input/number-input-field.tsx +93 -0
  234. package/templates/number-input/number-input-increment.tsx +53 -0
  235. package/templates/number-input/number-input-root.tsx +137 -0
  236. package/templates/number-input/wc/index.ts +1 -0
  237. package/templates/number-input/wc/number-input.ts +283 -0
  238. package/templates/pagination/index.tsx +198 -0
  239. package/templates/pagination/wc/index.ts +11 -0
  240. package/templates/pagination/wc/pagination-content.ts +30 -0
  241. package/templates/pagination/wc/pagination-ellipsis.ts +28 -0
  242. package/templates/pagination/wc/pagination-item.ts +30 -0
  243. package/templates/pagination/wc/pagination-link.ts +76 -0
  244. package/templates/pagination/wc/pagination-next.ts +69 -0
  245. package/templates/pagination/wc/pagination-previous.ts +69 -0
  246. package/templates/pagination/wc/pagination.ts +156 -0
  247. package/templates/pin-input/index.ts +39 -0
  248. package/templates/pin-input/pin-input-context.ts +30 -0
  249. package/templates/pin-input/pin-input-field.tsx +186 -0
  250. package/templates/pin-input/pin-input-root.tsx +120 -0
  251. package/templates/pin-input/wc/index.ts +1 -0
  252. package/templates/pin-input/wc/pin-input.ts +259 -0
  253. package/templates/popover/popover.tsx +121 -0
  254. package/templates/popover/wc/popover-content.ts +66 -0
  255. package/templates/popover/wc/popover.ts +343 -0
  256. package/templates/progress/index.tsx +117 -0
  257. package/templates/progress/wc/index.ts +4 -0
  258. package/templates/progress/wc/progress.ts +174 -0
  259. package/templates/radio/radio.tsx +43 -0
  260. package/templates/radio/wc/radio-group.ts +261 -0
  261. package/templates/radio/wc/radio.ts +145 -0
  262. package/templates/scroll-area/index.tsx +144 -0
  263. package/templates/scroll-area/wc/index.ts +8 -0
  264. package/templates/scroll-area/wc/scroll-area-scrollbar.ts +143 -0
  265. package/templates/scroll-area/wc/scroll-area-thumb.ts +225 -0
  266. package/templates/scroll-area/wc/scroll-area-viewport.ts +120 -0
  267. package/templates/scroll-area/wc/scroll-area.ts +63 -0
  268. package/templates/select/index.ts +57 -0
  269. package/templates/select/select-content.tsx +243 -0
  270. package/templates/select/select-context.ts +30 -0
  271. package/templates/select/select-group.tsx +53 -0
  272. package/templates/select/select-label.tsx +34 -0
  273. package/templates/select/select-option.tsx +97 -0
  274. package/templates/select/select-root.tsx +153 -0
  275. package/templates/select/select-separator.tsx +27 -0
  276. package/templates/select/select-trigger.tsx +112 -0
  277. package/templates/select/select-value.tsx +48 -0
  278. package/templates/select/wc/index.ts +6 -0
  279. package/templates/select/wc/select-content.ts +89 -0
  280. package/templates/select/wc/select-group.ts +82 -0
  281. package/templates/select/wc/select-label.ts +49 -0
  282. package/templates/select/wc/select-option.ts +111 -0
  283. package/templates/select/wc/select-trigger.ts +101 -0
  284. package/templates/select/wc/select.ts +840 -0
  285. package/templates/separator/index.tsx +49 -0
  286. package/templates/separator/wc/index.ts +5 -0
  287. package/templates/separator/wc/separator.ts +60 -0
  288. package/templates/sheet/index.tsx +291 -0
  289. package/templates/sheet/wc/index.ts +12 -0
  290. package/templates/sheet/wc/sheet-close.ts +43 -0
  291. package/templates/sheet/wc/sheet-content.ts +47 -0
  292. package/templates/sheet/wc/sheet-description.ts +34 -0
  293. package/templates/sheet/wc/sheet-footer.ts +25 -0
  294. package/templates/sheet/wc/sheet-header.ts +25 -0
  295. package/templates/sheet/wc/sheet-overlay.ts +23 -0
  296. package/templates/sheet/wc/sheet-title.ts +34 -0
  297. package/templates/sheet/wc/sheet.ts +336 -0
  298. package/templates/skeleton/index.tsx +131 -0
  299. package/templates/skeleton/wc/index.ts +10 -0
  300. package/templates/skeleton/wc/skeleton.ts +107 -0
  301. package/templates/slider/index.ts +41 -0
  302. package/templates/slider/slider-context.ts +36 -0
  303. package/templates/slider/slider-range.tsx +59 -0
  304. package/templates/slider/slider-root.tsx +166 -0
  305. package/templates/slider/slider-thumb.tsx +213 -0
  306. package/templates/slider/slider-track.tsx +113 -0
  307. package/templates/slider/wc/index.ts +1 -0
  308. package/templates/slider/wc/slider.ts +465 -0
  309. package/templates/spinner/spinner.tsx +64 -0
  310. package/templates/spinner/wc/spinner.ts +70 -0
  311. package/templates/stepper/index.tsx +230 -0
  312. package/templates/stepper/wc/index.ts +12 -0
  313. package/templates/stepper/wc/stepper-content.ts +30 -0
  314. package/templates/stepper/wc/stepper-description.ts +25 -0
  315. package/templates/stepper/wc/stepper-indicator.ts +30 -0
  316. package/templates/stepper/wc/stepper-item.ts +55 -0
  317. package/templates/stepper/wc/stepper-separator.ts +29 -0
  318. package/templates/stepper/wc/stepper-title.ts +25 -0
  319. package/templates/stepper/wc/stepper-trigger.ts +67 -0
  320. package/templates/stepper/wc/stepper.ts +164 -0
  321. package/templates/switch/switch.tsx +90 -0
  322. package/templates/switch/wc/switch.ts +228 -0
  323. package/templates/table/body.tsx +21 -0
  324. package/templates/table/cell.tsx +44 -0
  325. package/templates/table/head.tsx +112 -0
  326. package/templates/table/header.tsx +21 -0
  327. package/templates/table/index.tsx +93 -0
  328. package/templates/table/root.tsx +82 -0
  329. package/templates/table/row.tsx +36 -0
  330. package/templates/table/wc/index.ts +9 -0
  331. package/templates/table/wc/table-body.ts +32 -0
  332. package/templates/table/wc/table-cell.ts +58 -0
  333. package/templates/table/wc/table-head.ts +129 -0
  334. package/templates/table/wc/table-header.ts +32 -0
  335. package/templates/table/wc/table-row.ts +50 -0
  336. package/templates/table/wc/table.ts +93 -0
  337. package/templates/tabs/index.tsx +222 -0
  338. package/templates/tabs/wc/index.ts +8 -0
  339. package/templates/tabs/wc/tabs-content.ts +82 -0
  340. package/templates/tabs/wc/tabs-list.ts +56 -0
  341. package/templates/tabs/wc/tabs-trigger.ts +136 -0
  342. package/templates/tabs/wc/tabs.ts +202 -0
  343. package/templates/tag/index.tsx +186 -0
  344. package/templates/tag/wc/index.ts +4 -0
  345. package/templates/tag/wc/tag.ts +166 -0
  346. package/templates/text/text.tsx +100 -0
  347. package/templates/text/wc/text.ts +94 -0
  348. package/templates/textarea/textarea.tsx +134 -0
  349. package/templates/textarea/wc/textarea.ts +280 -0
  350. package/templates/time-picker/index.ts +42 -0
  351. package/templates/time-picker/time-picker-context.ts +28 -0
  352. package/templates/time-picker/time-picker-root.tsx +113 -0
  353. package/templates/time-picker/time-picker-segment.tsx +91 -0
  354. package/templates/time-picker/wc/index.ts +1 -0
  355. package/templates/time-picker/wc/time-picker.ts +221 -0
  356. package/templates/toast/index.tsx +71 -0
  357. package/templates/toast/provider.tsx +228 -0
  358. package/templates/toast/toast.tsx +142 -0
  359. package/templates/toast/use-toast.ts +89 -0
  360. package/templates/toast/wc/index.ts +15 -0
  361. package/templates/toast/wc/toast-controller.ts +282 -0
  362. package/templates/toast/wc/toast-provider.ts +161 -0
  363. package/templates/toast/wc/toast.ts +165 -0
  364. package/templates/tooltip/tooltip.tsx +62 -0
  365. package/templates/tooltip/wc/tooltip-content.ts +64 -0
  366. package/templates/tooltip/wc/tooltip.ts +289 -0
  367. package/templates/tree/index.tsx +60 -0
  368. package/templates/tree/tree-item.tsx +131 -0
  369. package/templates/tree/tree.tsx +138 -0
  370. package/templates/tree/wc/index.ts +11 -0
  371. package/templates/tree/wc/tree-item.ts +273 -0
  372. package/templates/tree/wc/tree-utils.ts +143 -0
  373. package/templates/tree/wc/tree.ts +139 -0
  374. package/templates/visually-hidden/visually-hidden.tsx +45 -0
  375. package/templates/visually-hidden/wc/visually-hidden.ts +64 -0
@@ -0,0 +1,467 @@
1
+ /**
2
+ * Date utility functions for DatePicker component.
3
+ * Uses date-fns for reliable date manipulation across timezones.
4
+ */
5
+
6
+ import {
7
+ addMonths,
8
+ addYears,
9
+ isToday as dateFnsIsToday,
10
+ eachDayOfInterval,
11
+ endOfMonth,
12
+ endOfWeek,
13
+ format,
14
+ isAfter,
15
+ isBefore,
16
+ isSameDay,
17
+ isSameMonth,
18
+ isValid,
19
+ parse,
20
+ parseISO,
21
+ startOfMonth,
22
+ startOfWeek,
23
+ } from "date-fns";
24
+
25
+ export type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6;
26
+
27
+ /**
28
+ * Returns today's date in ISO format (YYYY-MM-DD).
29
+ */
30
+ export function getTodayIso(): string {
31
+ return format(new Date(), "yyyy-MM-dd");
32
+ }
33
+
34
+ /**
35
+ * Parses a date string to a Date object.
36
+ * Returns null if the string is empty or invalid.
37
+ */
38
+ export function parseDate(dateStr: string): Date | null {
39
+ if (!dateStr) return null;
40
+ const parsed = parseISO(dateStr);
41
+ return isValid(parsed) ? parsed : null;
42
+ }
43
+
44
+ /**
45
+ * Formats a Date object to ISO format (YYYY-MM-DD).
46
+ */
47
+ export function formatIso(date: Date): string {
48
+ return format(date, "yyyy-MM-dd");
49
+ }
50
+
51
+ /**
52
+ * Formats a Date object to a viewing month string (YYYY-MM).
53
+ */
54
+ export function formatViewingMonth(date: Date): string {
55
+ return format(date, "yyyy-MM");
56
+ }
57
+
58
+ /**
59
+ * Parses a viewing month string (YYYY-MM) to a Date object (first day of month).
60
+ */
61
+ export function parseViewingMonth(viewingMonth: string): Date {
62
+ if (!viewingMonth) {
63
+ return startOfMonth(new Date());
64
+ }
65
+ const parsed = parse(viewingMonth, "yyyy-MM", new Date());
66
+ return isValid(parsed) ? parsed : startOfMonth(new Date());
67
+ }
68
+
69
+ /**
70
+ * Gets the weekday names for a locale, starting from the specified day.
71
+ */
72
+ export function getWeekdayNames(
73
+ locale: string,
74
+ weekStartsOn: WeekStartsOn = 0,
75
+ formatStyle: "short" | "narrow" | "long" = "short"
76
+ ): string[] {
77
+ const formatter = new Intl.DateTimeFormat(locale, { weekday: formatStyle });
78
+
79
+ // Start from January 2024, which starts on Monday (day 1)
80
+ // We adjust based on weekStartsOn
81
+ const baseDate = new Date(2024, 0, weekStartsOn);
82
+ const names: string[] = [];
83
+
84
+ for (let i = 0; i < 7; i++) {
85
+ const day = new Date(baseDate);
86
+ day.setDate(day.getDate() + i);
87
+ names.push(formatter.format(day));
88
+ }
89
+
90
+ return names;
91
+ }
92
+
93
+ /**
94
+ * Gets all days to display in a calendar month grid.
95
+ * Includes padding days from previous and next months.
96
+ */
97
+ export function getMonthDays(viewingMonth: string, weekStartsOn: WeekStartsOn = 0): Date[] {
98
+ const monthDate = parseViewingMonth(viewingMonth);
99
+ const monthStart = startOfMonth(monthDate);
100
+ const monthEnd = endOfMonth(monthDate);
101
+
102
+ // Get calendar bounds (including days from adjacent months)
103
+ const calendarStart = startOfWeek(monthStart, { weekStartsOn });
104
+ const calendarEnd = endOfWeek(monthEnd, { weekStartsOn });
105
+
106
+ return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
107
+ }
108
+
109
+ /**
110
+ * Gets the formatted month label (e.g., "January 2026").
111
+ */
112
+ export function getMonthLabel(
113
+ viewingMonth: string,
114
+ locale: string,
115
+ options: Intl.DateTimeFormatOptions = { month: "long", year: "numeric" }
116
+ ): string {
117
+ const date = parseViewingMonth(viewingMonth);
118
+ return new Intl.DateTimeFormat(locale, options).format(date);
119
+ }
120
+
121
+ /**
122
+ * Navigates to the previous or next month.
123
+ */
124
+ export function navigateMonth(viewingMonth: string, delta: number): string {
125
+ const date = parseViewingMonth(viewingMonth);
126
+ return formatViewingMonth(addMonths(date, delta));
127
+ }
128
+
129
+ /**
130
+ * Navigates to the previous or next year.
131
+ */
132
+ export function navigateYear(viewingMonth: string, delta: number): string {
133
+ const date = parseViewingMonth(viewingMonth);
134
+ return formatViewingMonth(addYears(date, delta));
135
+ }
136
+
137
+ /**
138
+ * Checks if a date is disabled (outside min/max range).
139
+ */
140
+ export function isDateDisabled(
141
+ date: Date,
142
+ minDate: string | undefined,
143
+ maxDate: string | undefined
144
+ ): boolean {
145
+ if (minDate) {
146
+ const min = parseDate(minDate);
147
+ if (min && isBefore(date, min) && !isSameDay(date, min)) {
148
+ return true;
149
+ }
150
+ }
151
+ if (maxDate) {
152
+ const max = parseDate(maxDate);
153
+ if (max && isAfter(date, max) && !isSameDay(date, max)) {
154
+ return true;
155
+ }
156
+ }
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Checks if a date is selected (single mode or range endpoints).
162
+ */
163
+ export function isDateSelected(
164
+ date: Date,
165
+ selectedDate: string | undefined,
166
+ rangeStart: string | undefined,
167
+ rangeEnd: string | undefined,
168
+ isRangeMode: boolean
169
+ ): boolean {
170
+ if (isRangeMode) {
171
+ if (rangeStart) {
172
+ const start = parseDate(rangeStart);
173
+ if (start && isSameDay(date, start)) return true;
174
+ }
175
+ if (rangeEnd) {
176
+ const end = parseDate(rangeEnd);
177
+ if (end && isSameDay(date, end)) return true;
178
+ }
179
+ return false;
180
+ }
181
+
182
+ if (selectedDate) {
183
+ const selected = parseDate(selectedDate);
184
+ return selected ? isSameDay(date, selected) : false;
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Checks if a date is within a selected range (exclusive of endpoints).
192
+ */
193
+ export function isDateInRange(
194
+ date: Date,
195
+ rangeStart: string | undefined,
196
+ rangeEnd: string | undefined
197
+ ): boolean {
198
+ if (!rangeStart || !rangeEnd) return false;
199
+
200
+ const start = parseDate(rangeStart);
201
+ const end = parseDate(rangeEnd);
202
+
203
+ if (!start || !end) return false;
204
+
205
+ return isAfter(date, start) && isBefore(date, end);
206
+ }
207
+
208
+ /**
209
+ * Checks if a date is today.
210
+ */
211
+ export function isToday(date: Date): boolean {
212
+ return dateFnsIsToday(date);
213
+ }
214
+
215
+ /**
216
+ * Checks if a date is in the currently viewed month.
217
+ */
218
+ export function isDateInCurrentMonth(date: Date, viewingMonth: string): boolean {
219
+ const monthDate = parseViewingMonth(viewingMonth);
220
+ return isSameMonth(date, monthDate);
221
+ }
222
+
223
+ /**
224
+ * Formats a date for display in the input field.
225
+ */
226
+ export function formatDisplayDate(
227
+ date: Date | null,
228
+ locale: string,
229
+ options: Intl.DateTimeFormatOptions = { dateStyle: "medium" }
230
+ ): string {
231
+ if (!date) return "";
232
+ return new Intl.DateTimeFormat(locale, options).format(date);
233
+ }
234
+
235
+ /**
236
+ * Formats a date range for display.
237
+ */
238
+ export function formatDisplayRange(
239
+ start: string | undefined,
240
+ end: string | undefined,
241
+ locale: string,
242
+ options: Intl.DateTimeFormatOptions = { dateStyle: "medium" }
243
+ ): string {
244
+ const startDate = start ? parseDate(start) : null;
245
+ const endDate = end ? parseDate(end) : null;
246
+ const formatter = new Intl.DateTimeFormat(locale, options);
247
+
248
+ if (startDate && endDate) {
249
+ return `${formatter.format(startDate)} – ${formatter.format(endDate)}`;
250
+ }
251
+ if (startDate) {
252
+ return formatter.format(startDate);
253
+ }
254
+ return "";
255
+ }
256
+
257
+ /**
258
+ * Validates a date string format (YYYY-MM-DD).
259
+ */
260
+ export function isValidDateString(dateStr: string): boolean {
261
+ if (!dateStr) return false;
262
+ const regex = /^\d{4}-\d{2}-\d{2}$/;
263
+ if (!regex.test(dateStr)) return false;
264
+ const parsed = parseISO(dateStr);
265
+ return isValid(parsed);
266
+ }
267
+
268
+ /**
269
+ * Clamps a date to within min/max bounds.
270
+ */
271
+ export function clampDate(
272
+ date: Date,
273
+ minDate: string | undefined,
274
+ maxDate: string | undefined
275
+ ): Date {
276
+ let result = date;
277
+
278
+ if (minDate) {
279
+ const min = parseDate(minDate);
280
+ if (min && isBefore(result, min)) {
281
+ result = min;
282
+ }
283
+ }
284
+
285
+ if (maxDate) {
286
+ const max = parseDate(maxDate);
287
+ if (max && isAfter(result, max)) {
288
+ result = max;
289
+ }
290
+ }
291
+
292
+ return result;
293
+ }
294
+
295
+ /**
296
+ * Date format patterns for common locales.
297
+ * Maps locale codes to their date-fns format patterns.
298
+ */
299
+ const LOCALE_DATE_FORMATS: Record<string, string> = {
300
+ "en-US": "MM/dd/yyyy",
301
+ "en-GB": "dd/MM/yyyy",
302
+ "en-CA": "yyyy-MM-dd",
303
+ "en-AU": "dd/MM/yyyy",
304
+ "de-DE": "dd.MM.yyyy",
305
+ "fr-FR": "dd/MM/yyyy",
306
+ "es-ES": "dd/MM/yyyy",
307
+ "it-IT": "dd/MM/yyyy",
308
+ "pt-BR": "dd/MM/yyyy",
309
+ "ja-JP": "yyyy/MM/dd",
310
+ "ko-KR": "yyyy.MM.dd",
311
+ "zh-CN": "yyyy/MM/dd",
312
+ "zh-TW": "yyyy/MM/dd",
313
+ "ru-RU": "dd.MM.yyyy",
314
+ "pl-PL": "dd.MM.yyyy",
315
+ "nl-NL": "dd-MM-yyyy",
316
+ "sv-SE": "yyyy-MM-dd",
317
+ "da-DK": "dd-MM-yyyy",
318
+ "fi-FI": "dd.MM.yyyy",
319
+ "nb-NO": "dd.MM.yyyy",
320
+ "cs-CZ": "dd.MM.yyyy",
321
+ "hu-HU": "yyyy.MM.dd",
322
+ "tr-TR": "dd.MM.yyyy",
323
+ "ar-SA": "dd/MM/yyyy",
324
+ "he-IL": "dd/MM/yyyy",
325
+ "th-TH": "dd/MM/yyyy",
326
+ };
327
+
328
+ /**
329
+ * Gets the date format pattern for a locale.
330
+ * Returns MM/dd/yyyy as default for unknown locales.
331
+ */
332
+ export function getDateFormat(locale: string): string {
333
+ // Try exact match
334
+ if (LOCALE_DATE_FORMATS[locale]) {
335
+ return LOCALE_DATE_FORMATS[locale];
336
+ }
337
+
338
+ // Try language only (e.g., "en" from "en-XX")
339
+ const lang = locale.split("-")[0];
340
+ for (const [key, fmt] of Object.entries(LOCALE_DATE_FORMATS)) {
341
+ if (key.startsWith(`${lang}-`)) {
342
+ return fmt;
343
+ }
344
+ }
345
+
346
+ // Default to MM/dd/yyyy
347
+ return "MM/dd/yyyy";
348
+ }
349
+
350
+ /**
351
+ * Gets a human-readable date format placeholder for a locale.
352
+ * E.g., "MM/DD/YYYY" for en-US, "DD/MM/YYYY" for en-GB
353
+ */
354
+ export function getDateFormatPlaceholder(locale: string): string {
355
+ const fmt = getDateFormat(locale);
356
+ return fmt.replace(/yyyy/g, "YYYY").replace(/MM/g, "MM").replace(/dd/g, "DD");
357
+ }
358
+
359
+ /**
360
+ * Result of parsing a typed date input.
361
+ */
362
+ export interface DateParseResult {
363
+ /** Whether the input was successfully parsed */
364
+ valid: boolean;
365
+ /** The parsed date in ISO format (YYYY-MM-DD), if valid */
366
+ date: string | null;
367
+ /** Error message if invalid */
368
+ error: string | null;
369
+ }
370
+
371
+ /**
372
+ * Parses a user-typed date string based on locale format.
373
+ * Supports flexible separator matching (/, -, .).
374
+ */
375
+ export function parseTypedDate(input: string, locale: string): DateParseResult {
376
+ if (!input || !input.trim()) {
377
+ return { valid: false, date: null, error: null };
378
+ }
379
+
380
+ const trimmed = input.trim();
381
+ const fmt = getDateFormat(locale);
382
+
383
+ // Try parsing with the locale format
384
+ const parsed = parse(trimmed, fmt, new Date());
385
+ if (isValid(parsed)) {
386
+ return { valid: true, date: formatIso(parsed), error: null };
387
+ }
388
+
389
+ // Try common separator variations
390
+ const separators = ["/", "-", "."];
391
+ for (const sep of separators) {
392
+ const normalizedInput = trimmed.replace(/[\/\-\.]/g, sep);
393
+ const normalizedFormat = fmt.replace(/[\/\-\.]/g, sep);
394
+ const attempt = parse(normalizedInput, normalizedFormat, new Date());
395
+ if (isValid(attempt)) {
396
+ return { valid: true, date: formatIso(attempt), error: null };
397
+ }
398
+ }
399
+
400
+ // Try ISO format as fallback
401
+ const isoAttempt = parseISO(trimmed);
402
+ if (isValid(isoAttempt)) {
403
+ return { valid: true, date: formatIso(isoAttempt), error: null };
404
+ }
405
+
406
+ return {
407
+ valid: false,
408
+ date: null,
409
+ error: `Invalid date format. Expected: ${getDateFormatPlaceholder(locale)}`,
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Formats a date for display in a typed input field.
415
+ * Uses the locale-specific format pattern.
416
+ */
417
+ export function formatTypedDate(dateIso: string, locale: string): string {
418
+ if (!dateIso) return "";
419
+ const date = parseDate(dateIso);
420
+ if (!date) return "";
421
+ return format(date, getDateFormat(locale));
422
+ }
423
+
424
+ /**
425
+ * Validates a typed date against min/max constraints.
426
+ */
427
+ export function validateTypedDate(
428
+ dateIso: string,
429
+ minDate: string | undefined,
430
+ maxDate: string | undefined,
431
+ locale: string
432
+ ): DateParseResult {
433
+ if (!dateIso) {
434
+ return { valid: false, date: null, error: null };
435
+ }
436
+
437
+ const date = parseDate(dateIso);
438
+ if (!date) {
439
+ return { valid: false, date: null, error: "Invalid date" };
440
+ }
441
+
442
+ if (minDate) {
443
+ const min = parseDate(minDate);
444
+ if (min && isBefore(date, min) && !isSameDay(date, min)) {
445
+ const minFormatted = formatTypedDate(minDate, locale);
446
+ return {
447
+ valid: false,
448
+ date: dateIso,
449
+ error: `Date must be on or after ${minFormatted}`,
450
+ };
451
+ }
452
+ }
453
+
454
+ if (maxDate) {
455
+ const max = parseDate(maxDate);
456
+ if (max && isAfter(date, max) && !isSameDay(date, max)) {
457
+ const maxFormatted = formatTypedDate(maxDate, locale);
458
+ return {
459
+ valid: false,
460
+ date: dateIso,
461
+ error: `Date must be on or before ${maxFormatted}`,
462
+ };
463
+ }
464
+ }
465
+
466
+ return { valid: true, date: dateIso, error: null };
467
+ }
@@ -0,0 +1,3 @@
1
+ export { DsDatePicker, type DatePickerMode } from "./date-picker.js";
2
+ export { DsDatePickerCalendar, type CalendarState } from "./date-picker-calendar.js";
3
+ export * from "./date-utils.js";
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Dialog Close component - button that closes the dialog.
3
+ */
4
+
5
+ import { type ButtonHTMLAttributes, type ReactNode, forwardRef, useCallback } from "react";
6
+ import { Slot } from "../../primitives/slot.js";
7
+ import { useDialogContext } from "./dialog-context.js";
8
+
9
+ export interface DialogCloseProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
+ /** Button content */
11
+ children?: ReactNode;
12
+ /** Render as child element (polymorphic) */
13
+ asChild?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Button that closes the dialog when activated.
18
+ * Supports asChild for custom close elements.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <Dialog.Close>Close</Dialog.Close>
23
+ *
24
+ * <Dialog.Close asChild>
25
+ * <button className="custom-close">X</button>
26
+ * </Dialog.Close>
27
+ * ```
28
+ */
29
+ export const DialogClose = forwardRef<HTMLButtonElement, DialogCloseProps>(
30
+ ({ children, asChild = false, onClick, ...restProps }, ref) => {
31
+ const { setOpen } = useDialogContext("Dialog.Close");
32
+
33
+ // Handle click to close dialog
34
+ const handleClick = useCallback(
35
+ (event: React.MouseEvent<HTMLButtonElement>) => {
36
+ setOpen(false);
37
+ onClick?.(event);
38
+ },
39
+ [setOpen, onClick]
40
+ );
41
+
42
+ const Component = asChild ? Slot : "button";
43
+
44
+ return (
45
+ <Component
46
+ ref={ref}
47
+ type={asChild ? undefined : "button"}
48
+ onClick={handleClick}
49
+ {...restProps}
50
+ >
51
+ {children}
52
+ </Component>
53
+ );
54
+ }
55
+ );
56
+
57
+ DialogClose.displayName = "Dialog.Close";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Dialog Content component - the dialog panel with focus trap and portal.
3
+ */
4
+
5
+ import {
6
+ type HTMLAttributes,
7
+ type ReactNode,
8
+ forwardRef,
9
+ useCallback,
10
+ useEffect,
11
+ useRef,
12
+ } from "react";
13
+ import { createPortal } from "react-dom";
14
+ import { useDialogContext } from "./dialog-context.js";
15
+
16
+ export type DialogContentSize = "sm" | "md" | "lg" | "xl" | "full";
17
+
18
+ export interface DialogContentProps extends HTMLAttributes<HTMLDivElement> {
19
+ /** Dialog content */
20
+ children?: ReactNode;
21
+ /** Content size */
22
+ size?: DialogContentSize;
23
+ /** Container element for portal (defaults to document.body) */
24
+ container?: HTMLElement | null;
25
+ /** Force mount even when closed (for animations) */
26
+ forceMount?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Dialog content panel with focus trap and ARIA attributes.
31
+ * Renders in a portal by default.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <Dialog.Content>
36
+ * <Dialog.Title>Dialog Title</Dialog.Title>
37
+ * <p>Dialog body content</p>
38
+ * <Dialog.Close>Close</Dialog.Close>
39
+ * </Dialog.Content>
40
+ * ```
41
+ */
42
+ export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
43
+ ({ children, size = "md", container, forceMount = false, ...restProps }, ref) => {
44
+ const { behavior, open, modal, descriptionId } = useDialogContext("Dialog.Content");
45
+ const internalRef = useRef<HTMLDivElement>(null);
46
+
47
+ // Register content element with behavior for focus trap and dismissable layer
48
+ useEffect(() => {
49
+ if (open) {
50
+ const element = internalRef.current;
51
+ behavior.setContentElement(element);
52
+ return () => {
53
+ behavior.setContentElement(null);
54
+ };
55
+ }
56
+ }, [behavior, open]);
57
+
58
+ // Get content props from behavior
59
+ const contentProps = behavior.getContentProps();
60
+
61
+ // Merge refs
62
+ const mergedRef = useCallback(
63
+ (element: HTMLDivElement | null) => {
64
+ (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = element;
65
+ if (typeof ref === "function") {
66
+ ref(element);
67
+ } else if (ref) {
68
+ (ref as React.MutableRefObject<HTMLDivElement | null>).current = element;
69
+ }
70
+ },
71
+ [ref]
72
+ );
73
+
74
+ // Don't render if closed and not force mounted
75
+ if (!open && !forceMount) {
76
+ return null;
77
+ }
78
+
79
+ const content = (
80
+ <div
81
+ ref={mergedRef}
82
+ id={contentProps.id}
83
+ role={contentProps.role}
84
+ aria-modal={modal ? contentProps["aria-modal"] : undefined}
85
+ aria-labelledby={contentProps["aria-labelledby"]}
86
+ aria-describedby={descriptionId ?? undefined}
87
+ tabIndex={contentProps.tabIndex}
88
+ data-state={open ? "open" : "closed"}
89
+ data-size={size}
90
+ {...restProps}
91
+ >
92
+ {children}
93
+ </div>
94
+ );
95
+
96
+ // Render in portal
97
+ const portalContainer = container ?? (typeof document !== "undefined" ? document.body : null);
98
+ if (portalContainer) {
99
+ return createPortal(content, portalContainer);
100
+ }
101
+
102
+ return content;
103
+ }
104
+ );
105
+
106
+ DialogContent.displayName = "Dialog.Content";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Dialog context for compound component pattern.
3
+ */
4
+
5
+ import type { DialogBehavior } from "@hypoth-ui/primitives-dom";
6
+ import { createCompoundContext } from "../../utils/create-context.js";
7
+
8
+ export interface DialogContextValue {
9
+ /** Dialog behavior instance */
10
+ behavior: DialogBehavior;
11
+ /** Whether dialog is open */
12
+ open: boolean;
13
+ /** Set open state */
14
+ setOpen: (open: boolean) => void;
15
+ /** Whether dialog is modal */
16
+ modal: boolean;
17
+ /** Description ID when description is present */
18
+ descriptionId: string | null;
19
+ /** Set description ID */
20
+ setDescriptionId: (id: string | null) => void;
21
+ }
22
+
23
+ export const [DialogProvider, useDialogContext] =
24
+ createCompoundContext<DialogContextValue>("Dialog");
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Dialog Description component - accessible description for the dialog.
3
+ */
4
+
5
+ import { type HTMLAttributes, type ReactNode, forwardRef, useEffect } from "react";
6
+ import { useDialogContext } from "./dialog-context.js";
7
+
8
+ export interface DialogDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {
9
+ /** Description content */
10
+ children?: ReactNode;
11
+ }
12
+
13
+ /**
14
+ * Accessible description for the dialog.
15
+ * Automatically linked to dialog via aria-describedby.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <Dialog.Content>
20
+ * <Dialog.Title>Confirm Delete</Dialog.Title>
21
+ * <Dialog.Description>
22
+ * This action cannot be undone. Are you sure?
23
+ * </Dialog.Description>
24
+ * ...
25
+ * </Dialog.Content>
26
+ * ```
27
+ */
28
+ export const DialogDescription = forwardRef<HTMLParagraphElement, DialogDescriptionProps>(
29
+ ({ children, ...restProps }, ref) => {
30
+ const { behavior, setDescriptionId } = useDialogContext("Dialog.Description");
31
+ const descriptionProps = behavior.getDescriptionProps();
32
+
33
+ // Register description presence with both behavior and React context
34
+ useEffect(() => {
35
+ behavior.setHasDescription(true);
36
+ setDescriptionId(descriptionProps.id);
37
+ return () => {
38
+ behavior.setHasDescription(false);
39
+ setDescriptionId(null);
40
+ };
41
+ }, [behavior, descriptionProps.id, setDescriptionId]);
42
+
43
+ return (
44
+ <p ref={ref} id={descriptionProps.id} {...restProps}>
45
+ {children}
46
+ </p>
47
+ );
48
+ }
49
+ );
50
+
51
+ DialogDescription.displayName = "Dialog.Description";