@spatika/react 1.1.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 (311) hide show
  1. package/AGENTS.md +71 -0
  2. package/README.md +4 -3
  3. package/dist/composites/Card.d.ts +7 -1
  4. package/dist/composites/Card.d.ts.map +1 -1
  5. package/dist/composites/Card.js +10 -0
  6. package/dist/composites/ChartDataGrid.d.ts +33 -0
  7. package/dist/composites/ChartDataGrid.d.ts.map +1 -0
  8. package/dist/composites/ChartDataGrid.js +77 -0
  9. package/dist/composites/CoverHero.d.ts +1 -1
  10. package/dist/composites/CoverHero.d.ts.map +1 -1
  11. package/dist/composites/CoverHero.js +2 -2
  12. package/dist/composites/EntityMediaCard.d.ts +1 -1
  13. package/dist/composites/EntityMediaCard.d.ts.map +1 -1
  14. package/dist/composites/EntityMediaCard.js +12 -4
  15. package/dist/composites/EventCalendar.d.ts +35 -0
  16. package/dist/composites/EventCalendar.d.ts.map +1 -0
  17. package/dist/composites/EventCalendar.js +339 -0
  18. package/dist/composites/EventEditor.d.ts +22 -0
  19. package/dist/composites/EventEditor.d.ts.map +1 -0
  20. package/dist/composites/EventEditor.js +86 -0
  21. package/dist/composites/EventTimeline.d.ts +24 -0
  22. package/dist/composites/EventTimeline.d.ts.map +1 -0
  23. package/dist/composites/EventTimeline.js +184 -0
  24. package/dist/composites/MapChart.d.ts +24 -0
  25. package/dist/composites/MapChart.d.ts.map +1 -0
  26. package/dist/composites/MapChart.js +58 -0
  27. package/dist/composites/ProfileHero.d.ts +6 -4
  28. package/dist/composites/ProfileHero.d.ts.map +1 -1
  29. package/dist/composites/ProfileHero.js +8 -8
  30. package/dist/composites/ScatterWebGL.d.ts +22 -0
  31. package/dist/composites/ScatterWebGL.d.ts.map +1 -0
  32. package/dist/composites/ScatterWebGL.js +45 -0
  33. package/dist/composites/SiteFooter.d.ts +1 -1
  34. package/dist/composites/SiteFooter.js +1 -1
  35. package/dist/composites/SiteNav.d.ts +7 -2
  36. package/dist/composites/SiteNav.d.ts.map +1 -1
  37. package/dist/composites/SiteNav.js +9 -5
  38. package/dist/composites/StatCard.d.ts +2 -1
  39. package/dist/composites/StatCard.d.ts.map +1 -1
  40. package/dist/composites/StatCard.js +3 -3
  41. package/dist/composites/StatusDot.d.ts +1 -1
  42. package/dist/composites/Wordmark.d.ts +2 -2
  43. package/dist/composites/Wordmark.d.ts.map +1 -1
  44. package/dist/composites/cartesian-charts.d.ts +138 -0
  45. package/dist/composites/cartesian-charts.d.ts.map +1 -0
  46. package/dist/composites/cartesian-charts.js +401 -0
  47. package/dist/composites/chart-composition.d.ts +139 -0
  48. package/dist/composites/chart-composition.d.ts.map +1 -0
  49. package/dist/composites/chart-composition.js +486 -0
  50. package/dist/composites/chart-ui.d.ts +83 -0
  51. package/dist/composites/chart-ui.d.ts.map +1 -0
  52. package/dist/composites/chart-ui.js +108 -0
  53. package/dist/composites/charts-3d.d.ts +37 -0
  54. package/dist/composites/charts-3d.d.ts.map +1 -0
  55. package/dist/composites/charts-3d.js +121 -0
  56. package/dist/composites/flow-charts.d.ts +89 -0
  57. package/dist/composites/flow-charts.d.ts.map +1 -0
  58. package/dist/composites/flow-charts.js +186 -0
  59. package/dist/composites/radial-charts.d.ts +109 -0
  60. package/dist/composites/radial-charts.d.ts.map +1 -0
  61. package/dist/composites/radial-charts.js +176 -0
  62. package/dist/composites/scheduler-ui.d.ts +43 -0
  63. package/dist/composites/scheduler-ui.d.ts.map +1 -0
  64. package/dist/composites/scheduler-ui.js +71 -0
  65. package/dist/index.d.ts +67 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +57 -7
  68. package/dist/lib/chart-export.d.ts +10 -0
  69. package/dist/lib/chart-export.d.ts.map +1 -0
  70. package/dist/lib/chart-export.js +102 -0
  71. package/dist/lib/charts.d.ts +235 -0
  72. package/dist/lib/charts.d.ts.map +1 -0
  73. package/dist/lib/charts.js +744 -0
  74. package/dist/lib/geo.d.ts +39 -0
  75. package/dist/lib/geo.d.ts.map +1 -0
  76. package/dist/lib/geo.js +101 -0
  77. package/dist/lib/iso.d.ts +21 -0
  78. package/dist/lib/iso.d.ts.map +1 -0
  79. package/dist/lib/iso.js +56 -0
  80. package/dist/lib/rrule.d.ts +20 -0
  81. package/dist/lib/rrule.d.ts.map +1 -0
  82. package/dist/lib/rrule.js +236 -0
  83. package/dist/lib/scheduler.d.ts +152 -0
  84. package/dist/lib/scheduler.d.ts.map +1 -0
  85. package/dist/lib/scheduler.js +501 -0
  86. package/dist/lib/webgl-scatter.d.ts +44 -0
  87. package/dist/lib/webgl-scatter.d.ts.map +1 -0
  88. package/dist/lib/webgl-scatter.js +185 -0
  89. package/dist/primitives/Accordion.d.ts +44 -20
  90. package/dist/primitives/Accordion.d.ts.map +1 -1
  91. package/dist/primitives/Accordion.js +82 -22
  92. package/dist/primitives/AppBar.d.ts +13 -0
  93. package/dist/primitives/AppBar.d.ts.map +1 -0
  94. package/dist/primitives/AppBar.js +27 -0
  95. package/dist/primitives/Autocomplete.d.ts +28 -0
  96. package/dist/primitives/Autocomplete.d.ts.map +1 -0
  97. package/dist/primitives/Autocomplete.js +118 -0
  98. package/dist/primitives/Avatar.d.ts +13 -1
  99. package/dist/primitives/Avatar.d.ts.map +1 -1
  100. package/dist/primitives/Avatar.js +17 -2
  101. package/dist/primitives/Backdrop.d.ts +17 -0
  102. package/dist/primitives/Backdrop.d.ts.map +1 -0
  103. package/dist/primitives/Backdrop.js +17 -0
  104. package/dist/primitives/BottomNavigation.d.ts +23 -0
  105. package/dist/primitives/BottomNavigation.d.ts.map +1 -0
  106. package/dist/primitives/BottomNavigation.js +30 -0
  107. package/dist/primitives/ButtonBase.d.ts +14 -0
  108. package/dist/primitives/ButtonBase.d.ts.map +1 -0
  109. package/dist/primitives/ButtonBase.js +13 -0
  110. package/dist/primitives/ButtonGroup.d.ts +25 -0
  111. package/dist/primitives/ButtonGroup.d.ts.map +1 -0
  112. package/dist/primitives/ButtonGroup.js +44 -0
  113. package/dist/primitives/CircularProgress.d.ts +16 -0
  114. package/dist/primitives/CircularProgress.d.ts.map +1 -0
  115. package/dist/primitives/CircularProgress.js +17 -0
  116. package/dist/primitives/ClickAwayListener.d.ts +16 -0
  117. package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
  118. package/dist/primitives/ClickAwayListener.js +33 -0
  119. package/dist/primitives/Collapse.d.ts +19 -0
  120. package/dist/primitives/Collapse.d.ts.map +1 -0
  121. package/dist/primitives/Collapse.js +14 -0
  122. package/dist/primitives/Container.d.ts +13 -0
  123. package/dist/primitives/Container.d.ts.map +1 -0
  124. package/dist/primitives/Container.js +26 -0
  125. package/dist/primitives/Fab.d.ts +22 -0
  126. package/dist/primitives/Fab.d.ts.map +1 -0
  127. package/dist/primitives/Fab.js +42 -0
  128. package/dist/primitives/FormControl.d.ts +27 -0
  129. package/dist/primitives/FormControl.d.ts.map +1 -0
  130. package/dist/primitives/FormControl.js +24 -0
  131. package/dist/primitives/FormControlLabel.d.ts +18 -0
  132. package/dist/primitives/FormControlLabel.d.ts.map +1 -0
  133. package/dist/primitives/FormControlLabel.js +19 -0
  134. package/dist/primitives/Grid.d.ts +32 -0
  135. package/dist/primitives/Grid.d.ts.map +1 -0
  136. package/dist/primitives/Grid.js +50 -0
  137. package/dist/primitives/IconButton.d.ts +22 -0
  138. package/dist/primitives/IconButton.d.ts.map +1 -0
  139. package/dist/primitives/IconButton.js +39 -0
  140. package/dist/primitives/ImageList.d.ts +31 -0
  141. package/dist/primitives/ImageList.d.ts.map +1 -0
  142. package/dist/primitives/ImageList.js +33 -0
  143. package/dist/primitives/InputAdornment.d.ts +28 -0
  144. package/dist/primitives/InputAdornment.d.ts.map +1 -0
  145. package/dist/primitives/InputAdornment.js +13 -0
  146. package/dist/primitives/Link.d.ts +17 -0
  147. package/dist/primitives/Link.d.ts.map +1 -0
  148. package/dist/primitives/Link.js +29 -0
  149. package/dist/primitives/List.d.ts +41 -0
  150. package/dist/primitives/List.d.ts.map +1 -0
  151. package/dist/primitives/List.js +22 -0
  152. package/dist/primitives/Masonry.d.ts +14 -0
  153. package/dist/primitives/Masonry.d.ts.map +1 -0
  154. package/dist/primitives/Masonry.js +15 -0
  155. package/dist/primitives/Menu.d.ts +28 -0
  156. package/dist/primitives/Menu.d.ts.map +1 -0
  157. package/dist/primitives/Menu.js +54 -0
  158. package/dist/primitives/MobileStepper.d.ts +15 -0
  159. package/dist/primitives/MobileStepper.d.ts.map +1 -0
  160. package/dist/primitives/MobileStepper.js +8 -0
  161. package/dist/primitives/Modal.d.ts +18 -0
  162. package/dist/primitives/Modal.d.ts.map +1 -0
  163. package/dist/primitives/Modal.js +29 -0
  164. package/dist/primitives/NoSsr.d.ts +15 -0
  165. package/dist/primitives/NoSsr.d.ts.map +1 -0
  166. package/dist/primitives/NoSsr.js +19 -0
  167. package/dist/primitives/Paper.d.ts +22 -0
  168. package/dist/primitives/Paper.d.ts.map +1 -0
  169. package/dist/primitives/Paper.js +33 -0
  170. package/dist/primitives/Progress.d.ts +6 -2
  171. package/dist/primitives/Progress.d.ts.map +1 -1
  172. package/dist/primitives/Progress.js +8 -3
  173. package/dist/primitives/Snackbar.d.ts +23 -0
  174. package/dist/primitives/Snackbar.d.ts.map +1 -0
  175. package/dist/primitives/Snackbar.js +33 -0
  176. package/dist/primitives/SpeedDial.d.ts +26 -0
  177. package/dist/primitives/SpeedDial.d.ts.map +1 -0
  178. package/dist/primitives/SpeedDial.js +26 -0
  179. package/dist/primitives/Stack.d.ts +15 -0
  180. package/dist/primitives/Stack.d.ts.map +1 -0
  181. package/dist/primitives/Stack.js +45 -0
  182. package/dist/primitives/StarRating.d.ts +19 -1
  183. package/dist/primitives/StarRating.d.ts.map +1 -1
  184. package/dist/primitives/StarRating.js +36 -6
  185. package/dist/primitives/Stepper.d.ts +36 -0
  186. package/dist/primitives/Stepper.d.ts.map +1 -0
  187. package/dist/primitives/Stepper.js +53 -0
  188. package/dist/primitives/TablePagination.d.ts +16 -0
  189. package/dist/primitives/TablePagination.d.ts.map +1 -0
  190. package/dist/primitives/TablePagination.js +12 -0
  191. package/dist/primitives/TextField.d.ts +24 -0
  192. package/dist/primitives/TextField.d.ts.map +1 -0
  193. package/dist/primitives/TextField.js +19 -0
  194. package/dist/primitives/TextareaAutosize.d.ts +11 -0
  195. package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
  196. package/dist/primitives/TextareaAutosize.js +26 -0
  197. package/dist/primitives/ToggleButton.d.ts +28 -0
  198. package/dist/primitives/ToggleButton.d.ts.map +1 -0
  199. package/dist/primitives/ToggleButton.js +56 -0
  200. package/dist/primitives/TransferList.d.ts +21 -0
  201. package/dist/primitives/TransferList.d.ts.map +1 -0
  202. package/dist/primitives/TransferList.js +40 -0
  203. package/dist/primitives/Typography.d.ts +23 -0
  204. package/dist/primitives/Typography.d.ts.map +1 -0
  205. package/dist/primitives/Typography.js +69 -0
  206. package/dist/primitives/transitions.d.ts +29 -0
  207. package/dist/primitives/transitions.d.ts.map +1 -0
  208. package/dist/primitives/transitions.js +52 -0
  209. package/package.json +28 -4
  210. package/skills/spatika-ui/SKILL.md +96 -0
  211. package/src/composites/Card.tsx +51 -1
  212. package/src/composites/ChartDataGrid.test.tsx +57 -0
  213. package/src/composites/ChartDataGrid.tsx +196 -0
  214. package/src/composites/CoverHero.test.tsx +38 -0
  215. package/src/composites/CoverHero.tsx +29 -22
  216. package/src/composites/EntityMediaCard.test.tsx +58 -0
  217. package/src/composites/EntityMediaCard.tsx +31 -13
  218. package/src/composites/EventCalendar.tsx +928 -0
  219. package/src/composites/EventEditor.tsx +268 -0
  220. package/src/composites/EventTimeline.tsx +357 -0
  221. package/src/composites/MapChart.test.tsx +39 -0
  222. package/src/composites/MapChart.tsx +130 -0
  223. package/src/composites/ProfileHero.test.tsx +33 -0
  224. package/src/composites/ProfileHero.tsx +96 -79
  225. package/src/composites/ScatterWebGL.test.tsx +25 -0
  226. package/src/composites/ScatterWebGL.tsx +79 -0
  227. package/src/composites/SiteFooter.test.tsx +37 -0
  228. package/src/composites/SiteFooter.tsx +1 -1
  229. package/src/composites/SiteNav.test.tsx +46 -0
  230. package/src/composites/SiteNav.tsx +14 -4
  231. package/src/composites/StatCard.test.tsx +30 -0
  232. package/src/composites/StatCard.tsx +4 -0
  233. package/src/composites/Wordmark.test.tsx +23 -0
  234. package/src/composites/Wordmark.tsx +2 -2
  235. package/src/composites/cartesian-charts.test.tsx +52 -0
  236. package/src/composites/cartesian-charts.tsx +1287 -0
  237. package/src/composites/chart-composition.test.tsx +53 -0
  238. package/src/composites/chart-composition.tsx +898 -0
  239. package/src/composites/chart-ui.tsx +328 -0
  240. package/src/composites/charts-3d.test.tsx +25 -0
  241. package/src/composites/charts-3d.tsx +318 -0
  242. package/src/composites/charts.test.tsx +254 -0
  243. package/src/composites/chrome.test.tsx +29 -0
  244. package/src/composites/flow-charts.test.tsx +79 -0
  245. package/src/composites/flow-charts.tsx +607 -0
  246. package/src/composites/marketing.test.tsx +38 -34
  247. package/src/composites/radial-charts.test.tsx +74 -0
  248. package/src/composites/radial-charts.tsx +617 -0
  249. package/src/composites/scheduler-ui.tsx +304 -0
  250. package/src/composites/scheduler.test.tsx +194 -0
  251. package/src/index.test.ts +22 -0
  252. package/src/index.ts +277 -5
  253. package/src/lib/chart-export.test.ts +20 -0
  254. package/src/lib/chart-export.ts +99 -0
  255. package/src/lib/charts-platform.test.ts +93 -0
  256. package/src/lib/charts.test.ts +132 -0
  257. package/src/lib/charts.ts +1021 -0
  258. package/src/lib/geo.test.ts +42 -0
  259. package/src/lib/geo.ts +138 -0
  260. package/src/lib/iso.test.ts +22 -0
  261. package/src/lib/iso.ts +102 -0
  262. package/src/lib/rrule.test.ts +53 -0
  263. package/src/lib/rrule.ts +281 -0
  264. package/src/lib/scheduler.test.ts +186 -0
  265. package/src/lib/scheduler.ts +688 -0
  266. package/src/lib/tokens.test.ts +2 -2
  267. package/src/lib/webgl-scatter.test.ts +36 -0
  268. package/src/lib/webgl-scatter.ts +230 -0
  269. package/src/primitives/Accordion.tsx +218 -63
  270. package/src/primitives/AppBar.tsx +39 -0
  271. package/src/primitives/Autocomplete.tsx +236 -0
  272. package/src/primitives/Avatar.tsx +52 -1
  273. package/src/primitives/Backdrop.tsx +48 -0
  274. package/src/primitives/BottomNavigation.tsx +102 -0
  275. package/src/primitives/ButtonBase.tsx +34 -0
  276. package/src/primitives/ButtonGroup.tsx +90 -0
  277. package/src/primitives/CircularProgress.tsx +104 -0
  278. package/src/primitives/ClickAwayListener.tsx +46 -0
  279. package/src/primitives/Collapse.tsx +39 -0
  280. package/src/primitives/Container.tsx +40 -0
  281. package/src/primitives/Fab.tsx +62 -0
  282. package/src/primitives/FormControl.tsx +90 -0
  283. package/src/primitives/FormControlLabel.tsx +51 -0
  284. package/src/primitives/Grid.tsx +101 -0
  285. package/src/primitives/IconButton.tsx +59 -0
  286. package/src/primitives/ImageList.test.tsx +48 -0
  287. package/src/primitives/ImageList.tsx +112 -0
  288. package/src/primitives/InputAdornment.tsx +86 -0
  289. package/src/primitives/Link.tsx +48 -0
  290. package/src/primitives/List.tsx +121 -0
  291. package/src/primitives/Masonry.tsx +32 -0
  292. package/src/primitives/Menu.tsx +112 -0
  293. package/src/primitives/MobileStepper.tsx +64 -0
  294. package/src/primitives/Modal.tsx +70 -0
  295. package/src/primitives/NoSsr.tsx +28 -0
  296. package/src/primitives/Paper.tsx +55 -0
  297. package/src/primitives/Progress.tsx +25 -7
  298. package/src/primitives/Snackbar.tsx +83 -0
  299. package/src/primitives/SpeedDial.tsx +110 -0
  300. package/src/primitives/Stack.tsx +88 -0
  301. package/src/primitives/StarRating.tsx +87 -28
  302. package/src/primitives/Stepper.tsx +184 -0
  303. package/src/primitives/TablePagination.tsx +58 -0
  304. package/src/primitives/TextField.tsx +105 -0
  305. package/src/primitives/TextareaAutosize.tsx +49 -0
  306. package/src/primitives/ToggleButton.tsx +148 -0
  307. package/src/primitives/TransferList.tsx +110 -0
  308. package/src/primitives/Typography.tsx +90 -0
  309. package/src/primitives/mui-more.test.tsx +76 -0
  310. package/src/primitives/mui.test.tsx +134 -0
  311. package/src/primitives/transitions.tsx +120 -0
@@ -0,0 +1,46 @@
1
+ import * as React from "react";
2
+ import { composeRefs } from "../lib/compose-refs";
3
+
4
+ export type ClickAwayListenerProps = {
5
+ onClickAway: (event: MouseEvent | TouchEvent) => void;
6
+ children: React.ReactElement;
7
+ mouseEvent?: "mousedown" | "mouseup" | "click" | false;
8
+ touchEvent?: "touchstart" | "touchend" | false;
9
+ };
10
+
11
+ /**
12
+ * Invokes `onClickAway` when a pointer event occurs outside the child.
13
+ */
14
+ function ClickAwayListener({
15
+ onClickAway,
16
+ children,
17
+ mouseEvent = "mousedown",
18
+ touchEvent = "touchstart",
19
+ }: ClickAwayListenerProps) {
20
+ const ref = React.useRef<HTMLElement | null>(null);
21
+
22
+ React.useEffect(() => {
23
+ const handler = (event: MouseEvent | TouchEvent) => {
24
+ const node = ref.current;
25
+ const target = event.target as Node | null;
26
+ if (!node || !target || node.contains(target)) return;
27
+ onClickAway(event);
28
+ };
29
+
30
+ if (mouseEvent) document.addEventListener(mouseEvent, handler);
31
+ if (touchEvent) document.addEventListener(touchEvent, handler);
32
+ return () => {
33
+ if (mouseEvent) document.removeEventListener(mouseEvent, handler);
34
+ if (touchEvent) document.removeEventListener(touchEvent, handler);
35
+ };
36
+ }, [onClickAway, mouseEvent, touchEvent]);
37
+
38
+ const childRef = (children as { ref?: React.Ref<HTMLElement> }).ref;
39
+
40
+ return React.cloneElement(children, {
41
+ ref: composeRefs(ref, childRef),
42
+ } as Partial<unknown>);
43
+ }
44
+ ClickAwayListener.displayName = "ClickAwayListener";
45
+
46
+ export { ClickAwayListener };
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ export type CollapseProps = Omit<React.ComponentPropsWithoutRef<"div">, "in"> & {
5
+ /** When true, the child is expanded. */
6
+ in?: boolean;
7
+ /** Keep children mounted while collapsed. Defaults to true so height can animate. */
8
+ unmountOnExit?: boolean;
9
+ };
10
+
11
+ /**
12
+ * Height collapse used by Accordion and other expandable regions.
13
+ * Animates `grid-template-rows` (transform-safe; no `transition-all`).
14
+ */
15
+ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>(
16
+ ({ in: open = false, unmountOnExit = false, className, children, ...props }, ref) => {
17
+ if (unmountOnExit && !open) return null;
18
+
19
+ return (
20
+ <div
21
+ ref={ref}
22
+ data-slot="collapse"
23
+ data-state={open ? "open" : "closed"}
24
+ aria-hidden={!open}
25
+ className={cn(
26
+ "grid transition-[grid-template-rows] duration-300 ease-out",
27
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
28
+ className,
29
+ )}
30
+ {...props}
31
+ >
32
+ <div className="min-h-0 overflow-hidden">{children}</div>
33
+ </div>
34
+ );
35
+ },
36
+ );
37
+ Collapse.displayName = "Collapse";
38
+
39
+ export { Collapse };
@@ -0,0 +1,40 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../lib/cn";
4
+
5
+ const containerVariants = cva("mx-auto w-full", {
6
+ variants: {
7
+ maxWidth: {
8
+ sm: "max-w-screen-sm",
9
+ md: "max-w-screen-md",
10
+ lg: "max-w-screen-lg",
11
+ xl: "max-w-screen-xl",
12
+ full: "max-w-none",
13
+ },
14
+ disableGutters: {
15
+ true: "px-0",
16
+ false: "px-4 sm:px-6",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ maxWidth: "lg",
21
+ disableGutters: false,
22
+ },
23
+ });
24
+
25
+ export type ContainerProps = React.ComponentPropsWithoutRef<"div"> &
26
+ VariantProps<typeof containerVariants>;
27
+
28
+ const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
29
+ ({ className, maxWidth, disableGutters, ...props }, ref) => (
30
+ <div
31
+ ref={ref}
32
+ data-slot="container"
33
+ className={cn(containerVariants({ maxWidth, disableGutters }), className)}
34
+ {...props}
35
+ />
36
+ ),
37
+ );
38
+ Container.displayName = "Container";
39
+
40
+ export { Container, containerVariants };
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { Slot } from "../lib/slot";
4
+ import { cn } from "../lib/cn";
5
+
6
+ const fabVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 font-bold shadow-[0_10px_28px_color-mix(in_srgb,var(--foreground)_16%,transparent)] outline-none transition-[color,background-color,box-shadow,transform] duration-150 focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ circular: "rounded-full",
12
+ extended: "rounded-full px-5",
13
+ },
14
+ color: {
15
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
16
+ glass: "glass border-border/50 text-foreground hover:border-primary/30 hover:text-primary",
17
+ default: "bg-card text-foreground border border-border/50 hover:bg-muted/60",
18
+ },
19
+ size: {
20
+ sm: "size-10 text-sm",
21
+ md: "size-14 text-base",
22
+ lg: "size-16 text-lg",
23
+ },
24
+ },
25
+ compoundVariants: [
26
+ { variant: "extended", size: "sm", className: "h-10 w-auto min-w-10" },
27
+ { variant: "extended", size: "md", className: "h-12 w-auto min-w-12 size-auto" },
28
+ { variant: "extended", size: "lg", className: "h-14 w-auto min-w-14 size-auto" },
29
+ ],
30
+ defaultVariants: {
31
+ variant: "circular",
32
+ color: "primary",
33
+ size: "md",
34
+ },
35
+ },
36
+ );
37
+
38
+ export type FabProps = Omit<React.ComponentPropsWithoutRef<"button">, "color"> &
39
+ VariantProps<typeof fabVariants> & {
40
+ asChild?: boolean;
41
+ };
42
+
43
+ /**
44
+ * Floating action button. Use `variant="extended"` for a labeled FAB.
45
+ */
46
+ const Fab = React.forwardRef<HTMLButtonElement, FabProps>(
47
+ ({ className, variant, color, size, asChild = false, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "button";
49
+ return (
50
+ <Comp
51
+ ref={ref}
52
+ type={asChild ? undefined : "button"}
53
+ data-slot="fab"
54
+ className={cn(fabVariants({ variant, color, size }), className)}
55
+ {...props}
56
+ />
57
+ );
58
+ },
59
+ );
60
+ Fab.displayName = "Fab";
61
+
62
+ export { Fab, fabVariants };
@@ -0,0 +1,90 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+ import { Label } from "./Label";
4
+
5
+ type FormControlContextValue = {
6
+ error?: boolean;
7
+ disabled?: boolean;
8
+ required?: boolean;
9
+ fullWidth?: boolean;
10
+ };
11
+
12
+ const FormControlContext = React.createContext<FormControlContextValue | null>(null);
13
+
14
+ export type FormControlProps = React.ComponentPropsWithoutRef<"div"> & {
15
+ error?: boolean;
16
+ disabled?: boolean;
17
+ required?: boolean;
18
+ fullWidth?: boolean;
19
+ };
20
+
21
+ function FormControl({
22
+ error,
23
+ disabled,
24
+ required,
25
+ fullWidth,
26
+ className,
27
+ ...props
28
+ }: FormControlProps) {
29
+ return (
30
+ <FormControlContext.Provider value={{ error, disabled, required, fullWidth }}>
31
+ <div
32
+ data-slot="form-control"
33
+ data-error={error ? "" : undefined}
34
+ data-disabled={disabled ? "" : undefined}
35
+ className={cn("flex flex-col gap-1.5", fullWidth && "w-full", className)}
36
+ {...props}
37
+ />
38
+ </FormControlContext.Provider>
39
+ );
40
+ }
41
+ FormControl.displayName = "FormControl";
42
+
43
+ function FormLabel({ className, children, ...props }: React.ComponentPropsWithoutRef<"label">) {
44
+ const ctx = React.useContext(FormControlContext);
45
+ return (
46
+ <Label
47
+ data-slot="form-label"
48
+ className={cn(ctx?.error && "text-destructive", className)}
49
+ {...props}
50
+ >
51
+ {children}
52
+ {ctx?.required ? <span className="text-destructive">*</span> : null}
53
+ </Label>
54
+ );
55
+ }
56
+ FormLabel.displayName = "FormLabel";
57
+
58
+ function FormHelperText({ className, ...props }: React.ComponentPropsWithoutRef<"p">) {
59
+ const ctx = React.useContext(FormControlContext);
60
+ return (
61
+ <p
62
+ data-slot="form-helper-text"
63
+ className={cn(
64
+ "text-xs font-medium",
65
+ ctx?.error ? "text-destructive" : "text-muted-foreground",
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ );
71
+ }
72
+ FormHelperText.displayName = "FormHelperText";
73
+
74
+ function FormGroup({
75
+ row,
76
+ className,
77
+ ...props
78
+ }: React.ComponentPropsWithoutRef<"div"> & { row?: boolean }) {
79
+ return (
80
+ <div
81
+ data-slot="form-group"
82
+ role="group"
83
+ className={cn("flex", row ? "flex-row flex-wrap gap-4" : "flex-col gap-2", className)}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+ FormGroup.displayName = "FormGroup";
89
+
90
+ export { FormControl, FormLabel, FormHelperText, FormGroup };
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+ import { Label } from "./Label";
4
+
5
+ export type FormControlLabelProps = Omit<React.ComponentPropsWithoutRef<"label">, "control"> & {
6
+ control: React.ReactElement<{ disabled?: boolean }>;
7
+ label: React.ReactNode;
8
+ labelPlacement?: "end" | "start" | "top" | "bottom";
9
+ disabled?: boolean;
10
+ };
11
+
12
+ const placementClass = {
13
+ end: "flex-row",
14
+ start: "flex-row-reverse",
15
+ top: "flex-col-reverse items-center",
16
+ bottom: "flex-col items-center",
17
+ } as const;
18
+
19
+ /**
20
+ * Pairs a checkbox, radio, or switch with its visible label.
21
+ */
22
+ function FormControlLabel({
23
+ control,
24
+ label,
25
+ labelPlacement = "end",
26
+ disabled,
27
+ className,
28
+ ...props
29
+ }: FormControlLabelProps) {
30
+ const controlDisabled = disabled || control.props.disabled;
31
+
32
+ return (
33
+ <Label
34
+ data-slot="form-control-label"
35
+ data-disabled={controlDisabled ? "" : undefined}
36
+ className={cn(
37
+ "inline-flex w-fit cursor-pointer items-center gap-2",
38
+ placementClass[labelPlacement],
39
+ controlDisabled && "cursor-not-allowed opacity-50",
40
+ className,
41
+ )}
42
+ {...props}
43
+ >
44
+ {React.cloneElement(control, { disabled: controlDisabled })}
45
+ <span className="text-sm font-medium">{label}</span>
46
+ </Label>
47
+ );
48
+ }
49
+ FormControlLabel.displayName = "FormControlLabel";
50
+
51
+ export { FormControlLabel };
@@ -0,0 +1,101 @@
1
+ import * as React from "react";
2
+ import { Slot } from "../lib/slot";
3
+ import { cn } from "../lib/cn";
4
+
5
+ export type BoxProps = React.ComponentPropsWithoutRef<"div"> & {
6
+ component?: React.ElementType;
7
+ asChild?: boolean;
8
+ };
9
+
10
+ /**
11
+ * Generic layout primitive. Prefer Stack / Grid / Paper for structured layouts.
12
+ */
13
+ const Box = React.forwardRef<HTMLDivElement, BoxProps>(
14
+ ({ className, component = "div", asChild = false, ...props }, ref) => {
15
+ const Comp = asChild ? Slot : component;
16
+ return <Comp ref={ref} data-slot="box" className={cn(className)} {...props} />;
17
+ },
18
+ );
19
+ Box.displayName = "Box";
20
+
21
+ const gapClass: Record<number, string> = {
22
+ 0: "gap-0",
23
+ 1: "gap-1",
24
+ 2: "gap-2",
25
+ 3: "gap-3",
26
+ 4: "gap-4",
27
+ 5: "gap-5",
28
+ 6: "gap-6",
29
+ 8: "gap-8",
30
+ };
31
+
32
+ const spanClass: Record<number, string> = {
33
+ 1: "col-span-1",
34
+ 2: "col-span-2",
35
+ 3: "col-span-3",
36
+ 4: "col-span-4",
37
+ 5: "col-span-5",
38
+ 6: "col-span-6",
39
+ 7: "col-span-7",
40
+ 8: "col-span-8",
41
+ 9: "col-span-9",
42
+ 10: "col-span-10",
43
+ 11: "col-span-11",
44
+ 12: "col-span-12",
45
+ };
46
+
47
+ export type GridProps = React.ComponentPropsWithoutRef<"div"> & {
48
+ container?: boolean;
49
+ item?: boolean;
50
+ spacing?: number;
51
+ columns?: 12;
52
+ xs?: number | "auto";
53
+ sm?: number | "auto";
54
+ md?: number | "auto";
55
+ lg?: number | "auto";
56
+ size?: number | "grow";
57
+ };
58
+
59
+ function spanFor(value?: number | "auto" | "grow") {
60
+ if (value == null || value === "auto" || value === "grow") return undefined;
61
+ return spanClass[value] ?? "col-span-12";
62
+ }
63
+
64
+ /**
65
+ * 12-column CSS grid. Use `container` on the parent and breakpoint spans on items.
66
+ */
67
+ function Grid({
68
+ container,
69
+ item: _item,
70
+ spacing = 2,
71
+ columns: _columns = 12,
72
+ xs,
73
+ sm,
74
+ md,
75
+ lg,
76
+ size,
77
+ className,
78
+ ...props
79
+ }: GridProps) {
80
+ const span = spanFor(typeof size === "number" ? size : xs);
81
+ return (
82
+ <div
83
+ data-slot="grid"
84
+ data-container={container ? "" : undefined}
85
+ data-item={_item || !container ? "" : undefined}
86
+ className={cn(
87
+ container && "grid grid-cols-12",
88
+ container && (gapClass[spacing] ?? "gap-2"),
89
+ !container && (span ?? (size === "grow" ? "col-span-12" : "col-span-12")),
90
+ sm && typeof sm === "number" && spanClass[sm]?.replace("col-span", "sm:col-span"),
91
+ md && typeof md === "number" && spanClass[md]?.replace("col-span", "md:col-span"),
92
+ lg && typeof lg === "number" && spanClass[lg]?.replace("col-span", "lg:col-span"),
93
+ className,
94
+ )}
95
+ {...props}
96
+ />
97
+ );
98
+ }
99
+ Grid.displayName = "Grid";
100
+
101
+ export { Box, Grid };
@@ -0,0 +1,59 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { Slot } from "../lib/slot";
4
+ import { cn } from "../lib/cn";
5
+
6
+ const iconButtonVariants = cva(
7
+ "inline-flex shrink-0 items-center justify-center outline-none transition-[color,background-color,border-color,transform] duration-150 focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ ghost: "text-muted-foreground hover:bg-muted/40 hover:text-foreground",
12
+ outline: "border border-border/60 bg-background/80 text-foreground hover:bg-muted/40",
13
+ glass: "glass border-border/50 text-foreground hover:border-primary/30 hover:text-primary",
14
+ filled: "bg-primary text-primary-foreground hover:bg-primary/90",
15
+ },
16
+ size: {
17
+ sm: "size-8 rounded-lg",
18
+ md: "size-10 rounded-xl",
19
+ lg: "size-12 rounded-xl",
20
+ touch: "size-11 rounded-xl",
21
+ },
22
+ shape: {
23
+ rounded: "",
24
+ circular: "rounded-full",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "ghost",
29
+ size: "md",
30
+ shape: "rounded",
31
+ },
32
+ },
33
+ );
34
+
35
+ export type IconButtonProps = React.ComponentPropsWithoutRef<"button"> &
36
+ VariantProps<typeof iconButtonVariants> & {
37
+ asChild?: boolean;
38
+ };
39
+
40
+ /**
41
+ * Compact icon-only control. Prefer {@link HeaderIconButton} inside app chrome.
42
+ */
43
+ const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
44
+ ({ className, variant, size, shape, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button";
46
+ return (
47
+ <Comp
48
+ ref={ref}
49
+ type={asChild ? undefined : "button"}
50
+ data-slot="icon-button"
51
+ className={cn(iconButtonVariants({ variant, size, shape }), className)}
52
+ {...props}
53
+ />
54
+ );
55
+ },
56
+ );
57
+ IconButton.displayName = "IconButton";
58
+
59
+ export { IconButton, iconButtonVariants };
@@ -0,0 +1,48 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { ImageList, ImageListItem, ImageListItemBar } from "./ImageList";
4
+
5
+ describe("ImageList", () => {
6
+ it("lays out items in a full-width grid with overlay titles", () => {
7
+ const { container } = render(
8
+ <ImageList cols={3} rowHeight={148} gap={10}>
9
+ <ImageListItem>
10
+ <img src="/coast.jpg" alt="Coast" />
11
+ <ImageListItemBar title="Coast" subtitle="Pacific" />
12
+ </ImageListItem>
13
+ <ImageListItem>
14
+ <img src="/dawn.jpg" alt="Dawn" />
15
+ <ImageListItemBar title="Dawn" />
16
+ </ImageListItem>
17
+ </ImageList>,
18
+ );
19
+
20
+ const list = container.querySelector("[data-slot='image-list']");
21
+ expect(list).toHaveAttribute("data-variant", "standard");
22
+ expect(list).toHaveClass("w-full");
23
+ expect(list).toHaveStyle({
24
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
25
+ gridAutoRows: "148px",
26
+ });
27
+ expect(screen.getByText("Coast")).toBeInTheDocument();
28
+ expect(screen.getByText("Pacific")).toBeInTheDocument();
29
+ expect(screen.getByRole("img", { name: "Coast" })).toBeInTheDocument();
30
+ const item = container.querySelector("[data-slot='image-list-item']");
31
+ expect(item).not.toHaveStyle({ marginBottom: "8px" });
32
+ });
33
+
34
+ it("switches to masonry columns without a css grid", () => {
35
+ const { container } = render(
36
+ <ImageList variant="masonry" cols={2} gap={12}>
37
+ <ImageListItem>
38
+ <img src="/dusk.jpg" alt="Dusk" />
39
+ </ImageListItem>
40
+ </ImageList>,
41
+ );
42
+ const list = container.querySelector("[data-slot='image-list']");
43
+ expect(list).toHaveAttribute("data-variant", "masonry");
44
+ expect(list).toHaveClass("block");
45
+ expect(list).not.toHaveClass("grid");
46
+ expect(list).toHaveStyle({ columnCount: "2" });
47
+ });
48
+ });
@@ -0,0 +1,112 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ export type ImageListProps = React.ComponentPropsWithoutRef<"ul"> & {
5
+ cols?: number;
6
+ gap?: number;
7
+ variant?: "standard" | "quilted" | "masonry";
8
+ rowHeight?: number;
9
+ };
10
+
11
+ function ImageList({
12
+ cols = 2,
13
+ gap = 8,
14
+ variant = "standard",
15
+ rowHeight = 180,
16
+ className,
17
+ style,
18
+ children,
19
+ ...props
20
+ }: ImageListProps) {
21
+ return (
22
+ <ul
23
+ data-slot="image-list"
24
+ data-variant={variant}
25
+ className={cn(
26
+ "m-0 w-full list-none p-0",
27
+ variant === "masonry"
28
+ ? "block [&>[data-slot=image-list-item]]:mb-2 [&>[data-slot=image-list-item]]:h-auto"
29
+ : "grid",
30
+ className,
31
+ )}
32
+ style={{
33
+ gap,
34
+ ...(variant === "masonry"
35
+ ? { columnCount: cols, columnGap: gap }
36
+ : {
37
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
38
+ gridAutoRows: rowHeight,
39
+ }),
40
+ ...style,
41
+ }}
42
+ {...props}
43
+ >
44
+ {children}
45
+ </ul>
46
+ );
47
+ }
48
+ ImageList.displayName = "ImageList";
49
+
50
+ export type ImageListItemProps = React.ComponentPropsWithoutRef<"li"> & {
51
+ cols?: number;
52
+ rows?: number;
53
+ };
54
+
55
+ function ImageListItem({ cols = 1, rows = 1, className, style, ...props }: ImageListItemProps) {
56
+ return (
57
+ <li
58
+ data-slot="image-list-item"
59
+ className={cn(
60
+ "relative h-full min-h-0 overflow-hidden rounded-xl [&_img]:absolute [&_img]:inset-0 [&_img]:size-full [&_img]:object-cover",
61
+ className,
62
+ )}
63
+ style={{
64
+ gridColumn: `span ${cols}`,
65
+ gridRow: `span ${rows}`,
66
+ breakInside: "avoid",
67
+ ...style,
68
+ }}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+ ImageListItem.displayName = "ImageListItem";
74
+
75
+ export type ImageListItemBarProps = React.ComponentPropsWithoutRef<"div"> & {
76
+ title?: React.ReactNode;
77
+ subtitle?: React.ReactNode;
78
+ actionIcon?: React.ReactNode;
79
+ position?: "bottom" | "top";
80
+ };
81
+
82
+ function ImageListItemBar({
83
+ title,
84
+ subtitle,
85
+ actionIcon,
86
+ position = "bottom",
87
+ className,
88
+ ...props
89
+ }: ImageListItemBarProps) {
90
+ return (
91
+ <div
92
+ data-slot="image-list-item-bar"
93
+ className={cn(
94
+ "absolute inset-x-0 z-10 flex items-end justify-between gap-2 p-3 text-white",
95
+ position === "top"
96
+ ? "top-0 bg-gradient-to-b from-black/70 to-transparent"
97
+ : "bottom-0 bg-gradient-to-t from-black/70 to-transparent",
98
+ className,
99
+ )}
100
+ {...props}
101
+ >
102
+ <div className="min-w-0">
103
+ <div className="truncate text-sm font-bold">{title}</div>
104
+ {subtitle ? <div className="truncate text-xs opacity-80">{subtitle}</div> : null}
105
+ </div>
106
+ {actionIcon}
107
+ </div>
108
+ );
109
+ }
110
+ ImageListItemBar.displayName = "ImageListItemBar";
111
+
112
+ export { ImageList, ImageListItem, ImageListItemBar };