@spatika/react 1.0.0 → 1.2.0

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 (369) hide show
  1. package/AGENTS.md +71 -0
  2. package/README.md +4 -3
  3. package/dist/composites/AccentRule.d.ts +6 -0
  4. package/dist/composites/AccentRule.d.ts.map +1 -0
  5. package/dist/composites/AccentRule.js +6 -0
  6. package/dist/composites/AvailabilityBadge.d.ts +9 -0
  7. package/dist/composites/AvailabilityBadge.d.ts.map +1 -0
  8. package/dist/composites/AvailabilityBadge.js +9 -0
  9. package/dist/composites/Card.d.ts +7 -1
  10. package/dist/composites/Card.d.ts.map +1 -1
  11. package/dist/composites/Card.js +10 -0
  12. package/dist/composites/CareerCard.d.ts +20 -0
  13. package/dist/composites/CareerCard.d.ts.map +1 -0
  14. package/dist/composites/CareerCard.js +17 -0
  15. package/dist/composites/ChartDataGrid.d.ts +33 -0
  16. package/dist/composites/ChartDataGrid.d.ts.map +1 -0
  17. package/dist/composites/ChartDataGrid.js +77 -0
  18. package/dist/composites/ContactLink.d.ts +15 -0
  19. package/dist/composites/ContactLink.d.ts.map +1 -0
  20. package/dist/composites/ContactLink.js +8 -0
  21. package/dist/composites/CoverHero.d.ts +1 -1
  22. package/dist/composites/CoverHero.d.ts.map +1 -1
  23. package/dist/composites/CoverHero.js +2 -2
  24. package/dist/composites/DisplayHeading.d.ts +9 -0
  25. package/dist/composites/DisplayHeading.d.ts.map +1 -0
  26. package/dist/composites/DisplayHeading.js +6 -0
  27. package/dist/composites/EntityMediaCard.d.ts +1 -1
  28. package/dist/composites/EntityMediaCard.d.ts.map +1 -1
  29. package/dist/composites/EntityMediaCard.js +12 -4
  30. package/dist/composites/EventCalendar.d.ts +35 -0
  31. package/dist/composites/EventCalendar.d.ts.map +1 -0
  32. package/dist/composites/EventCalendar.js +339 -0
  33. package/dist/composites/EventEditor.d.ts +22 -0
  34. package/dist/composites/EventEditor.d.ts.map +1 -0
  35. package/dist/composites/EventEditor.js +86 -0
  36. package/dist/composites/EventTimeline.d.ts +24 -0
  37. package/dist/composites/EventTimeline.d.ts.map +1 -0
  38. package/dist/composites/EventTimeline.js +184 -0
  39. package/dist/composites/FloatChip.d.ts +15 -0
  40. package/dist/composites/FloatChip.d.ts.map +1 -0
  41. package/dist/composites/FloatChip.js +7 -0
  42. package/dist/composites/GradientText.d.ts +11 -0
  43. package/dist/composites/GradientText.d.ts.map +1 -0
  44. package/dist/composites/GradientText.js +6 -0
  45. package/dist/composites/IconTile.d.ts +10 -0
  46. package/dist/composites/IconTile.d.ts.map +1 -0
  47. package/dist/composites/IconTile.js +7 -0
  48. package/dist/composites/MapChart.d.ts +24 -0
  49. package/dist/composites/MapChart.d.ts.map +1 -0
  50. package/dist/composites/MapChart.js +58 -0
  51. package/dist/composites/MetaChip.d.ts +9 -0
  52. package/dist/composites/MetaChip.d.ts.map +1 -0
  53. package/dist/composites/MetaChip.js +6 -0
  54. package/dist/composites/PresenceDot.d.ts +10 -0
  55. package/dist/composites/PresenceDot.d.ts.map +1 -0
  56. package/dist/composites/PresenceDot.js +10 -0
  57. package/dist/composites/ProfileHero.d.ts +6 -4
  58. package/dist/composites/ProfileHero.d.ts.map +1 -1
  59. package/dist/composites/ProfileHero.js +8 -8
  60. package/dist/composites/ProjectCard.d.ts +12 -0
  61. package/dist/composites/ProjectCard.d.ts.map +1 -0
  62. package/dist/composites/ProjectCard.js +9 -0
  63. package/dist/composites/ScatterWebGL.d.ts +22 -0
  64. package/dist/composites/ScatterWebGL.d.ts.map +1 -0
  65. package/dist/composites/ScatterWebGL.js +45 -0
  66. package/dist/composites/SectionHeading.d.ts +16 -0
  67. package/dist/composites/SectionHeading.d.ts.map +1 -0
  68. package/dist/composites/SectionHeading.js +12 -0
  69. package/dist/composites/SiteFooter.d.ts +18 -0
  70. package/dist/composites/SiteFooter.d.ts.map +1 -0
  71. package/dist/composites/SiteFooter.js +15 -0
  72. package/dist/composites/SiteNav.d.ts +25 -0
  73. package/dist/composites/SiteNav.d.ts.map +1 -0
  74. package/dist/composites/SiteNav.js +44 -0
  75. package/dist/composites/StatCard.d.ts +7 -1
  76. package/dist/composites/StatCard.d.ts.map +1 -1
  77. package/dist/composites/StatCard.js +5 -2
  78. package/dist/composites/StatusDot.d.ts +1 -1
  79. package/dist/composites/Tag.d.ts +11 -0
  80. package/dist/composites/Tag.d.ts.map +1 -0
  81. package/dist/composites/Tag.js +33 -0
  82. package/dist/composites/Wordmark.d.ts +19 -0
  83. package/dist/composites/Wordmark.d.ts.map +1 -0
  84. package/dist/composites/Wordmark.js +15 -0
  85. package/dist/composites/cartesian-charts.d.ts +138 -0
  86. package/dist/composites/cartesian-charts.d.ts.map +1 -0
  87. package/dist/composites/cartesian-charts.js +401 -0
  88. package/dist/composites/chart-composition.d.ts +139 -0
  89. package/dist/composites/chart-composition.d.ts.map +1 -0
  90. package/dist/composites/chart-composition.js +486 -0
  91. package/dist/composites/chart-ui.d.ts +83 -0
  92. package/dist/composites/chart-ui.d.ts.map +1 -0
  93. package/dist/composites/chart-ui.js +108 -0
  94. package/dist/composites/charts-3d.d.ts +37 -0
  95. package/dist/composites/charts-3d.d.ts.map +1 -0
  96. package/dist/composites/charts-3d.js +121 -0
  97. package/dist/composites/flow-charts.d.ts +89 -0
  98. package/dist/composites/flow-charts.d.ts.map +1 -0
  99. package/dist/composites/flow-charts.js +186 -0
  100. package/dist/composites/radial-charts.d.ts +109 -0
  101. package/dist/composites/radial-charts.d.ts.map +1 -0
  102. package/dist/composites/radial-charts.js +176 -0
  103. package/dist/composites/scheduler-ui.d.ts +43 -0
  104. package/dist/composites/scheduler-ui.d.ts.map +1 -0
  105. package/dist/composites/scheduler-ui.js +71 -0
  106. package/dist/index.d.ts +83 -7
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +74 -7
  109. package/dist/lib/chart-export.d.ts +10 -0
  110. package/dist/lib/chart-export.d.ts.map +1 -0
  111. package/dist/lib/chart-export.js +102 -0
  112. package/dist/lib/charts.d.ts +235 -0
  113. package/dist/lib/charts.d.ts.map +1 -0
  114. package/dist/lib/charts.js +744 -0
  115. package/dist/lib/geo.d.ts +39 -0
  116. package/dist/lib/geo.d.ts.map +1 -0
  117. package/dist/lib/geo.js +101 -0
  118. package/dist/lib/iso.d.ts +21 -0
  119. package/dist/lib/iso.d.ts.map +1 -0
  120. package/dist/lib/iso.js +56 -0
  121. package/dist/lib/rrule.d.ts +20 -0
  122. package/dist/lib/rrule.d.ts.map +1 -0
  123. package/dist/lib/rrule.js +236 -0
  124. package/dist/lib/scheduler.d.ts +152 -0
  125. package/dist/lib/scheduler.d.ts.map +1 -0
  126. package/dist/lib/scheduler.js +501 -0
  127. package/dist/lib/webgl-scatter.d.ts +44 -0
  128. package/dist/lib/webgl-scatter.d.ts.map +1 -0
  129. package/dist/lib/webgl-scatter.js +185 -0
  130. package/dist/primitives/Accordion.d.ts +44 -20
  131. package/dist/primitives/Accordion.d.ts.map +1 -1
  132. package/dist/primitives/Accordion.js +82 -22
  133. package/dist/primitives/AppBar.d.ts +13 -0
  134. package/dist/primitives/AppBar.d.ts.map +1 -0
  135. package/dist/primitives/AppBar.js +27 -0
  136. package/dist/primitives/Autocomplete.d.ts +28 -0
  137. package/dist/primitives/Autocomplete.d.ts.map +1 -0
  138. package/dist/primitives/Autocomplete.js +118 -0
  139. package/dist/primitives/Avatar.d.ts +13 -1
  140. package/dist/primitives/Avatar.d.ts.map +1 -1
  141. package/dist/primitives/Avatar.js +17 -2
  142. package/dist/primitives/Backdrop.d.ts +17 -0
  143. package/dist/primitives/Backdrop.d.ts.map +1 -0
  144. package/dist/primitives/Backdrop.js +17 -0
  145. package/dist/primitives/BottomNavigation.d.ts +23 -0
  146. package/dist/primitives/BottomNavigation.d.ts.map +1 -0
  147. package/dist/primitives/BottomNavigation.js +30 -0
  148. package/dist/primitives/Button.d.ts +4 -4
  149. package/dist/primitives/Button.d.ts.map +1 -1
  150. package/dist/primitives/Button.js +2 -0
  151. package/dist/primitives/ButtonBase.d.ts +14 -0
  152. package/dist/primitives/ButtonBase.d.ts.map +1 -0
  153. package/dist/primitives/ButtonBase.js +13 -0
  154. package/dist/primitives/ButtonGroup.d.ts +25 -0
  155. package/dist/primitives/ButtonGroup.d.ts.map +1 -0
  156. package/dist/primitives/ButtonGroup.js +44 -0
  157. package/dist/primitives/CircularProgress.d.ts +16 -0
  158. package/dist/primitives/CircularProgress.d.ts.map +1 -0
  159. package/dist/primitives/CircularProgress.js +17 -0
  160. package/dist/primitives/ClickAwayListener.d.ts +16 -0
  161. package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
  162. package/dist/primitives/ClickAwayListener.js +33 -0
  163. package/dist/primitives/Collapse.d.ts +19 -0
  164. package/dist/primitives/Collapse.d.ts.map +1 -0
  165. package/dist/primitives/Collapse.js +14 -0
  166. package/dist/primitives/Container.d.ts +13 -0
  167. package/dist/primitives/Container.d.ts.map +1 -0
  168. package/dist/primitives/Container.js +26 -0
  169. package/dist/primitives/Fab.d.ts +22 -0
  170. package/dist/primitives/Fab.d.ts.map +1 -0
  171. package/dist/primitives/Fab.js +42 -0
  172. package/dist/primitives/FormControl.d.ts +27 -0
  173. package/dist/primitives/FormControl.d.ts.map +1 -0
  174. package/dist/primitives/FormControl.js +24 -0
  175. package/dist/primitives/FormControlLabel.d.ts +18 -0
  176. package/dist/primitives/FormControlLabel.d.ts.map +1 -0
  177. package/dist/primitives/FormControlLabel.js +19 -0
  178. package/dist/primitives/Grid.d.ts +32 -0
  179. package/dist/primitives/Grid.d.ts.map +1 -0
  180. package/dist/primitives/Grid.js +50 -0
  181. package/dist/primitives/IconButton.d.ts +22 -0
  182. package/dist/primitives/IconButton.d.ts.map +1 -0
  183. package/dist/primitives/IconButton.js +39 -0
  184. package/dist/primitives/ImageList.d.ts +31 -0
  185. package/dist/primitives/ImageList.d.ts.map +1 -0
  186. package/dist/primitives/ImageList.js +33 -0
  187. package/dist/primitives/InputAdornment.d.ts +28 -0
  188. package/dist/primitives/InputAdornment.d.ts.map +1 -0
  189. package/dist/primitives/InputAdornment.js +13 -0
  190. package/dist/primitives/Link.d.ts +17 -0
  191. package/dist/primitives/Link.d.ts.map +1 -0
  192. package/dist/primitives/Link.js +29 -0
  193. package/dist/primitives/List.d.ts +41 -0
  194. package/dist/primitives/List.d.ts.map +1 -0
  195. package/dist/primitives/List.js +22 -0
  196. package/dist/primitives/Masonry.d.ts +14 -0
  197. package/dist/primitives/Masonry.d.ts.map +1 -0
  198. package/dist/primitives/Masonry.js +15 -0
  199. package/dist/primitives/Menu.d.ts +28 -0
  200. package/dist/primitives/Menu.d.ts.map +1 -0
  201. package/dist/primitives/Menu.js +54 -0
  202. package/dist/primitives/MobileStepper.d.ts +15 -0
  203. package/dist/primitives/MobileStepper.d.ts.map +1 -0
  204. package/dist/primitives/MobileStepper.js +8 -0
  205. package/dist/primitives/Modal.d.ts +18 -0
  206. package/dist/primitives/Modal.d.ts.map +1 -0
  207. package/dist/primitives/Modal.js +29 -0
  208. package/dist/primitives/NoSsr.d.ts +15 -0
  209. package/dist/primitives/NoSsr.d.ts.map +1 -0
  210. package/dist/primitives/NoSsr.js +19 -0
  211. package/dist/primitives/Paper.d.ts +22 -0
  212. package/dist/primitives/Paper.d.ts.map +1 -0
  213. package/dist/primitives/Paper.js +33 -0
  214. package/dist/primitives/Progress.d.ts +6 -2
  215. package/dist/primitives/Progress.d.ts.map +1 -1
  216. package/dist/primitives/Progress.js +8 -3
  217. package/dist/primitives/Snackbar.d.ts +23 -0
  218. package/dist/primitives/Snackbar.d.ts.map +1 -0
  219. package/dist/primitives/Snackbar.js +33 -0
  220. package/dist/primitives/SpeedDial.d.ts +26 -0
  221. package/dist/primitives/SpeedDial.d.ts.map +1 -0
  222. package/dist/primitives/SpeedDial.js +26 -0
  223. package/dist/primitives/Stack.d.ts +15 -0
  224. package/dist/primitives/Stack.d.ts.map +1 -0
  225. package/dist/primitives/Stack.js +45 -0
  226. package/dist/primitives/StarRating.d.ts +19 -1
  227. package/dist/primitives/StarRating.d.ts.map +1 -1
  228. package/dist/primitives/StarRating.js +36 -6
  229. package/dist/primitives/Stepper.d.ts +36 -0
  230. package/dist/primitives/Stepper.d.ts.map +1 -0
  231. package/dist/primitives/Stepper.js +53 -0
  232. package/dist/primitives/TablePagination.d.ts +16 -0
  233. package/dist/primitives/TablePagination.d.ts.map +1 -0
  234. package/dist/primitives/TablePagination.js +12 -0
  235. package/dist/primitives/TextField.d.ts +24 -0
  236. package/dist/primitives/TextField.d.ts.map +1 -0
  237. package/dist/primitives/TextField.js +19 -0
  238. package/dist/primitives/TextareaAutosize.d.ts +11 -0
  239. package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
  240. package/dist/primitives/TextareaAutosize.js +26 -0
  241. package/dist/primitives/ToggleButton.d.ts +28 -0
  242. package/dist/primitives/ToggleButton.d.ts.map +1 -0
  243. package/dist/primitives/ToggleButton.js +56 -0
  244. package/dist/primitives/TransferList.d.ts +21 -0
  245. package/dist/primitives/TransferList.d.ts.map +1 -0
  246. package/dist/primitives/TransferList.js +40 -0
  247. package/dist/primitives/Typography.d.ts +23 -0
  248. package/dist/primitives/Typography.d.ts.map +1 -0
  249. package/dist/primitives/Typography.js +69 -0
  250. package/dist/primitives/transitions.d.ts +29 -0
  251. package/dist/primitives/transitions.d.ts.map +1 -0
  252. package/dist/primitives/transitions.js +52 -0
  253. package/package.json +30 -5
  254. package/skills/spatika-ui/SKILL.md +96 -0
  255. package/src/composites/AccentRule.tsx +12 -0
  256. package/src/composites/AvailabilityBadge.tsx +32 -0
  257. package/src/composites/Card.tsx +51 -1
  258. package/src/composites/CareerCard.tsx +111 -0
  259. package/src/composites/ChartDataGrid.test.tsx +57 -0
  260. package/src/composites/ChartDataGrid.tsx +196 -0
  261. package/src/composites/ContactLink.tsx +66 -0
  262. package/src/composites/CoverHero.test.tsx +38 -0
  263. package/src/composites/CoverHero.tsx +29 -22
  264. package/src/composites/DisplayHeading.tsx +27 -0
  265. package/src/composites/EntityMediaCard.test.tsx +58 -0
  266. package/src/composites/EntityMediaCard.tsx +31 -13
  267. package/src/composites/EventCalendar.tsx +928 -0
  268. package/src/composites/EventEditor.tsx +268 -0
  269. package/src/composites/EventTimeline.tsx +357 -0
  270. package/src/composites/FloatChip.tsx +63 -0
  271. package/src/composites/GradientText.tsx +31 -0
  272. package/src/composites/IconTile.tsx +33 -0
  273. package/src/composites/MapChart.test.tsx +39 -0
  274. package/src/composites/MapChart.tsx +130 -0
  275. package/src/composites/MetaChip.tsx +24 -0
  276. package/src/composites/PresenceDot.tsx +42 -0
  277. package/src/composites/ProfileHero.test.tsx +33 -0
  278. package/src/composites/ProfileHero.tsx +96 -79
  279. package/src/composites/ProjectCard.tsx +65 -0
  280. package/src/composites/ScatterWebGL.test.tsx +25 -0
  281. package/src/composites/ScatterWebGL.tsx +79 -0
  282. package/src/composites/SectionHeading.tsx +61 -0
  283. package/src/composites/SiteFooter.test.tsx +37 -0
  284. package/src/composites/SiteFooter.tsx +79 -0
  285. package/src/composites/SiteNav.test.tsx +46 -0
  286. package/src/composites/SiteNav.tsx +150 -0
  287. package/src/composites/StatCard.test.tsx +30 -0
  288. package/src/composites/StatCard.tsx +36 -1
  289. package/src/composites/Tag.tsx +50 -0
  290. package/src/composites/Wordmark.test.tsx +23 -0
  291. package/src/composites/Wordmark.tsx +46 -0
  292. package/src/composites/cartesian-charts.test.tsx +52 -0
  293. package/src/composites/cartesian-charts.tsx +1287 -0
  294. package/src/composites/chart-composition.test.tsx +53 -0
  295. package/src/composites/chart-composition.tsx +898 -0
  296. package/src/composites/chart-ui.tsx +328 -0
  297. package/src/composites/charts-3d.test.tsx +25 -0
  298. package/src/composites/charts-3d.tsx +318 -0
  299. package/src/composites/charts.test.tsx +254 -0
  300. package/src/composites/chrome.test.tsx +29 -0
  301. package/src/composites/flow-charts.test.tsx +79 -0
  302. package/src/composites/flow-charts.tsx +607 -0
  303. package/src/composites/marketing.test.tsx +192 -0
  304. package/src/composites/radial-charts.test.tsx +74 -0
  305. package/src/composites/radial-charts.tsx +617 -0
  306. package/src/composites/scheduler-ui.tsx +304 -0
  307. package/src/composites/scheduler.test.tsx +194 -0
  308. package/src/index.test.ts +22 -0
  309. package/src/index.ts +300 -5
  310. package/src/lib/chart-export.test.ts +20 -0
  311. package/src/lib/chart-export.ts +99 -0
  312. package/src/lib/charts-platform.test.ts +93 -0
  313. package/src/lib/charts.test.ts +132 -0
  314. package/src/lib/charts.ts +1021 -0
  315. package/src/lib/geo.test.ts +42 -0
  316. package/src/lib/geo.ts +138 -0
  317. package/src/lib/iso.test.ts +22 -0
  318. package/src/lib/iso.ts +102 -0
  319. package/src/lib/rrule.test.ts +53 -0
  320. package/src/lib/rrule.ts +281 -0
  321. package/src/lib/scheduler.test.ts +186 -0
  322. package/src/lib/scheduler.ts +688 -0
  323. package/src/lib/tokens.test.ts +2 -2
  324. package/src/lib/webgl-scatter.test.ts +36 -0
  325. package/src/lib/webgl-scatter.ts +230 -0
  326. package/src/primitives/Accordion.tsx +218 -63
  327. package/src/primitives/AppBar.tsx +39 -0
  328. package/src/primitives/Autocomplete.tsx +236 -0
  329. package/src/primitives/Avatar.tsx +52 -1
  330. package/src/primitives/Backdrop.tsx +48 -0
  331. package/src/primitives/BottomNavigation.tsx +102 -0
  332. package/src/primitives/Button.tsx +3 -0
  333. package/src/primitives/ButtonBase.tsx +34 -0
  334. package/src/primitives/ButtonGroup.tsx +90 -0
  335. package/src/primitives/CircularProgress.tsx +104 -0
  336. package/src/primitives/ClickAwayListener.tsx +46 -0
  337. package/src/primitives/Collapse.tsx +39 -0
  338. package/src/primitives/Container.tsx +40 -0
  339. package/src/primitives/Fab.tsx +62 -0
  340. package/src/primitives/FormControl.tsx +90 -0
  341. package/src/primitives/FormControlLabel.tsx +51 -0
  342. package/src/primitives/Grid.tsx +101 -0
  343. package/src/primitives/IconButton.tsx +59 -0
  344. package/src/primitives/ImageList.test.tsx +48 -0
  345. package/src/primitives/ImageList.tsx +112 -0
  346. package/src/primitives/InputAdornment.tsx +86 -0
  347. package/src/primitives/Link.tsx +48 -0
  348. package/src/primitives/List.tsx +121 -0
  349. package/src/primitives/Masonry.tsx +32 -0
  350. package/src/primitives/Menu.tsx +112 -0
  351. package/src/primitives/MobileStepper.tsx +64 -0
  352. package/src/primitives/Modal.tsx +70 -0
  353. package/src/primitives/NoSsr.tsx +28 -0
  354. package/src/primitives/Paper.tsx +55 -0
  355. package/src/primitives/Progress.tsx +25 -7
  356. package/src/primitives/Snackbar.tsx +83 -0
  357. package/src/primitives/SpeedDial.tsx +110 -0
  358. package/src/primitives/Stack.tsx +88 -0
  359. package/src/primitives/StarRating.tsx +87 -28
  360. package/src/primitives/Stepper.tsx +184 -0
  361. package/src/primitives/TablePagination.tsx +58 -0
  362. package/src/primitives/TextField.tsx +105 -0
  363. package/src/primitives/TextareaAutosize.tsx +49 -0
  364. package/src/primitives/ToggleButton.tsx +148 -0
  365. package/src/primitives/TransferList.tsx +110 -0
  366. package/src/primitives/Typography.tsx +90 -0
  367. package/src/primitives/mui-more.test.tsx +76 -0
  368. package/src/primitives/mui.test.tsx +134 -0
  369. package/src/primitives/transitions.tsx +120 -0
@@ -0,0 +1,304 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import { ChevronDown, ChevronLeft, ChevronRight, Settings } from "lucide-react";
3
+ import { cn } from "../lib/cn";
4
+ import {
5
+ eventColorStyle,
6
+ formatDateRange,
7
+ snapMs,
8
+ startOfDay,
9
+ type CalendarView,
10
+ type NormalizedEvent,
11
+ type SchedulerPreferences,
12
+ type SchedulerResource,
13
+ type TimelineScale,
14
+ } from "../lib/scheduler";
15
+ import { Button } from "../primitives/Button";
16
+ import {
17
+ DropdownMenu,
18
+ DropdownMenuContent,
19
+ DropdownMenuItem,
20
+ DropdownMenuTrigger,
21
+ } from "../primitives/DropdownMenu";
22
+ import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
23
+
24
+ export const CALENDAR_VIEW_LABEL: Record<CalendarView, string> = {
25
+ month: "Month",
26
+ week: "Week",
27
+ day: "Day",
28
+ agenda: "Agenda",
29
+ };
30
+
31
+ export const TIMELINE_SCALE_LABEL: Record<TimelineScale, string> = {
32
+ hours: "Hours",
33
+ days: "Days",
34
+ weeks: "Weeks",
35
+ months: "Months",
36
+ years: "Years",
37
+ };
38
+
39
+ export function SchedulerToolbar({
40
+ title,
41
+ onPrev,
42
+ onNext,
43
+ onToday,
44
+ menuLabel,
45
+ menu,
46
+ trailing,
47
+ }: {
48
+ title: string;
49
+ onPrev: () => void;
50
+ onNext: () => void;
51
+ onToday: () => void;
52
+ menuLabel: string;
53
+ menu: ReactNode;
54
+ trailing?: ReactNode;
55
+ }) {
56
+ return (
57
+ <div
58
+ data-slot="scheduler-toolbar"
59
+ className="flex flex-col gap-3 border-b border-border/50 px-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-4"
60
+ >
61
+ <h2 className="min-w-0 truncate text-xl font-black tracking-tight text-foreground sm:text-2xl">
62
+ {title}
63
+ </h2>
64
+ <div className="flex flex-wrap items-center gap-2">
65
+ <div className="flex items-center gap-1">
66
+ <Button
67
+ type="button"
68
+ variant="ghost"
69
+ size="icon"
70
+ aria-label="Previous"
71
+ onClick={onPrev}
72
+ >
73
+ <ChevronLeft className="size-4" />
74
+ </Button>
75
+ <Button type="button" variant="outline" size="sm" className="uppercase tracking-wide" onClick={onToday}>
76
+ Today
77
+ </Button>
78
+ <Button type="button" variant="ghost" size="icon" aria-label="Next" onClick={onNext}>
79
+ <ChevronRight className="size-4" />
80
+ </Button>
81
+ </div>
82
+ <DropdownMenu>
83
+ <DropdownMenuTrigger asChild>
84
+ <Button type="button" variant="outline" size="sm" className="min-w-[6.5rem] justify-between gap-2 uppercase tracking-wide">
85
+ {menuLabel}
86
+ <ChevronDown className="size-3.5 opacity-70" />
87
+ </Button>
88
+ </DropdownMenuTrigger>
89
+ <DropdownMenuContent align="end">{menu}</DropdownMenuContent>
90
+ </DropdownMenu>
91
+ {trailing}
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ export function SchedulerPreferencesMenu({
98
+ value,
99
+ onChange,
100
+ disabled,
101
+ }: {
102
+ value: Required<SchedulerPreferences>;
103
+ onChange: (next: Required<SchedulerPreferences>) => void;
104
+ disabled?: boolean;
105
+ }) {
106
+ return (
107
+ <DropdownMenu>
108
+ <DropdownMenuTrigger asChild>
109
+ <Button type="button" variant="ghost" size="icon" aria-label="Preferences" disabled={disabled}>
110
+ <Settings className="size-4" />
111
+ </Button>
112
+ </DropdownMenuTrigger>
113
+ <DropdownMenuContent align="end" className="w-52">
114
+ <DropdownMenuItem
115
+ onClick={() => onChange({ ...value, showWeekends: !value.showWeekends })}
116
+ >
117
+ {value.showWeekends ? "Hide weekends" : "Show weekends"}
118
+ </DropdownMenuItem>
119
+ <DropdownMenuItem
120
+ onClick={() => onChange({ ...value, showWeekNumber: !value.showWeekNumber })}
121
+ >
122
+ {value.showWeekNumber ? "Hide week numbers" : "Show week numbers"}
123
+ </DropdownMenuItem>
124
+ <DropdownMenuItem onClick={() => onChange({ ...value, ampm: !value.ampm })}>
125
+ {value.ampm ? "Use 24-hour time" : "Use 12-hour time"}
126
+ </DropdownMenuItem>
127
+ <DropdownMenuItem
128
+ onClick={() =>
129
+ onChange({ ...value, weekStartsOn: value.weekStartsOn === 0 ? 1 : 0 })
130
+ }
131
+ >
132
+ Week starts on {value.weekStartsOn === 0 ? "Monday" : "Sunday"}
133
+ </DropdownMenuItem>
134
+ </DropdownMenuContent>
135
+ </DropdownMenu>
136
+ );
137
+ }
138
+
139
+ export function EventResizeHandle({
140
+ event,
141
+ edge,
142
+ axis,
143
+ hourStart,
144
+ hourEnd,
145
+ onCommit,
146
+ }: {
147
+ event: NormalizedEvent;
148
+ edge: "start" | "end";
149
+ axis: "ns" | "ew";
150
+ hourStart?: number;
151
+ hourEnd?: number;
152
+ onCommit: (event: NormalizedEvent, edge: "start" | "end", instant: Date) => void;
153
+ }) {
154
+ const ns = axis === "ns";
155
+ return (
156
+ <button
157
+ type="button"
158
+ aria-label={edge === "start" ? "Resize start" : "Resize end"}
159
+ className={cn(
160
+ "absolute z-10 touch-none bg-transparent",
161
+ ns ? "inset-x-1 h-1.5 cursor-ns-resize" : "inset-y-1 w-1.5 cursor-ew-resize",
162
+ ns && edge === "start" && "top-0",
163
+ ns && edge === "end" && "bottom-0",
164
+ !ns && edge === "start" && "left-0",
165
+ !ns && edge === "end" && "right-0",
166
+ )}
167
+ onClick={(click) => click.stopPropagation()}
168
+ onPointerDown={(pointer) => {
169
+ pointer.stopPropagation();
170
+ (pointer.currentTarget as HTMLElement).setPointerCapture?.(pointer.pointerId);
171
+ }}
172
+ onPointerUp={(pointer) => {
173
+ pointer.stopPropagation();
174
+ if (ns) {
175
+ const column = (pointer.currentTarget as HTMLElement).closest(
176
+ "[data-scheduler-day]",
177
+ ) as HTMLElement | null;
178
+ if (!column || hourStart == null || hourEnd == null) return;
179
+ const rect = column.getBoundingClientRect();
180
+ const ratio = Math.min(1, Math.max(0, (pointer.clientY - rect.top) / rect.height));
181
+ const ms =
182
+ startOfDay(event.start).getTime() + (hourStart + ratio * (hourEnd - hourStart)) * 3_600_000;
183
+ onCommit(event, edge, new Date(snapMs(ms)));
184
+ return;
185
+ }
186
+ const grid = (pointer.currentTarget as HTMLElement).closest(
187
+ "[data-scheduler-grid]",
188
+ ) as HTMLElement | null;
189
+ if (!grid) return;
190
+ const start = Number(grid.dataset.rangeStart);
191
+ const end = Number(grid.dataset.rangeEnd);
192
+ const rect = grid.getBoundingClientRect();
193
+ const ratio = Math.min(1, Math.max(0, (pointer.clientX - rect.left) / rect.width));
194
+ onCommit(event, edge, new Date(snapMs(start + ratio * (end - start))));
195
+ }}
196
+ />
197
+ );
198
+ }
199
+
200
+
201
+ export function ResourceLegend({
202
+ resources,
203
+ hiddenIds,
204
+ onToggle,
205
+ }: {
206
+ resources: SchedulerResource[];
207
+ hiddenIds: ReadonlySet<string>;
208
+ onToggle: (id: string) => void;
209
+ }) {
210
+ if (resources.length === 0) return null;
211
+ return (
212
+ <div
213
+ data-slot="scheduler-resources"
214
+ className="flex flex-wrap items-center gap-1.5 border-t border-border/50 px-3 py-2.5 sm:px-4"
215
+ >
216
+ <p className="mr-1 text-[10px] font-black uppercase tracking-widest text-muted-foreground">
217
+ Resources
218
+ </p>
219
+ {resources.map((resource) => {
220
+ const active = !hiddenIds.has(resource.id);
221
+ const tone = resource.color ?? "blue";
222
+ return (
223
+ <button
224
+ key={resource.id}
225
+ type="button"
226
+ onClick={() => onToggle(resource.id)}
227
+ aria-pressed={active}
228
+ className={cn(
229
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-semibold transition-colors",
230
+ active
231
+ ? "border-border/60 bg-background/80 text-foreground"
232
+ : "border-transparent bg-muted/30 text-muted-foreground line-through",
233
+ )}
234
+ >
235
+ <span
236
+ className="size-2 rounded-full"
237
+ style={eventColorStyle(tone, "dot")}
238
+ aria-hidden
239
+ />
240
+ {resource.title}
241
+ </button>
242
+ );
243
+ })}
244
+ </div>
245
+ );
246
+ }
247
+
248
+ export function EventPopover({
249
+ event,
250
+ resources,
251
+ children,
252
+ onOpenChange,
253
+ }: {
254
+ event: NormalizedEvent;
255
+ resources: SchedulerResource[];
256
+ children: ReactNode;
257
+ onOpenChange?: (open: boolean) => void;
258
+ }) {
259
+ const resource = resources.find((item) => item.id === event.resourceId);
260
+ return (
261
+ <Popover onOpenChange={onOpenChange}>
262
+ <PopoverTrigger asChild>{children}</PopoverTrigger>
263
+ <PopoverContent align="start" sideOffset={6} className="w-72 p-3">
264
+ <p className="text-sm font-bold text-foreground">{event.title}</p>
265
+ <p className="mt-1 text-xs text-muted-foreground">
266
+ {formatDateRange(event.start, event.end, event.allDay)}
267
+ </p>
268
+ {resource ? (
269
+ <p className="mt-2 inline-flex items-center gap-1.5 text-xs font-semibold text-foreground">
270
+ <span className="size-2 rounded-full" style={eventColorStyle(event.color, "dot")} />
271
+ {resource.title}
272
+ </p>
273
+ ) : null}
274
+ {event.description ? (
275
+ <p className="mt-2 text-xs leading-relaxed text-muted-foreground">{event.description}</p>
276
+ ) : null}
277
+ </PopoverContent>
278
+ </Popover>
279
+ );
280
+ }
281
+
282
+ export function SchedulerShell({
283
+ className,
284
+ slot,
285
+ children,
286
+ ...props
287
+ }: {
288
+ className?: string;
289
+ slot: string;
290
+ children: ReactNode;
291
+ } & HTMLAttributes<HTMLDivElement>) {
292
+ return (
293
+ <div
294
+ data-slot={slot}
295
+ className={cn(
296
+ "relative overflow-hidden rounded-[var(--radius-card)] border border-border/60 bg-background/80 shadow-[var(--shadow-soft)]",
297
+ className,
298
+ )}
299
+ {...props}
300
+ >
301
+ {children}
302
+ </div>
303
+ );
304
+ }
@@ -0,0 +1,194 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { EventCalendar } from "./EventCalendar";
5
+ import { EventTimeline } from "./EventTimeline";
6
+ import type { SchedulerEvent, SchedulerResource } from "../lib/scheduler";
7
+
8
+ const resources: SchedulerResource[] = [
9
+ { id: "work", title: "Work", color: "violet" },
10
+ { id: "health", title: "Health", color: "teal" },
11
+ ];
12
+
13
+ const events: SchedulerEvent[] = [
14
+ {
15
+ id: "run",
16
+ title: "Morning Run",
17
+ start: "2026-08-03T07:00:00",
18
+ end: "2026-08-03T07:45:00",
19
+ resourceId: "health",
20
+ },
21
+ {
22
+ id: "bday",
23
+ title: "Alice's Birthday",
24
+ start: "2026-08-06",
25
+ end: "2026-08-08",
26
+ allDay: true,
27
+ resourceId: "work",
28
+ color: "rose",
29
+ },
30
+ {
31
+ id: "review",
32
+ title: "Design Review",
33
+ start: "2026-08-09T14:00:00",
34
+ end: "2026-08-09T16:00:00",
35
+ resourceId: "work",
36
+ },
37
+ ];
38
+
39
+ describe("EventCalendar", () => {
40
+ it("renders the month heading, weekday labels, and events", () => {
41
+ render(
42
+ <EventCalendar
43
+ defaultDate={new Date(2026, 7, 15)}
44
+ events={events}
45
+ resources={resources}
46
+ locale="en-US"
47
+ />,
48
+ );
49
+ expect(screen.getByRole("heading", { name: /August 2026/i })).toBeInTheDocument();
50
+ expect(screen.getByText(/Sun/)).toBeInTheDocument();
51
+ expect(screen.getByText("Morning Run")).toBeInTheDocument();
52
+ expect(screen.getByText("Alice's Birthday")).toBeInTheDocument();
53
+ expect(screen.getByText("Work")).toBeInTheDocument();
54
+ });
55
+
56
+ it("jumps to today from the toolbar", async () => {
57
+ const user = userEvent.setup();
58
+ const onDateChange = vi.fn();
59
+ render(
60
+ <EventCalendar
61
+ defaultDate={new Date(2025, 0, 1)}
62
+ onDateChange={onDateChange}
63
+ events={events}
64
+ />,
65
+ );
66
+ await user.click(screen.getByRole("button", { name: "Today" }));
67
+ expect(onDateChange).toHaveBeenCalled();
68
+ const next = onDateChange.mock.calls[0][0] as Date;
69
+ const now = new Date();
70
+ expect(next.getFullYear()).toBe(now.getFullYear());
71
+ expect(next.getMonth()).toBe(now.getMonth());
72
+ });
73
+
74
+ it("fires onEventClick for a timed event", async () => {
75
+ const user = userEvent.setup();
76
+ const onEventClick = vi.fn();
77
+ render(
78
+ <EventCalendar
79
+ defaultDate={new Date(2026, 7, 15)}
80
+ events={events}
81
+ resources={resources}
82
+ locale="en-US"
83
+ onEventClick={onEventClick}
84
+ />,
85
+ );
86
+ await user.click(screen.getByText("Morning Run"));
87
+ expect(onEventClick).toHaveBeenCalledWith(expect.objectContaining({ id: "run" }));
88
+ });
89
+
90
+ it("switches to week view from the menu", async () => {
91
+ const user = userEvent.setup();
92
+ render(
93
+ <EventCalendar defaultDate={new Date(2026, 7, 15)} events={events} locale="en-US" />,
94
+ );
95
+ await user.click(screen.getByRole("button", { name: /month/i }));
96
+ await user.click(screen.getByRole("menuitem", { name: "Week" }));
97
+ expect(screen.getByRole("heading", { name: /2026/ })).toBeInTheDocument();
98
+ });
99
+
100
+ it("lists events in agenda view", () => {
101
+ render(
102
+ <EventCalendar
103
+ defaultView="agenda"
104
+ defaultDate={new Date(2026, 7, 3)}
105
+ events={events}
106
+ locale="en-US"
107
+ />,
108
+ );
109
+ expect(screen.getByRole("heading", { name: /Aug 3/ })).toBeInTheDocument();
110
+ expect(screen.getAllByText("No events").length).toBeGreaterThan(0);
111
+ expect(screen.getByText("Morning Run")).toBeInTheDocument();
112
+ expect(screen.getAllByText("Alice's Birthday").length).toBeGreaterThan(0);
113
+ });
114
+
115
+ it("opens the editor and saves title changes", async () => {
116
+ const user = userEvent.setup();
117
+ const onEventChange = vi.fn();
118
+ render(
119
+ <EventCalendar
120
+ defaultDate={new Date(2026, 7, 15)}
121
+ events={events}
122
+ resources={resources}
123
+ locale="en-US"
124
+ onEventChange={onEventChange}
125
+ />,
126
+ );
127
+ await user.click(screen.getByText("Morning Run"));
128
+ const title = screen.getByRole("textbox", { name: /title/i });
129
+ await user.clear(title);
130
+ await user.type(title, "Jog");
131
+ await user.click(screen.getByRole("button", { name: "Save" }));
132
+ expect(onEventChange).toHaveBeenCalledWith(
133
+ expect.objectContaining({ id: "run" }),
134
+ expect.objectContaining({ title: "Jog" }),
135
+ );
136
+ });
137
+
138
+ it("creates an event from a day slot", async () => {
139
+ const user = userEvent.setup();
140
+ const onEventCreate = vi.fn();
141
+ render(
142
+ <EventCalendar
143
+ defaultDate={new Date(2026, 7, 15)}
144
+ events={events}
145
+ locale="en-US"
146
+ onEventCreate={onEventCreate}
147
+ />,
148
+ );
149
+ await user.click(screen.getByRole("button", { name: "15" }));
150
+ expect(screen.getByRole("heading", { name: "New event" })).toBeInTheDocument();
151
+ await user.type(screen.getByRole("textbox", { name: /title/i }), "Focus time");
152
+ await user.click(screen.getByRole("button", { name: "Save" }));
153
+ expect(onEventCreate).toHaveBeenCalledWith(expect.objectContaining({ title: "Focus time" }));
154
+ });
155
+ });
156
+
157
+ describe("EventTimeline", () => {
158
+ const timelineEvents: SchedulerEvent[] = [
159
+ {
160
+ id: "api",
161
+ title: "API V3 Development",
162
+ start: "2026-07-01",
163
+ end: "2026-09-20",
164
+ allDay: true,
165
+ resourceId: "eng",
166
+ },
167
+ {
168
+ id: "ui",
169
+ title: "Mobile App UI/UX",
170
+ start: "2026-08-01",
171
+ end: "2026-10-15",
172
+ allDay: true,
173
+ resourceId: "design",
174
+ },
175
+ ];
176
+ const timelineResources: SchedulerResource[] = [
177
+ { id: "design", title: "Design", color: "rose" },
178
+ { id: "eng", title: "Engineering", color: "blue" },
179
+ ];
180
+
181
+ it("renders resource rows and event bars", () => {
182
+ render(
183
+ <EventTimeline
184
+ defaultDate={new Date(2026, 7, 1)}
185
+ events={timelineEvents}
186
+ resources={timelineResources}
187
+ />,
188
+ );
189
+ expect(screen.getAllByText("Design").length).toBeGreaterThan(0);
190
+ expect(screen.getAllByText("Engineering").length).toBeGreaterThan(0);
191
+ expect(screen.getByText("API V3 Development")).toBeInTheDocument();
192
+ expect(screen.getByText("Mobile App UI/UX")).toBeInTheDocument();
193
+ });
194
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ BarChart,
4
+ BarChart3D,
5
+ Button,
6
+ ChartContainer,
7
+ ChartDataGrid,
8
+ MapChart,
9
+ mercator,
10
+ } from "./index";
11
+
12
+ describe("@spatika/react public exports", () => {
13
+ it("exports primitives, chart composites, and helpers", () => {
14
+ expect(Button.displayName).toBe("Button");
15
+ expect(BarChart).toBeTypeOf("function");
16
+ expect(ChartContainer).toBeTypeOf("function");
17
+ expect(ChartDataGrid).toBeTypeOf("function");
18
+ expect(MapChart).toBeTypeOf("function");
19
+ expect(BarChart3D).toBeTypeOf("function");
20
+ expect(mercator([0, 0])[0]).toBeCloseTo(0.5);
21
+ });
22
+ });