@grampro/kit 0.0.0-stage → 2.4.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 (505) hide show
  1. package/.gbs/skills/gbs-components/SKILL.md +190 -0
  2. package/.gbs/skills/gbs-components/references/accessibility.md +102 -0
  3. package/.gbs/skills/gbs-components/references/data-display.md +185 -0
  4. package/.gbs/skills/gbs-components/references/data-grid.md +116 -0
  5. package/.gbs/skills/gbs-components/references/forms.md +202 -0
  6. package/.gbs/skills/gbs-components/references/install.md +211 -0
  7. package/.gbs/skills/gbs-components/references/overlays.md +206 -0
  8. package/.gbs/skills/gbs-components/references/pickers.md +83 -0
  9. package/.gbs/skills/gbs-components/references/styling.md +246 -0
  10. package/.gbs/skills/gbs-components/references/toaster.md +57 -0
  11. package/CHANGELOG.md +173 -0
  12. package/LICENSE +15 -0
  13. package/README.md +193 -2
  14. package/index.cjs +1410 -0
  15. package/package.json +77 -6
  16. package/schema/passport-v1.schema.json +204 -0
  17. package/source/components/accordion/README.md +45 -0
  18. package/source/components/accordion/core/index.ts +2 -0
  19. package/source/components/accordion/core/open.ts +56 -0
  20. package/source/components/accordion/core/types.ts +17 -0
  21. package/source/components/accordion/index.ts +8 -0
  22. package/source/components/accordion/passport.json +259 -0
  23. package/source/components/accordion/react/Accordion.tsx +133 -0
  24. package/source/components/accordion/react/AccordionItem.tsx +120 -0
  25. package/source/components/accordion/react/context.ts +20 -0
  26. package/source/components/accordion/react/props.ts +13 -0
  27. package/source/components/accordion/styles.css +207 -0
  28. package/source/components/alert/README.md +39 -0
  29. package/source/components/alert/core/index.ts +2 -0
  30. package/source/components/alert/core/live.ts +25 -0
  31. package/source/components/alert/core/types.ts +7 -0
  32. package/source/components/alert/index.ts +5 -0
  33. package/source/components/alert/passport.json +250 -0
  34. package/source/components/alert/react/Alert.tsx +113 -0
  35. package/source/components/alert/react/icons.tsx +21 -0
  36. package/source/components/alert/react/locale.ts +5 -0
  37. package/source/components/alert/react/props.ts +11 -0
  38. package/source/components/alert/styles.css +154 -0
  39. package/source/components/avatar/core/identity.ts +103 -0
  40. package/source/components/avatar/core/index.ts +2 -0
  41. package/source/components/avatar/core/types.ts +12 -0
  42. package/source/components/avatar/index.ts +7 -0
  43. package/source/components/avatar/passport.json +294 -0
  44. package/source/components/avatar/react/Avatar.tsx +118 -0
  45. package/source/components/avatar/react/AvatarGroup.tsx +74 -0
  46. package/source/components/avatar/react/locale.ts +11 -0
  47. package/source/components/avatar/react/props.ts +6 -0
  48. package/source/components/avatar/styles.css +225 -0
  49. package/source/components/badge/README.md +37 -0
  50. package/source/components/badge/core/count.ts +37 -0
  51. package/source/components/badge/core/index.ts +2 -0
  52. package/source/components/badge/core/types.ts +11 -0
  53. package/source/components/badge/index.ts +7 -0
  54. package/source/components/badge/passport.json +332 -0
  55. package/source/components/badge/react/Badge.tsx +89 -0
  56. package/source/components/badge/react/Tag.tsx +82 -0
  57. package/source/components/badge/react/locale.ts +5 -0
  58. package/source/components/badge/react/props.ts +6 -0
  59. package/source/components/badge/styles.css +203 -0
  60. package/source/components/breadcrumb/README.md +34 -0
  61. package/source/components/breadcrumb/core/index.ts +4 -0
  62. package/source/components/breadcrumb/core/trail.ts +42 -0
  63. package/source/components/breadcrumb/core/types.ts +20 -0
  64. package/source/components/breadcrumb/index.ts +5 -0
  65. package/source/components/breadcrumb/passport.json +243 -0
  66. package/source/components/breadcrumb/react/Breadcrumb.tsx +177 -0
  67. package/source/components/breadcrumb/react/locale.ts +6 -0
  68. package/source/components/breadcrumb/react/props.ts +14 -0
  69. package/source/components/breadcrumb/styles.css +138 -0
  70. package/source/components/button/README.md +32 -0
  71. package/source/components/button/core/index.ts +3 -0
  72. package/source/components/button/core/state.ts +16 -0
  73. package/source/components/button/core/types.ts +28 -0
  74. package/source/components/button/index.ts +5 -0
  75. package/source/components/button/passport.json +402 -0
  76. package/source/components/button/passport.manual.json +31 -0
  77. package/source/components/button/react/Button.tsx +173 -0
  78. package/source/components/button/react/locale.ts +5 -0
  79. package/source/components/button/react/props.ts +23 -0
  80. package/source/components/button/styles.css +232 -0
  81. package/source/components/card/README.md +60 -0
  82. package/source/components/card/core/index.ts +4 -0
  83. package/source/components/card/core/trend.ts +32 -0
  84. package/source/components/card/core/types.ts +5 -0
  85. package/source/components/card/index.ts +5 -0
  86. package/source/components/card/passport.json +337 -0
  87. package/source/components/card/react/Card.tsx +133 -0
  88. package/source/components/card/react/Stat.tsx +127 -0
  89. package/source/components/card/react/props.ts +4 -0
  90. package/source/components/card/styles.css +230 -0
  91. package/source/components/checkbox/README.md +36 -0
  92. package/source/components/checkbox/core/group.ts +30 -0
  93. package/source/components/checkbox/core/index.ts +3 -0
  94. package/source/components/checkbox/core/types.ts +17 -0
  95. package/source/components/checkbox/index.ts +9 -0
  96. package/source/components/checkbox/passport.json +456 -0
  97. package/source/components/checkbox/react/Checkbox.tsx +147 -0
  98. package/source/components/checkbox/react/CheckboxGroup.tsx +149 -0
  99. package/source/components/checkbox/react/context.ts +16 -0
  100. package/source/components/checkbox/react/locale.ts +5 -0
  101. package/source/components/checkbox/react/props.ts +6 -0
  102. package/source/components/checkbox/styles.css +211 -0
  103. package/source/components/combobox/README.md +170 -0
  104. package/source/components/combobox/core/filter.ts +155 -0
  105. package/source/components/combobox/core/index.ts +16 -0
  106. package/source/components/combobox/core/types.ts +48 -0
  107. package/source/components/combobox/core/virtual.ts +67 -0
  108. package/source/components/combobox/index.ts +13 -0
  109. package/source/components/combobox/passport.json +456 -0
  110. package/source/components/combobox/react/Listbox.tsx +310 -0
  111. package/source/components/combobox/react/MultiSelect.tsx +265 -0
  112. package/source/components/combobox/react/Popover.tsx +38 -0
  113. package/source/components/combobox/react/Select.tsx +200 -0
  114. package/source/components/combobox/react/icons.tsx +11 -0
  115. package/source/components/combobox/react/locale.ts +16 -0
  116. package/source/components/combobox/react/props.ts +88 -0
  117. package/source/components/combobox/react/useCombobox.ts +409 -0
  118. package/source/components/combobox/styles.css +419 -0
  119. package/source/components/data-grid/README.md +233 -0
  120. package/source/components/data-grid/agent/coerce.ts +368 -0
  121. package/source/components/data-grid/agent/contract.ts +410 -0
  122. package/source/components/data-grid/agent/dataset.ts +92 -0
  123. package/source/components/data-grid/agent/engine.ts +470 -0
  124. package/source/components/data-grid/agent/executors.ts +155 -0
  125. package/source/components/data-grid/agent/index.ts +79 -0
  126. package/source/components/data-grid/agent/intent.ts +324 -0
  127. package/source/components/data-grid/agent/operations.ts +335 -0
  128. package/source/components/data-grid/agent/validate.ts +630 -0
  129. package/source/components/data-grid/agent/webmcp.ts +107 -0
  130. package/source/components/data-grid/core/columnHelper.ts +21 -0
  131. package/source/components/data-grid/core/columns.ts +288 -0
  132. package/source/components/data-grid/core/filtering.ts +237 -0
  133. package/source/components/data-grid/core/grid.ts +638 -0
  134. package/source/components/data-grid/core/index.ts +14 -0
  135. package/source/components/data-grid/core/rows.ts +64 -0
  136. package/source/components/data-grid/core/sorting.ts +133 -0
  137. package/source/components/data-grid/core/state.ts +82 -0
  138. package/source/components/data-grid/core/store.ts +52 -0
  139. package/source/components/data-grid/core/types.ts +302 -0
  140. package/source/components/data-grid/core/values.ts +98 -0
  141. package/source/components/data-grid/core/virtual.ts +85 -0
  142. package/source/components/data-grid/export/csv.ts +32 -0
  143. package/source/components/data-grid/export/download.ts +12 -0
  144. package/source/components/data-grid/export/pdf-font.ts +139 -0
  145. package/source/components/data-grid/export/pdf-writer.ts +113 -0
  146. package/source/components/data-grid/export/pdf.ts +379 -0
  147. package/source/components/data-grid/export/table.ts +73 -0
  148. package/source/components/data-grid/export/xlsx.ts +145 -0
  149. package/source/components/data-grid/export/zip.ts +87 -0
  150. package/source/components/data-grid/index.ts +14 -0
  151. package/source/components/data-grid/passport.json +1051 -0
  152. package/source/components/data-grid/passport.manual.json +255 -0
  153. package/source/components/data-grid/react/AskGrid.tsx +164 -0
  154. package/source/components/data-grid/react/Cell.tsx +282 -0
  155. package/source/components/data-grid/react/ColumnMenu.tsx +135 -0
  156. package/source/components/data-grid/react/DataGrid.tsx +335 -0
  157. package/source/components/data-grid/react/FilterForm.tsx +142 -0
  158. package/source/components/data-grid/react/HeaderRow.tsx +332 -0
  159. package/source/components/data-grid/react/Pagination.tsx +107 -0
  160. package/source/components/data-grid/react/Popover.tsx +35 -0
  161. package/source/components/data-grid/react/Row.tsx +85 -0
  162. package/source/components/data-grid/react/Toolbar.tsx +267 -0
  163. package/source/components/data-grid/react/Viewport.tsx +167 -0
  164. package/source/components/data-grid/react/context.ts +57 -0
  165. package/source/components/data-grid/react/hooks.ts +83 -0
  166. package/source/components/data-grid/react/icons.tsx +45 -0
  167. package/source/components/data-grid/react/keyboard.ts +110 -0
  168. package/source/components/data-grid/react/locale.ts +130 -0
  169. package/source/components/data-grid/styles.css +874 -0
  170. package/source/components/date-picker/README.md +141 -0
  171. package/source/components/date-picker/core/calendar.ts +266 -0
  172. package/source/components/date-picker/core/format.ts +170 -0
  173. package/source/components/date-picker/core/index.ts +41 -0
  174. package/source/components/date-picker/core/today.ts +72 -0
  175. package/source/components/date-picker/core/types.ts +68 -0
  176. package/source/components/date-picker/index.ts +16 -0
  177. package/source/components/date-picker/passport.json +407 -0
  178. package/source/components/date-picker/react/Calendar.tsx +160 -0
  179. package/source/components/date-picker/react/CalendarPanel.tsx +170 -0
  180. package/source/components/date-picker/react/DatePicker.tsx +249 -0
  181. package/source/components/date-picker/react/DateRangePicker.tsx +291 -0
  182. package/source/components/date-picker/react/MonthYearPanels.tsx +123 -0
  183. package/source/components/date-picker/react/Popover.tsx +45 -0
  184. package/source/components/date-picker/react/icons.tsx +15 -0
  185. package/source/components/date-picker/react/locale.ts +23 -0
  186. package/source/components/date-picker/react/props.ts +76 -0
  187. package/source/components/date-picker/react/useDatePicker.ts +557 -0
  188. package/source/components/date-picker/styles.css +445 -0
  189. package/source/components/dialog/README.md +39 -0
  190. package/source/components/dialog/core/api.ts +34 -0
  191. package/source/components/dialog/core/dialog.ts +11 -0
  192. package/source/components/dialog/core/index.ts +7 -0
  193. package/source/components/dialog/core/store.ts +81 -0
  194. package/source/components/dialog/core/types.ts +59 -0
  195. package/source/components/dialog/index.ts +7 -0
  196. package/source/components/dialog/passport.json +344 -0
  197. package/source/components/dialog/react/Dialog.tsx +279 -0
  198. package/source/components/dialog/react/DialogHost.tsx +45 -0
  199. package/source/components/dialog/react/icons.tsx +20 -0
  200. package/source/components/dialog/react/locale.ts +8 -0
  201. package/source/components/dialog/react/props.ts +13 -0
  202. package/source/components/dialog/styles.css +280 -0
  203. package/source/components/file-uploader/README.md +99 -0
  204. package/source/components/file-uploader/core/format.ts +79 -0
  205. package/source/components/file-uploader/core/index.ts +26 -0
  206. package/source/components/file-uploader/core/store.ts +432 -0
  207. package/source/components/file-uploader/core/transport.ts +154 -0
  208. package/source/components/file-uploader/core/types.ts +126 -0
  209. package/source/components/file-uploader/core/validate.ts +83 -0
  210. package/source/components/file-uploader/index.ts +7 -0
  211. package/source/components/file-uploader/passport.json +518 -0
  212. package/source/components/file-uploader/react/FileRow.tsx +268 -0
  213. package/source/components/file-uploader/react/FileUploader.tsx +511 -0
  214. package/source/components/file-uploader/react/icons.tsx +60 -0
  215. package/source/components/file-uploader/react/locale.ts +33 -0
  216. package/source/components/file-uploader/react/props.ts +31 -0
  217. package/source/components/file-uploader/react/useFileUploader.ts +32 -0
  218. package/source/components/file-uploader/styles.css +394 -0
  219. package/source/components/input/README.md +44 -0
  220. package/source/components/input/core/count.ts +5 -0
  221. package/source/components/input/core/index.ts +11 -0
  222. package/source/components/input/core/otp.ts +64 -0
  223. package/source/components/input/core/types.ts +14 -0
  224. package/source/components/input/index.ts +8 -0
  225. package/source/components/input/passport.json +536 -0
  226. package/source/components/input/react/Input.tsx +219 -0
  227. package/source/components/input/react/OtpInput.tsx +256 -0
  228. package/source/components/input/react/dom.ts +10 -0
  229. package/source/components/input/react/icons.tsx +17 -0
  230. package/source/components/input/react/locale.ts +9 -0
  231. package/source/components/input/react/props.ts +6 -0
  232. package/source/components/input/styles.css +295 -0
  233. package/source/components/menu/README.md +68 -0
  234. package/source/components/menu/core/index.ts +11 -0
  235. package/source/components/menu/core/navigation.ts +84 -0
  236. package/source/components/menu/core/types.ts +32 -0
  237. package/source/components/menu/index.ts +19 -0
  238. package/source/components/menu/passport.json +322 -0
  239. package/source/components/menu/react/Menu.tsx +371 -0
  240. package/source/components/menu/react/context.ts +31 -0
  241. package/source/components/menu/react/items.tsx +268 -0
  242. package/source/components/menu/react/locale.ts +6 -0
  243. package/source/components/menu/react/props.ts +4 -0
  244. package/source/components/menu/react/useMenuKeys.ts +124 -0
  245. package/source/components/menu/styles.css +224 -0
  246. package/source/components/modal/README.md +51 -0
  247. package/source/components/modal/core/dismiss.ts +29 -0
  248. package/source/components/modal/core/index.ts +3 -0
  249. package/source/components/modal/core/types.ts +27 -0
  250. package/source/components/modal/index.ts +5 -0
  251. package/source/components/modal/passport.json +289 -0
  252. package/source/components/modal/react/Modal.tsx +242 -0
  253. package/source/components/modal/react/icons.tsx +1 -0
  254. package/source/components/modal/react/locale.ts +5 -0
  255. package/source/components/modal/react/props.ts +17 -0
  256. package/source/components/modal/styles.css +241 -0
  257. package/source/components/number-input/README.md +53 -0
  258. package/source/components/number-input/core/format.ts +37 -0
  259. package/source/components/number-input/core/index.ts +4 -0
  260. package/source/components/number-input/core/parse.ts +118 -0
  261. package/source/components/number-input/core/step.ts +73 -0
  262. package/source/components/number-input/core/types.ts +32 -0
  263. package/source/components/number-input/index.ts +5 -0
  264. package/source/components/number-input/passport.json +541 -0
  265. package/source/components/number-input/react/NumberInput.tsx +465 -0
  266. package/source/components/number-input/react/icons.tsx +17 -0
  267. package/source/components/number-input/react/locale.ts +7 -0
  268. package/source/components/number-input/react/props.ts +11 -0
  269. package/source/components/number-input/styles.css +230 -0
  270. package/source/components/popover/README.md +44 -0
  271. package/source/components/popover/core/index.ts +2 -0
  272. package/source/components/popover/core/types.ts +12 -0
  273. package/source/components/popover/index.ts +4 -0
  274. package/source/components/popover/passport.json +238 -0
  275. package/source/components/popover/react/Popover.tsx +202 -0
  276. package/source/components/popover/react/locale.ts +5 -0
  277. package/source/components/popover/react/props.ts +4 -0
  278. package/source/components/popover/styles.css +148 -0
  279. package/source/components/progress/README.md +38 -0
  280. package/source/components/progress/core/index.ts +2 -0
  281. package/source/components/progress/core/progress.ts +40 -0
  282. package/source/components/progress/core/types.ts +23 -0
  283. package/source/components/progress/index.ts +7 -0
  284. package/source/components/progress/passport.json +270 -0
  285. package/source/components/progress/react/CircularProgress.tsx +106 -0
  286. package/source/components/progress/react/Progress.tsx +113 -0
  287. package/source/components/progress/react/locale.ts +7 -0
  288. package/source/components/progress/react/props.ts +6 -0
  289. package/source/components/progress/styles.css +200 -0
  290. package/source/components/radio-group/README.md +58 -0
  291. package/source/components/radio-group/core/group.ts +36 -0
  292. package/source/components/radio-group/core/index.ts +2 -0
  293. package/source/components/radio-group/core/types.ts +20 -0
  294. package/source/components/radio-group/index.ts +9 -0
  295. package/source/components/radio-group/passport.json +477 -0
  296. package/source/components/radio-group/react/Radio.tsx +144 -0
  297. package/source/components/radio-group/react/RadioGroup.tsx +179 -0
  298. package/source/components/radio-group/react/context.ts +18 -0
  299. package/source/components/radio-group/react/locale.ts +5 -0
  300. package/source/components/radio-group/react/props.ts +12 -0
  301. package/source/components/radio-group/styles.css +269 -0
  302. package/source/components/shared/README.md +54 -0
  303. package/source/components/shared/core/agent/adapter.ts +65 -0
  304. package/source/components/shared/core/agent/history.ts +120 -0
  305. package/source/components/shared/core/agent/index.ts +46 -0
  306. package/source/components/shared/core/agent/numbers.ts +217 -0
  307. package/source/components/shared/core/agent/schema.ts +180 -0
  308. package/source/components/shared/core/agent/types.ts +169 -0
  309. package/source/components/shared/core/agent/webmcp.ts +328 -0
  310. package/source/components/shared/core/cx.ts +9 -0
  311. package/source/components/shared/core/field.ts +35 -0
  312. package/source/components/shared/core/position.ts +124 -0
  313. package/source/components/shared/core/text.ts +14 -0
  314. package/source/components/shared/index.ts +17 -0
  315. package/source/components/shared/react/GramproAIProvider.tsx +50 -0
  316. package/source/components/shared/react/Popover.tsx +189 -0
  317. package/source/components/shared/react/icons.tsx +74 -0
  318. package/source/components/shared/react/useAskAgent.ts +217 -0
  319. package/source/components/shared/react/useControllableState.ts +42 -0
  320. package/source/components/shared/styles.css +79 -0
  321. package/source/components/shared/version.json +4 -0
  322. package/source/components/shared/version.ts +6 -0
  323. package/source/components/skeleton/README.md +58 -0
  324. package/source/components/skeleton/core/index.ts +3 -0
  325. package/source/components/skeleton/core/lines.ts +25 -0
  326. package/source/components/skeleton/core/types.ts +5 -0
  327. package/source/components/skeleton/index.ts +5 -0
  328. package/source/components/skeleton/passport.json +251 -0
  329. package/source/components/skeleton/react/Empty.tsx +51 -0
  330. package/source/components/skeleton/react/Skeleton.tsx +92 -0
  331. package/source/components/skeleton/react/props.ts +4 -0
  332. package/source/components/skeleton/styles.css +185 -0
  333. package/source/components/spinner/README.md +29 -0
  334. package/source/components/spinner/core/index.ts +3 -0
  335. package/source/components/spinner/core/types.ts +28 -0
  336. package/source/components/spinner/core/visibility.ts +34 -0
  337. package/source/components/spinner/index.ts +6 -0
  338. package/source/components/spinner/passport.json +245 -0
  339. package/source/components/spinner/react/Spinner.tsx +96 -0
  340. package/source/components/spinner/react/locale.ts +5 -0
  341. package/source/components/spinner/react/props.ts +4 -0
  342. package/source/components/spinner/react/useDelayedLoading.ts +39 -0
  343. package/source/components/spinner/styles.css +173 -0
  344. package/source/components/switch/README.md +46 -0
  345. package/source/components/switch/core/index.ts +2 -0
  346. package/source/components/switch/core/toggle.ts +54 -0
  347. package/source/components/switch/core/types.ts +9 -0
  348. package/source/components/switch/index.ts +5 -0
  349. package/source/components/switch/passport.json +421 -0
  350. package/source/components/switch/react/Switch.tsx +219 -0
  351. package/source/components/switch/react/locale.ts +5 -0
  352. package/source/components/switch/react/props.ts +12 -0
  353. package/source/components/switch/styles.css +227 -0
  354. package/source/components/tabs/README.md +32 -0
  355. package/source/components/tabs/core/index.ts +3 -0
  356. package/source/components/tabs/core/navigation.ts +54 -0
  357. package/source/components/tabs/core/types.ts +16 -0
  358. package/source/components/tabs/index.ts +5 -0
  359. package/source/components/tabs/passport.json +315 -0
  360. package/source/components/tabs/react/Tabs.tsx +306 -0
  361. package/source/components/tabs/react/context.ts +21 -0
  362. package/source/components/tabs/styles.css +263 -0
  363. package/source/components/textarea/README.md +39 -0
  364. package/source/components/textarea/core/count.ts +5 -0
  365. package/source/components/textarea/core/index.ts +4 -0
  366. package/source/components/textarea/core/size.ts +23 -0
  367. package/source/components/textarea/core/types.ts +17 -0
  368. package/source/components/textarea/index.ts +5 -0
  369. package/source/components/textarea/passport.json +382 -0
  370. package/source/components/textarea/react/Textarea.tsx +208 -0
  371. package/source/components/textarea/react/locale.ts +5 -0
  372. package/source/components/textarea/react/props.ts +4 -0
  373. package/source/components/textarea/styles.css +158 -0
  374. package/source/components/toaster/README.md +128 -0
  375. package/source/components/toaster/core/api.ts +87 -0
  376. package/source/components/toaster/core/index.ts +14 -0
  377. package/source/components/toaster/core/store.ts +211 -0
  378. package/source/components/toaster/core/toast.ts +12 -0
  379. package/source/components/toaster/core/types.ts +78 -0
  380. package/source/components/toaster/index.ts +6 -0
  381. package/source/components/toaster/passport.json +221 -0
  382. package/source/components/toaster/react/ToastItem.tsx +164 -0
  383. package/source/components/toaster/react/Toaster.tsx +187 -0
  384. package/source/components/toaster/react/icons.tsx +23 -0
  385. package/source/components/toaster/react/locale.ts +6 -0
  386. package/source/components/toaster/react/props.ts +15 -0
  387. package/source/components/toaster/react/useToasts.ts +11 -0
  388. package/source/components/toaster/styles.css +282 -0
  389. package/source/components/tooltip/README.md +43 -0
  390. package/source/components/tooltip/core/index.ts +4 -0
  391. package/source/components/tooltip/core/types.ts +4 -0
  392. package/source/components/tooltip/core/visibility.ts +34 -0
  393. package/source/components/tooltip/index.ts +3 -0
  394. package/source/components/tooltip/passport.json +170 -0
  395. package/source/components/tooltip/react/Tooltip.tsx +196 -0
  396. package/source/components/tooltip/react/props.ts +4 -0
  397. package/source/components/tooltip/styles.css +71 -0
  398. package/source/fallback/index.tsx +11 -0
  399. package/source/globalStyle.ts +74 -0
  400. package/source/hooks/SelectionHooks/type.ts +4 -0
  401. package/source/hooks/SelectionHooks/useClickOutside.ts +22 -0
  402. package/source/hooks/SelectionHooks/useMultiSelectState.ts +64 -0
  403. package/source/hooks/SelectionHooks/useSelectData.ts +58 -0
  404. package/source/hooks/SelectionHooks/useSelectState.ts +36 -0
  405. package/source/icon/Icon.tsx +102 -0
  406. package/source/icon/iconPaths.ts +206 -0
  407. package/source/legacy-components/bargraph/index.tsx +95 -0
  408. package/source/legacy-components/bargraph/type.ts +12 -0
  409. package/source/legacy-components/breadcrumb/index.tsx +66 -0
  410. package/source/legacy-components/breadcrumb/types.ts +10 -0
  411. package/source/legacy-components/button/index.tsx +42 -0
  412. package/source/legacy-components/card/index.tsx +31 -0
  413. package/source/legacy-components/checkbox/index.tsx +25 -0
  414. package/source/legacy-components/contextmenu/ContextMenu.tsx +87 -0
  415. package/source/legacy-components/contextmenu/ContextMenuItem.tsx +30 -0
  416. package/source/legacy-components/contextmenu/index.ts +2 -0
  417. package/source/legacy-components/contextmenu/types.ts +19 -0
  418. package/source/legacy-components/darkmode/index.tsx +55 -0
  419. package/source/legacy-components/datagrid/context/GridContext.tsx +215 -0
  420. package/source/legacy-components/datagrid/createTemplate.tsx +82 -0
  421. package/source/legacy-components/datagrid/hooks/index.ts +6 -0
  422. package/source/legacy-components/datagrid/hooks/useColumns.ts +33 -0
  423. package/source/legacy-components/datagrid/hooks/useDataSource.ts +54 -0
  424. package/source/legacy-components/datagrid/hooks/useFiltering.ts +169 -0
  425. package/source/legacy-components/datagrid/hooks/usePagination.ts +112 -0
  426. package/source/legacy-components/datagrid/hooks/useRowSelection.ts +48 -0
  427. package/source/legacy-components/datagrid/hooks/useSearch.ts +90 -0
  428. package/source/legacy-components/datagrid/index.tsx +28 -0
  429. package/source/legacy-components/datagrid/layout/FilterPopup.tsx +92 -0
  430. package/source/legacy-components/datagrid/layout/GridBody.tsx +184 -0
  431. package/source/legacy-components/datagrid/layout/GridPagination.tsx +119 -0
  432. package/source/legacy-components/datagrid/layout/GridToolbar.tsx +109 -0
  433. package/source/legacy-components/datagrid/layout/index.tsx +68 -0
  434. package/source/legacy-components/datagrid/type.ts +198 -0
  435. package/source/legacy-components/datepicker/index.tsx +365 -0
  436. package/source/legacy-components/datepicker/types.ts +12 -0
  437. package/source/legacy-components/dialog/index.tsx +44 -0
  438. package/source/legacy-components/dialog/types.ts +12 -0
  439. package/source/legacy-components/formrenderer/componentHandles/CheckBoxHandles.tsx +80 -0
  440. package/source/legacy-components/formrenderer/componentHandles/DatePickerHandles.tsx +93 -0
  441. package/source/legacy-components/formrenderer/componentHandles/InputHandles.tsx +106 -0
  442. package/source/legacy-components/formrenderer/componentHandles/MultiHandles.tsx +93 -0
  443. package/source/legacy-components/formrenderer/componentHandles/SelectHandles.tsx +95 -0
  444. package/source/legacy-components/formrenderer/helperFunctions.ts +72 -0
  445. package/source/legacy-components/formrenderer/index.tsx +93 -0
  446. package/source/legacy-components/formrenderer/types.ts +127 -0
  447. package/source/legacy-components/input/index.tsx +142 -0
  448. package/source/legacy-components/input/types.ts +10 -0
  449. package/source/legacy-components/materialInput/index.tsx +138 -0
  450. package/source/legacy-components/materialInput/types.ts +16 -0
  451. package/source/legacy-components/modal/index.tsx +152 -0
  452. package/source/legacy-components/modal/types.ts +19 -0
  453. package/source/legacy-components/multiselect/PortalDropDown.tsx +44 -0
  454. package/source/legacy-components/multiselect/index.tsx +269 -0
  455. package/source/legacy-components/multiselect/types.ts +26 -0
  456. package/source/legacy-components/navbar/AuthSection.tsx +98 -0
  457. package/source/legacy-components/navbar/MobileToggle.tsx +31 -0
  458. package/source/legacy-components/navbar/NavItemComponent.tsx +119 -0
  459. package/source/legacy-components/navbar/SearchBar.tsx +55 -0
  460. package/source/legacy-components/navbar/helperFunctions.ts +27 -0
  461. package/source/legacy-components/navbar/index.tsx +156 -0
  462. package/source/legacy-components/navbar/types.ts +69 -0
  463. package/source/legacy-components/select/PortalDropDown.tsx +59 -0
  464. package/source/legacy-components/select/index.tsx +249 -0
  465. package/source/legacy-components/select/types.ts +29 -0
  466. package/source/legacy-components/sidebar/components/MenuItem.tsx +101 -0
  467. package/source/legacy-components/sidebar/components/MenuSection.tsx +34 -0
  468. package/source/legacy-components/sidebar/components/SidebarFooter.tsx +50 -0
  469. package/source/legacy-components/sidebar/components/SidebarLogo.tsx +24 -0
  470. package/source/legacy-components/sidebar/components/SidebarMenu.tsx +29 -0
  471. package/source/legacy-components/sidebar/components/UserProfile.tsx +31 -0
  472. package/source/legacy-components/sidebar/hooks/useSidebarState.ts +41 -0
  473. package/source/legacy-components/sidebar/index.tsx +77 -0
  474. package/source/legacy-components/sidebar/type.ts +42 -0
  475. package/source/legacy-components/spinner/Stroke.tsx +89 -0
  476. package/source/legacy-components/spinner/index.tsx +54 -0
  477. package/source/legacy-components/spinner/types.ts +9 -0
  478. package/source/legacy-components/tabs/index.tsx +71 -0
  479. package/source/legacy-components/tabs/types.ts +16 -0
  480. package/source/legacy-components/textarea/index.tsx +123 -0
  481. package/source/legacy-components/textarea/types.ts +15 -0
  482. package/source/legacy-components/toast/ToastComponent.tsx +101 -0
  483. package/source/legacy-components/toast/ToastIcon.tsx +61 -0
  484. package/source/legacy-components/toast/index.tsx +42 -0
  485. package/source/legacy-components/toast/toastStore.ts +42 -0
  486. package/source/legacy-components/toast/types.ts +18 -0
  487. package/source/legacy-components/toast/useToast.ts +12 -0
  488. package/source/legacy-components/uploader/ProgressAnimation.tsx +50 -0
  489. package/source/legacy-components/uploader/UploadedFilePreview.tsx +130 -0
  490. package/source/legacy-components/uploader/index.tsx +311 -0
  491. package/source/legacy-components/uploader/types.ts +30 -0
  492. package/source/legacy-components/uploader/uploaderIcon.tsx +72 -0
  493. package/source/legacy-components/uploader/uploaderService.ts +134 -0
  494. package/source/legacy-components/useUploader/useUploader.tsx +310 -0
  495. package/source/legacy-components/usepaginateddata/usePaginatedData.ts +174 -0
  496. package/source/theme.ts +40 -0
  497. package/source/utils.ts +10 -0
  498. package/tools/env.cjs +61 -0
  499. package/tools/passport/cli.cjs +79 -0
  500. package/tools/passport/extract.cjs +493 -0
  501. package/tools/passport/index.cjs +185 -0
  502. package/tools/passport/merge.cjs +131 -0
  503. package/tools/passport/policy.cjs +65 -0
  504. package/tools/passport/validate.cjs +277 -0
  505. package/tools/ts-require.cjs +79 -0
@@ -0,0 +1,141 @@
1
+ # DatePicker and DateRangePicker
2
+
3
+ Two date fields built on one engine and styled to match the DataGrid and the
4
+ Combobox. No runtime dependencies besides React 19.
5
+
6
+ - **`<DatePicker>`** — one date.
7
+ - **`<DateRangePicker>`** — a start and an end, with a live preview between them.
8
+
9
+ Both support typed entry in the user's own date order, min/max limits, blocked
10
+ days, month and year panels, week numbers, forms, full keyboard control, dark
11
+ mode and right-to-left layouts.
12
+
13
+ ## Setup
14
+
15
+ ```ts
16
+ import { DatePicker, DateRangePicker } from "@/components/date-picker";
17
+ import "@/components/date-picker/styles.css";
18
+ ```
19
+
20
+ The stylesheet reads the shared `--gbs-*` variables when you set them on
21
+ `:root`, so every component in the library shares one theme, and falls back to
22
+ the same default palette otherwise.
23
+
24
+ ## Basic usage
25
+
26
+ ```tsx
27
+ const [date, setDate] = useState<Date | null>(null);
28
+
29
+ <DatePicker label="Start date" value={date} onChange={setDate} />;
30
+
31
+ const [range, setRange] = useState<DateRange>({ start: null, end: null });
32
+
33
+ <DateRangePicker label="Period" value={range} onChange={setRange} />;
34
+ ```
35
+
36
+ Both are controlled with `value` + `onChange`, or uncontrolled with
37
+ `defaultValue`. `value`, `defaultValue`, `min` and `max` accept a `Date`, an ISO
38
+ `yyyy-mm-dd` string or a timestamp; `onChange` always gives you `Date` objects at
39
+ local midnight. The range picker's `onChange` also receives a `complete` flag,
40
+ which is `false` after the first of the two clicks.
41
+
42
+ ## Props
43
+
44
+ Shared by both components:
45
+
46
+ | Prop | Type | Default | Description |
47
+ | --- | --- | --- | --- |
48
+ | `min` / `max` | `Date \| string \| number` | — | Earliest and latest selectable day. |
49
+ | `isDateDisabled` | `(date: Date) => boolean` | — | Called per rendered day; return true to block it. |
50
+ | `locale` | `string` | runtime locale | Decides month names, field order and the first day of the week. |
51
+ | `weekStartsOn` | `0`–`6` | locale's first day | 0 is Sunday. |
52
+ | `format` | `Intl.DateTimeFormatOptions \| (date, locale) => string` | `{ year, month: "short", day }` | How the chosen date is written in the field. |
53
+ | `numberOfMonths` | `number` | `1` (range: `2`) | Months shown side by side. |
54
+ | `showWeekNumbers` | `boolean` | `false` | Adds an ISO week column. |
55
+ | `showToday` | `boolean` | `true` (range: `false`) | The "Today" shortcut in the footer. |
56
+ | `allowInput` | `boolean` | `true` | Let people type a date. When false the field is read-only and opens on click. |
57
+ | `fixedWeeks` | `boolean` | `true` | Always six week rows, so the popover never changes height. |
58
+ | `label`, `description`, `error` | `ReactNode` | — | Field label, hint and error message. `error` also marks the control invalid. |
59
+ | `placeholder` | `string` | the locale's pattern | e.g. `dd/mm/yyyy`. |
60
+ | `required`, `disabled`, `readOnly` | `boolean` | `false` | Field states. |
61
+ | `clearable` | `boolean` | `true` | Show the clear button. |
62
+ | `size` | `"sm"` \| `"md"` \| `"lg"` | `"md"` | Control height and font size. |
63
+ | `name` | `string` | — | Posts `yyyy-mm-dd` in a hidden input. The range picker posts `name-start` and `name-end`. |
64
+ | `className`, `classNames`, `style` | — | — | Styling hooks. Slots: `root`, `label`, `control`, `input`, `popover`, `calendar`, `day`, `footer`, `presets`. |
65
+ | `localeText` | `Partial<DatePickerLocaleText>` | English | Overrides UI text. |
66
+ | `onOpenChange` | `(open: boolean) => void` | — | Fires when the calendar opens or closes. |
67
+ | `ref` | `Ref<DatePickerHandle<T>>` | — | `open()`, `close()`, `toggle()`, `focus()`, `clear()`, `getValue()`. |
68
+
69
+ `DatePicker` adds `value` / `defaultValue` (`Date \| null`) and `closeOnSelect`
70
+ (default `true`). `DateRangePicker` adds `value` / `defaultValue`
71
+ (`{ start, end }`), `presets` and `closeOnSelect` (closes once both ends are set).
72
+
73
+ ## Typed entry
74
+
75
+ People can type instead of picking. Input is read in the locale's own field
76
+ order, so `03/04/2026` is 3 April in `en-GB` and 4 March in `en-US`. ISO
77
+ (`2026-03-12`) is accepted in every locale, month names work (`12 Mar 2026`), and
78
+ a missing year or month falls back to the month on screen. The calendar follows
79
+ along as you type. Text that isn't a date, or a date outside the limits, shows a
80
+ message when the field loses focus and leaves the value untouched.
81
+
82
+ ## Range presets
83
+
84
+ ```tsx
85
+ <DateRangePicker
86
+ value={range}
87
+ onChange={setRange}
88
+ presets={[
89
+ { label: "Last 7 days", range: { start: addDays(new Date(), -6), end: new Date() } },
90
+ { label: "This month", range: { start: startOfMonth(new Date()), end: new Date() } },
91
+ ]}
92
+ />
93
+ ```
94
+
95
+ ## Keyboard
96
+
97
+ | Keys | Action |
98
+ | --- | --- |
99
+ | **Enter**, **↓** | Open the calendar (from the field). |
100
+ | **←** / **→** | Previous / next day (swapped in right-to-left layouts). |
101
+ | **↑** / **↓** | Same weekday, previous / next week. |
102
+ | **Home** / **End** | First / last day of the week. |
103
+ | **Page Up** / **Page Down** | Previous / next month. |
104
+ | **Shift + Page Up/Down** | Previous / next year. |
105
+ | **Enter**, **Space** | Choose the focused day. |
106
+ | **Escape** | Close and return focus to the calendar button. |
107
+
108
+ Each month is a `role="grid"` where only the focused day is tabbable, which is
109
+ the WAI-ARIA pattern for a date picker. The calendar renders in the top layer
110
+ through the native Popover API, so it is never clipped by a scrolling parent
111
+ (including inside a DataGrid cell).
112
+
113
+ ## Theming
114
+
115
+ Override `--dp-*` variables (they default to the grid's `--dg-*`):
116
+
117
+ ```css
118
+ .dp-root { --dp-accent: #7c3aed; --dp-radius: 12px; --dp-day-size: 38px; }
119
+ ```
120
+
121
+ State attributes for styling: `data-state="open"`, `data-size`, `data-invalid`,
122
+ `data-disabled` on the root and control; `data-today`, `data-selected`,
123
+ `data-outside`, `data-weekend` on days; `data-in-range`, `data-range-start`,
124
+ `data-range-end` on day cells.
125
+
126
+ ## Headless use
127
+
128
+ `useDatePicker()` holds the whole engine (selection, visible months, typed
129
+ input, keyboard movement, popover state) and is exported if you want a different
130
+ UI on top. The framework-free helpers — `buildMonth`, `buildMonths`, `parseDate`,
131
+ `formatDate`, `moveByKey`, `normalizeRange`, `isInRange`, `toISODate` — are
132
+ exported from `@/components/date-picker/core` and also run on a server.
133
+
134
+ ## Known limits
135
+
136
+ - Dates only: no time-of-day or time zone selection. Values are local midnight.
137
+ - One range per field; multiple disjoint ranges aren't supported.
138
+ - Only the Gregorian calendar is handled, though month and weekday names,
139
+ field order and the first day of the week all follow the locale.
140
+ - When rendering on a server, pass `locale` explicitly so the server and the
141
+ browser format the field identically.
@@ -0,0 +1,266 @@
1
+ import type {
2
+ CalendarDay,
3
+ CalendarMonth,
4
+ CalendarWeek,
5
+ DateInput,
6
+ DateLimits,
7
+ DateRange,
8
+ } from "./types";
9
+
10
+ /**
11
+ * Every date in this package is a local-time Date at midnight. Keeping one
12
+ * convention means day comparisons never depend on hours, time zones or DST,
13
+ * and `toISODate` never shifts a day the way `toISOString` does.
14
+ */
15
+ export function startOfDay(date: Date): Date {
16
+ const copy = new Date(date.getTime());
17
+ copy.setHours(0, 0, 0, 0);
18
+ return copy;
19
+ }
20
+
21
+ /** Strict `yyyy-mm-dd` in local time; null for anything else, including `2026-02-31`. */
22
+ export function parseISODate(text: string): Date | null {
23
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text.trim());
24
+ if (!match) return null;
25
+ const year = Number(match[1]);
26
+ const month = Number(match[2]) - 1;
27
+ const day = Number(match[3]);
28
+ const date = new Date(year, month, day);
29
+ date.setHours(0, 0, 0, 0);
30
+ const valid =
31
+ date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
32
+ return valid ? date : null;
33
+ }
34
+
35
+ export function toISODate(date: Date): string {
36
+ const month = String(date.getMonth() + 1).padStart(2, "0");
37
+ const day = String(date.getDate()).padStart(2, "0");
38
+ return `${date.getFullYear()}-${month}-${day}`;
39
+ }
40
+
41
+ /** Normalizes a prop value. ISO strings are read as local dates, not UTC. */
42
+ export function toDate(value: DateInput | null | undefined): Date | null {
43
+ if (value === null || value === undefined) return null;
44
+ if (typeof value === "string") {
45
+ const iso = parseISODate(value);
46
+ if (iso) return iso;
47
+ }
48
+ const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
49
+ return Number.isNaN(date.getTime()) ? null : startOfDay(date);
50
+ }
51
+
52
+ /** Negative when `a` is the earlier day, 0 on the same day. Ignores the time. */
53
+ export function compareDay(a: Date, b: Date): number {
54
+ return (
55
+ a.getFullYear() - b.getFullYear() || a.getMonth() - b.getMonth() || a.getDate() - b.getDate()
56
+ );
57
+ }
58
+
59
+ export const isSameDay = (a: Date | null | undefined, b: Date | null | undefined): boolean =>
60
+ a != null && b != null && compareDay(a, b) === 0;
61
+
62
+ export const isSameMonth = (a: Date, b: Date): boolean =>
63
+ a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
64
+
65
+ export const daysInMonth = (year: number, month: number): number =>
66
+ new Date(year, month + 1, 0).getDate();
67
+
68
+ export function addDays(date: Date, amount: number): Date {
69
+ const copy = new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
70
+ copy.setHours(0, 0, 0, 0);
71
+ return copy;
72
+ }
73
+
74
+ /** Keeps the day of the month where the target month is long enough (Jan 31 + 1 → Feb 28). */
75
+ export function addMonths(date: Date, amount: number): Date {
76
+ const year = date.getFullYear();
77
+ const month = date.getMonth() + amount;
78
+ const day = Math.min(date.getDate(), daysInMonth(year, month));
79
+ const copy = new Date(year, month, day);
80
+ copy.setHours(0, 0, 0, 0);
81
+ return copy;
82
+ }
83
+
84
+ export const startOfMonth = (date: Date): Date =>
85
+ new Date(date.getFullYear(), date.getMonth(), 1);
86
+
87
+ export const endOfMonth = (date: Date): Date =>
88
+ new Date(date.getFullYear(), date.getMonth() + 1, 0);
89
+
90
+ export function clampDate(date: Date, min?: Date | null, max?: Date | null): Date {
91
+ if (min && compareDay(date, min) < 0) return startOfDay(min);
92
+ if (max && compareDay(date, max) > 0) return startOfDay(max);
93
+ return date;
94
+ }
95
+
96
+ export function isDayDisabled(date: Date, limits: DateLimits = {}): boolean {
97
+ if (limits.min && compareDay(date, limits.min) < 0) return true;
98
+ if (limits.max && compareDay(date, limits.max) > 0) return true;
99
+ return limits.isDateDisabled?.(date) === true;
100
+ }
101
+
102
+ interface WeekInfoLocale {
103
+ getWeekInfo?(): { firstDay: number };
104
+ weekInfo?: { firstDay: number };
105
+ }
106
+
107
+ /**
108
+ * First day of the week for a locale, 0 = Sunday. Browsers expose this through
109
+ * `Intl.Locale`; where they don't, ISO Monday is used unless `explicit` is given.
110
+ */
111
+ export function resolveWeekStart(locale?: string, explicit?: number): number {
112
+ if (explicit !== undefined) return ((explicit % 7) + 7) % 7;
113
+ try {
114
+ const tag = locale ?? new Intl.DateTimeFormat().resolvedOptions().locale;
115
+ const info = new Intl.Locale(tag) as unknown as WeekInfoLocale;
116
+ const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay;
117
+ if (typeof firstDay === "number") return firstDay % 7;
118
+ } catch {
119
+ // Locale data is unavailable in this runtime; fall through to the ISO default.
120
+ }
121
+ return 1;
122
+ }
123
+
124
+ /** ISO 8601 week number: week 1 is the one holding the first Thursday of the year. */
125
+ export function getISOWeek(date: Date): number {
126
+ const target = startOfDay(date);
127
+ target.setDate(target.getDate() - ((target.getDay() + 6) % 7) + 3);
128
+ const firstThursday = new Date(target.getFullYear(), 0, 4);
129
+ firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3);
130
+ const week = 7 * 24 * 60 * 60 * 1000;
131
+ return 1 + Math.round((target.getTime() - firstThursday.getTime()) / week);
132
+ }
133
+
134
+ export interface BuildMonthOptions extends DateLimits {
135
+ /** 0 = Sunday. Default Monday. */
136
+ weekStartsOn?: number;
137
+ /** Always six week rows, so the popover height never jumps. Default true. */
138
+ fixedWeeks?: boolean;
139
+ /**
140
+ * Injected so a test can pin "today". `null` marks no day at all, which is
141
+ * what a server render and the hydrating render pass (see core/today.ts).
142
+ */
143
+ today?: Date | null;
144
+ }
145
+
146
+ export function buildMonth(
147
+ year: number,
148
+ month: number,
149
+ options: BuildMonthOptions = {},
150
+ ): CalendarMonth {
151
+ const { weekStartsOn = 1, fixedWeeks = true, today = startOfDay(new Date()) } = options;
152
+ const first = new Date(year, month, 1);
153
+ const lead = (first.getDay() - weekStartsOn + 7) % 7;
154
+ const rows = fixedWeeks ? 6 : Math.ceil((lead + daysInMonth(year, month)) / 7);
155
+
156
+ const weeks: CalendarWeek[] = [];
157
+ let cursor = addDays(first, -lead);
158
+ for (let row = 0; row < rows; row++) {
159
+ const days: CalendarDay[] = [];
160
+ for (let column = 0; column < 7; column++) {
161
+ const weekday = cursor.getDay();
162
+ days.push({
163
+ date: cursor,
164
+ day: cursor.getDate(),
165
+ key: toISODate(cursor),
166
+ outside: cursor.getMonth() !== month || cursor.getFullYear() !== year,
167
+ today: today !== null && compareDay(cursor, today) === 0,
168
+ weekend: weekday === 0 || weekday === 6,
169
+ disabled: isDayDisabled(cursor, options),
170
+ });
171
+ cursor = addDays(cursor, 1);
172
+ }
173
+ weeks.push({ weekNumber: getISOWeek(days[0].date), days });
174
+ }
175
+ return { year, month, weeks };
176
+ }
177
+
178
+ /** `count` consecutive months starting at the month of `start`. */
179
+ export function buildMonths(
180
+ start: Date,
181
+ count: number,
182
+ options: BuildMonthOptions = {},
183
+ ): CalendarMonth[] {
184
+ return Array.from({ length: Math.max(1, count) }, (_, index) => {
185
+ const month = addMonths(startOfMonth(start), index);
186
+ return buildMonth(month.getFullYear(), month.getMonth(), options);
187
+ });
188
+ }
189
+
190
+ /** Puts a backwards range the right way round, so dragging in either direction works. */
191
+ export function normalizeRange(range: DateRange): DateRange {
192
+ const { start, end } = range;
193
+ if (start && end && compareDay(start, end) > 0) return { start: end, end: start };
194
+ return range;
195
+ }
196
+
197
+ export function isInRange(date: Date, start: Date | null, end: Date | null): boolean {
198
+ if (!start || !end) return false;
199
+ return compareDay(date, start) >= 0 && compareDay(date, end) <= 0;
200
+ }
201
+
202
+ export interface MoveOptions {
203
+ weekStartsOn?: number;
204
+ /** Swaps the arrow keys, so "next day" stays to the reading-forward side. */
205
+ rtl?: boolean;
206
+ shiftKey?: boolean;
207
+ }
208
+
209
+ /** Where a key press moves the focused day, or null when the key isn't ours. */
210
+ export function moveByKey(date: Date, key: string, options: MoveOptions = {}): Date | null {
211
+ const { weekStartsOn = 1, rtl = false, shiftKey = false } = options;
212
+ const offsetInWeek = (date.getDay() - weekStartsOn + 7) % 7;
213
+ switch (key) {
214
+ case rtl ? "ArrowRight" : "ArrowLeft":
215
+ return addDays(date, -1);
216
+ case rtl ? "ArrowLeft" : "ArrowRight":
217
+ return addDays(date, 1);
218
+ case "ArrowUp":
219
+ return addDays(date, -7);
220
+ case "ArrowDown":
221
+ return addDays(date, 7);
222
+ case "Home":
223
+ return addDays(date, -offsetInWeek);
224
+ case "End":
225
+ return addDays(date, 6 - offsetInWeek);
226
+ case "PageUp":
227
+ return addMonths(date, shiftKey ? -12 : -1);
228
+ case "PageDown":
229
+ return addMonths(date, shiftKey ? 12 : 1);
230
+ default:
231
+ return null;
232
+ }
233
+ }
234
+
235
+ /** Arrow-key movement inside the month and year panels. Returns null for other keys. */
236
+ export function moveInGrid(
237
+ index: number,
238
+ key: string,
239
+ columns: number,
240
+ count: number,
241
+ rtl = false,
242
+ ): number | null {
243
+ const clamp = (next: number) => Math.max(0, Math.min(count - 1, next));
244
+ switch (key) {
245
+ case rtl ? "ArrowRight" : "ArrowLeft":
246
+ return clamp(index - 1);
247
+ case rtl ? "ArrowLeft" : "ArrowRight":
248
+ return clamp(index + 1);
249
+ case "ArrowUp":
250
+ return clamp(index - columns);
251
+ case "ArrowDown":
252
+ return clamp(index + columns);
253
+ case "Home":
254
+ return 0;
255
+ case "End":
256
+ return count - 1;
257
+ default:
258
+ return null;
259
+ }
260
+ }
261
+
262
+ /** The page of years holding `year`, so the year panel pages in steady blocks. */
263
+ export function yearPage(year: number, size = 12): number[] {
264
+ const start = Math.floor(year / size) * size;
265
+ return Array.from({ length: size }, (_, index) => start + index);
266
+ }
@@ -0,0 +1,170 @@
1
+ import { parseISODate } from "./calendar";
2
+
3
+ /** `Intl.DateTimeFormat` is expensive to build, and these are reused every render. */
4
+ const formatters = new Map<string, Intl.DateTimeFormat>();
5
+
6
+ function formatter(locale: string | undefined, options: Intl.DateTimeFormatOptions) {
7
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
8
+ let found = formatters.get(key);
9
+ if (!found) {
10
+ found = new Intl.DateTimeFormat(locale, options);
11
+ formatters.set(key, found);
12
+ }
13
+ return found;
14
+ }
15
+
16
+ export const DEFAULT_DATE_FORMAT: Intl.DateTimeFormatOptions = {
17
+ year: "numeric",
18
+ month: "short",
19
+ day: "numeric",
20
+ };
21
+ export const DEFAULT_INPUT_PLACEHOLDER = "dd/mm/yyyy";
22
+
23
+ export function formatDate(
24
+ date: Date,
25
+ locale?: string,
26
+ options: Intl.DateTimeFormatOptions = DEFAULT_DATE_FORMAT,
27
+ ): string {
28
+ return formatter(locale, options).format(date);
29
+ }
30
+
31
+ export function formatMonthYear(year: number, month: number, locale?: string): string {
32
+ return formatter(locale, { year: "numeric", month: "long" }).format(new Date(year, month, 1));
33
+ }
34
+
35
+ export function monthNames(locale?: string, width: "long" | "short" = "long"): string[] {
36
+ const format = formatter(locale, { month: width });
37
+ return Array.from({ length: 12 }, (_, month) => format.format(new Date(2024, month, 1)));
38
+ }
39
+
40
+ export interface WeekdayName {
41
+ short: string;
42
+ long: string;
43
+ }
44
+
45
+ /** The seven weekday names, ordered from `weekStartsOn`. */
46
+ export function weekdayNames(locale?: string, weekStartsOn = 1): WeekdayName[] {
47
+ const short = formatter(locale, { weekday: "short" });
48
+ const long = formatter(locale, { weekday: "long" });
49
+ // 2024-01-07 was a Sunday, so index 0 lines up with `Date.prototype.getDay`.
50
+ return Array.from({ length: 7 }, (_, offset) => {
51
+ const date = new Date(2024, 0, 7 + ((weekStartsOn + offset) % 7));
52
+ return { short: short.format(date), long: long.format(date) };
53
+ });
54
+ }
55
+
56
+ export type DateField = "day" | "month" | "year";
57
+
58
+ /** The order a locale writes a numeric date in, used for parsing and the placeholder. */
59
+ export function fieldOrder(locale?: string): DateField[] {
60
+ const parts = formatter(locale, {
61
+ year: "numeric",
62
+ month: "2-digit",
63
+ day: "2-digit",
64
+ }).formatToParts(new Date(2024, 10, 22));
65
+ const order = parts
66
+ .map((part) => part.type)
67
+ .filter((type): type is DateField => type === "day" || type === "month" || type === "year");
68
+ return order.length === 3 ? order : ["day", "month", "year"];
69
+ }
70
+
71
+ function separator(locale?: string): string {
72
+ const parts = formatter(locale, {
73
+ year: "numeric",
74
+ month: "2-digit",
75
+ day: "2-digit",
76
+ }).formatToParts(new Date(2024, 10, 22));
77
+ const literal = parts.find((part) => part.type === "literal" && part.value.trim());
78
+ return literal ? literal.value.trim() : "/";
79
+ }
80
+
81
+ /** A hint in the locale's own order, e.g. `dd/mm/yyyy` or `mm/dd/yyyy`. */
82
+ export function inputPlaceholder(locale?: string): string {
83
+ if (!locale) return DEFAULT_INPUT_PLACEHOLDER;
84
+ const hint: Record<DateField, string> = { day: "dd", month: "mm", year: "yyyy" };
85
+ return fieldOrder(locale)
86
+ .map((field) => hint[field])
87
+ .join(separator(locale));
88
+ }
89
+
90
+ /** Two-digit years follow the usual pivot: 00-68 is 2000s, 69-99 is 1900s. */
91
+ function expandYear(year: number): number {
92
+ if (year >= 100) return year;
93
+ return year <= 68 ? 2000 + year : 1900 + year;
94
+ }
95
+
96
+ function build(year: number, month: number, day: number): Date | null {
97
+ if (month < 0 || month > 11 || day < 1 || day > 31) return null;
98
+ const date = new Date(year, month, day);
99
+ date.setHours(0, 0, 0, 0);
100
+ const valid =
101
+ date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
102
+ return valid ? date : null;
103
+ }
104
+
105
+ /** Matches a written month name, long or short, by either being a prefix of the other. */
106
+ function monthFromWords(words: string, locale?: string): number | null {
107
+ const needle = words.toLocaleLowerCase();
108
+ const names = [...monthNames(locale, "long"), ...monthNames(locale, "short")];
109
+ const index = names.findIndex((name) => {
110
+ const candidate = name.toLocaleLowerCase().replace(/\.$/, "");
111
+ return candidate.startsWith(needle) || needle.startsWith(candidate);
112
+ });
113
+ return index === -1 ? null : index % 12;
114
+ }
115
+
116
+ /**
117
+ * Reads what someone typed into the field. ISO is accepted everywhere; anything
118
+ * else is read in the locale's field order, so `03/04/2026` is 3 April in
119
+ * en-GB and 4 March in en-US. Month names work too (`12 Mar 2026`).
120
+ * Missing parts fall back to `reference`. Returns null when it isn't a date.
121
+ */
122
+ export function parseDate(
123
+ text: string,
124
+ locale?: string,
125
+ reference: Date = new Date(),
126
+ ): Date | null {
127
+ const trimmed = text.trim();
128
+ if (!trimmed) return null;
129
+
130
+ const iso = parseISODate(trimmed);
131
+ if (iso) return iso;
132
+
133
+ let month: number | null = null;
134
+ const words = trimmed.match(/\p{L}+/gu);
135
+ if (words) {
136
+ month = monthFromWords(words.join(" "), locale);
137
+ if (month === null) return null;
138
+ }
139
+
140
+ const numbers = (trimmed.match(/\d+/g) ?? []).map(Number);
141
+ if (numbers.length === 0) return null;
142
+
143
+ const order = fieldOrder(locale);
144
+
145
+ if (month !== null) {
146
+ // A month name was given, so the numbers are the day and maybe the year.
147
+ if (numbers.length === 1) {
148
+ return build(reference.getFullYear(), month, numbers[0]);
149
+ }
150
+ const yearFirst = numbers[0] >= 32;
151
+ const year = expandYear(yearFirst ? numbers[0] : numbers[1]);
152
+ const day = yearFirst ? numbers[1] : numbers[0];
153
+ return build(year, month, day);
154
+ }
155
+
156
+ if (numbers.length >= 3) {
157
+ const fields = numbers[0] >= 1000 ? (["year", "month", "day"] as DateField[]) : order;
158
+ const value = (field: DateField) => numbers[fields.indexOf(field)];
159
+ return build(expandYear(value("year")), value("month") - 1, value("day"));
160
+ }
161
+
162
+ if (numbers.length === 2) {
163
+ const dayFirst = order.indexOf("day") < order.indexOf("month");
164
+ const day = dayFirst ? numbers[0] : numbers[1];
165
+ const monthNumber = dayFirst ? numbers[1] : numbers[0];
166
+ return build(reference.getFullYear(), monthNumber - 1, day);
167
+ }
168
+
169
+ return build(reference.getFullYear(), reference.getMonth(), numbers[0]);
170
+ }
@@ -0,0 +1,41 @@
1
+ // Framework-free date helpers: the same calendar building, parsing and
2
+ // formatting the components use, so a server (or a test) can reproduce them.
3
+ export {
4
+ addDays,
5
+ addMonths,
6
+ buildMonth,
7
+ buildMonths,
8
+ clampDate,
9
+ compareDay,
10
+ daysInMonth,
11
+ endOfMonth,
12
+ getISOWeek,
13
+ isDayDisabled,
14
+ isInRange,
15
+ isSameDay,
16
+ isSameMonth,
17
+ moveByKey,
18
+ moveInGrid,
19
+ normalizeRange,
20
+ parseISODate,
21
+ resolveWeekStart,
22
+ startOfDay,
23
+ startOfMonth,
24
+ toDate,
25
+ toISODate,
26
+ yearPage,
27
+ } from "./calendar";
28
+ export type { BuildMonthOptions, MoveOptions } from "./calendar";
29
+ export {
30
+ DEFAULT_DATE_FORMAT,
31
+ fieldOrder,
32
+ formatDate,
33
+ formatMonthYear,
34
+ inputPlaceholder,
35
+ monthNames,
36
+ parseDate,
37
+ weekdayNames,
38
+ } from "./format";
39
+ export type { DateField, WeekdayName } from "./format";
40
+ export { getServerToday, getToday, subscribeToToday } from "./today";
41
+ export type * from "./types";
@@ -0,0 +1,72 @@
1
+ import { addDays, startOfDay } from "./calendar";
2
+
3
+ /**
4
+ * The current local day, as one value every picker on the page shares.
5
+ *
6
+ * It exists as a store rather than a `new Date()` in each component for two
7
+ * reasons. A date read once at mount goes stale: a page left open overnight
8
+ * keeps marking yesterday. And a date read during a server render is a value
9
+ * the browser cannot reproduce — it may be on the other side of midnight, or in
10
+ * another time zone — so rendering it would hand hydration a mismatch.
11
+ *
12
+ * `getServerToday` answers `null`, which React also uses for the client's
13
+ * hydrating render. The markup therefore agrees with the server exactly, no
14
+ * marker either side, and the real day arrives in the re-render straight after
15
+ * hydration. A page with no server render never sees the null: React reads
16
+ * `getToday` on the first pass, so the marker is there from the first paint.
17
+ *
18
+ * One timer serves every subscriber, set for the next local midnight and
19
+ * computed with `addDays` so it survives a daylight-saving change. A machine
20
+ * that slept through midnight fires it late, and the tick then reads the clock
21
+ * rather than trusting the schedule.
22
+ */
23
+
24
+ let current: Date | null = null;
25
+ let timer: ReturnType<typeof setTimeout> | undefined;
26
+ const listeners = new Set<() => void>();
27
+
28
+ const readClock = () => startOfDay(new Date());
29
+
30
+ function schedule(): void {
31
+ clearTimeout(timer);
32
+ timer = undefined;
33
+ if (listeners.size === 0 || current === null) return;
34
+ // A deadline already passed (a long sleep) collapses to zero and catches up.
35
+ timer = setTimeout(tick, Math.max(0, addDays(current, 1).getTime() - Date.now()));
36
+ }
37
+
38
+ function tick(): void {
39
+ const next = readClock();
40
+ if (current === null || next.getTime() !== current.getTime()) {
41
+ current = next;
42
+ for (const listener of listeners) listener();
43
+ }
44
+ schedule();
45
+ }
46
+
47
+ export function subscribeToToday(listener: () => void): () => void {
48
+ listeners.add(listener);
49
+ if (listeners.size === 1) {
50
+ // Nothing was watching the clock, so whatever is cached may be a day old.
51
+ current = readClock();
52
+ schedule();
53
+ }
54
+ return () => {
55
+ listeners.delete(listener);
56
+ if (listeners.size === 0) {
57
+ clearTimeout(timer);
58
+ timer = undefined;
59
+ }
60
+ };
61
+ }
62
+
63
+ /** Today, as a cached value: the same object until the day actually changes. */
64
+ export function getToday(): Date {
65
+ current ??= readClock();
66
+ return current;
67
+ }
68
+
69
+ /** Null on the server and through hydration; see the note above. */
70
+ export function getServerToday(): null {
71
+ return null;
72
+ }