@spatika/react 1.3.1 → 1.5.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 (127) hide show
  1. package/AGENTS.md +6 -3
  2. package/README.md +1 -1
  3. package/dist/composites/EventCalendar.d.ts +13 -2
  4. package/dist/composites/EventCalendar.d.ts.map +1 -1
  5. package/dist/composites/EventCalendar.js +13 -27
  6. package/dist/composites/GlassCard.d.ts +1 -1
  7. package/dist/composites/RichTextEditor.d.ts +1 -4
  8. package/dist/composites/RichTextEditor.d.ts.map +1 -1
  9. package/dist/composites/RichTextEditor.js +12 -19
  10. package/dist/composites/SurfaceCard.d.ts +1 -1
  11. package/dist/composites/Tag.d.ts +2 -2
  12. package/dist/composites/cartesian-charts.d.ts +12 -2
  13. package/dist/composites/cartesian-charts.d.ts.map +1 -1
  14. package/dist/composites/cartesian-charts.js +27 -13
  15. package/dist/composites/chart-composition.d.ts +19 -1
  16. package/dist/composites/chart-composition.d.ts.map +1 -1
  17. package/dist/composites/chart-composition.js +124 -62
  18. package/dist/composites/chart-interaction.d.ts +81 -0
  19. package/dist/composites/chart-interaction.d.ts.map +1 -0
  20. package/dist/composites/chart-interaction.js +56 -0
  21. package/dist/composites/chart-ui.d.ts +8 -13
  22. package/dist/composites/chart-ui.d.ts.map +1 -1
  23. package/dist/composites/chart-ui.js +9 -6
  24. package/dist/composites/flow-charts.d.ts +6 -1
  25. package/dist/composites/flow-charts.d.ts.map +1 -1
  26. package/dist/composites/flow-charts.js +28 -14
  27. package/dist/composites/radial-charts.d.ts +11 -1
  28. package/dist/composites/radial-charts.d.ts.map +1 -1
  29. package/dist/composites/radial-charts.js +33 -14
  30. package/dist/composites/scheduler-ui.d.ts +14 -1
  31. package/dist/composites/scheduler-ui.d.ts.map +1 -1
  32. package/dist/composites/scheduler-ui.js +23 -2
  33. package/dist/index.d.ts +14 -26
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +8 -14
  36. package/dist/lib/breakpoints.d.ts +17 -0
  37. package/dist/lib/breakpoints.d.ts.map +1 -0
  38. package/dist/lib/breakpoints.js +23 -0
  39. package/dist/lib/chips.d.ts +3 -0
  40. package/dist/lib/chips.d.ts.map +1 -1
  41. package/dist/lib/chips.js +10 -0
  42. package/dist/lib/create-theme.d.ts +162 -0
  43. package/dist/lib/create-theme.d.ts.map +1 -0
  44. package/dist/lib/create-theme.js +138 -0
  45. package/dist/lib/scheduler.d.ts +12 -0
  46. package/dist/lib/scheduler.d.ts.map +1 -1
  47. package/dist/lib/scheduler.js +24 -2
  48. package/dist/lib/themes.d.ts +17 -7
  49. package/dist/lib/themes.d.ts.map +1 -1
  50. package/dist/lib/themes.js +22 -8
  51. package/dist/lib/use-media-query.d.ts +9 -0
  52. package/dist/lib/use-media-query.d.ts.map +1 -1
  53. package/dist/lib/use-media-query.js +28 -1
  54. package/dist/primitives/Alert.d.ts +1 -1
  55. package/dist/primitives/Badge.d.ts +2 -2
  56. package/dist/primitives/Button.d.ts +2 -2
  57. package/dist/primitives/Container.d.ts +2 -2
  58. package/dist/primitives/Fab.d.ts +4 -4
  59. package/dist/primitives/Grid.d.ts +2 -1
  60. package/dist/primitives/Grid.d.ts.map +1 -1
  61. package/dist/primitives/Grid.js +10 -2
  62. package/dist/primitives/IconButton.d.ts +2 -2
  63. package/dist/primitives/NativeSelect.d.ts +10 -1
  64. package/dist/primitives/NativeSelect.d.ts.map +1 -1
  65. package/dist/primitives/NativeSelect.js +2 -2
  66. package/dist/primitives/ToggleButton.d.ts +1 -1
  67. package/dist/theme/SpatikaThemeProvider.d.ts +4 -5
  68. package/dist/theme/SpatikaThemeProvider.d.ts.map +1 -1
  69. package/dist/theme/SpatikaThemeProvider.js +28 -13
  70. package/package.json +4 -2
  71. package/skills/spatika-ui/SKILL.md +28 -5
  72. package/src/composites/EventCalendar.test.tsx +293 -0
  73. package/src/composites/EventCalendar.tsx +38 -27
  74. package/src/composites/RichTextEditor.test.tsx +51 -0
  75. package/src/composites/RichTextEditor.tsx +20 -84
  76. package/src/composites/chart-ui.test.tsx +21 -0
  77. package/src/composites/scheduler-ui.tsx +128 -26
  78. package/src/composites/scheduler.test.tsx +0 -219
  79. package/src/index.test.ts +10 -0
  80. package/src/index.ts +148 -65
  81. package/src/lib/breakpoints.test.ts +29 -0
  82. package/src/lib/breakpoints.ts +28 -0
  83. package/src/lib/chips.ts +16 -0
  84. package/src/lib/create-theme.test.ts +79 -0
  85. package/src/lib/create-theme.ts +249 -0
  86. package/src/lib/scheduler.test.ts +26 -0
  87. package/src/lib/scheduler.ts +36 -2
  88. package/src/lib/themes.test.ts +17 -0
  89. package/src/lib/themes.ts +43 -12
  90. package/src/lib/use-media-query.test.tsx +80 -0
  91. package/src/lib/use-media-query.ts +35 -1
  92. package/src/primitives/Grid.test.tsx +55 -0
  93. package/src/primitives/Grid.tsx +13 -3
  94. package/src/primitives/NativeSelect.test.tsx +28 -0
  95. package/src/primitives/NativeSelect.tsx +14 -4
  96. package/src/primitives/mui-more.test.tsx +17 -0
  97. package/src/theme/SpatikaThemeProvider.test.tsx +38 -0
  98. package/src/theme/SpatikaThemeProvider.tsx +43 -15
  99. package/src/composites/ChartDataGrid.test.tsx +0 -57
  100. package/src/composites/ChartDataGrid.tsx +0 -196
  101. package/src/composites/MapChart.test.tsx +0 -39
  102. package/src/composites/MapChart.tsx +0 -130
  103. package/src/composites/ScatterWebGL.test.tsx +0 -25
  104. package/src/composites/ScatterWebGL.tsx +0 -79
  105. package/src/composites/cartesian-charts.test.tsx +0 -52
  106. package/src/composites/cartesian-charts.tsx +0 -1287
  107. package/src/composites/chart-composition.test.tsx +0 -53
  108. package/src/composites/chart-composition.tsx +0 -898
  109. package/src/composites/chart-ui.tsx +0 -328
  110. package/src/composites/charts-3d.test.tsx +0 -25
  111. package/src/composites/charts-3d.tsx +0 -318
  112. package/src/composites/charts.test.tsx +0 -254
  113. package/src/composites/flow-charts.test.tsx +0 -79
  114. package/src/composites/flow-charts.tsx +0 -607
  115. package/src/composites/radial-charts.test.tsx +0 -74
  116. package/src/composites/radial-charts.tsx +0 -617
  117. package/src/lib/chart-export.test.ts +0 -20
  118. package/src/lib/chart-export.ts +0 -99
  119. package/src/lib/charts-platform.test.ts +0 -93
  120. package/src/lib/charts.test.ts +0 -132
  121. package/src/lib/charts.ts +0 -1021
  122. package/src/lib/geo.test.ts +0 -42
  123. package/src/lib/geo.ts +0 -138
  124. package/src/lib/iso.test.ts +0 -22
  125. package/src/lib/iso.ts +0 -102
  126. package/src/lib/webgl-scatter.test.ts +0 -36
  127. package/src/lib/webgl-scatter.ts +0 -230
@@ -19,12 +19,13 @@ export type GridProps = React.ComponentPropsWithoutRef<"div"> & {
19
19
  sm?: number | "auto";
20
20
  md?: number | "auto";
21
21
  lg?: number | "auto";
22
+ xl?: number | "auto";
22
23
  size?: number | "grow";
23
24
  };
24
25
  /**
25
26
  * 12-column CSS grid. Use `container` on the parent and breakpoint spans on items.
26
27
  */
27
- declare function Grid({ container, item: _item, spacing, columns: _columns, xs, sm, md, lg, size, className, ...props }: GridProps): React.JSX.Element;
28
+ declare function Grid({ container, item: _item, spacing, columns: _columns, xs, sm, md, lg, xl, size, className, ...props }: GridProps): React.JSX.Element;
28
29
  declare namespace Grid {
29
30
  var displayName: string;
30
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Grid.d.ts","sourceRoot":"","sources":["../../src/primitives/Grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,GAAG;IAC7D,SAAS,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,GAAG;gBAPK,KAAK,CAAC,WAAW;cACnB,OAAO;wCAWlB,CAAC;AA6BF,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,GAAG;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,EAAE,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAOF;;GAEG;AACH,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,IAAI,EAAE,KAAK,EACX,OAAW,EACX,OAAO,EAAE,QAAa,EACtB,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,EACF,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,SAAS,qBAmBX;kBA/BQ,IAAI;;;AAkCb,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"Grid.d.ts","sourceRoot":"","sources":["../../src/primitives/Grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,GAAG;IAC7D,SAAS,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,GAAG;gBAPK,KAAK,CAAC,WAAW;cACnB,OAAO;wCAWlB,CAAC;AA6BF,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,GAAG;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,EAAE,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAcF;;GAEG;AACH,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,IAAI,EAAE,KAAK,EACX,OAAW,EACX,OAAO,EAAE,QAAa,EACtB,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,EACF,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,SAAS,qBAoBX;kBAjCQ,IAAI;;;AAoCb,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC"}
@@ -39,12 +39,20 @@ function spanFor(value) {
39
39
  return undefined;
40
40
  return spanClass[value] ?? "col-span-12";
41
41
  }
42
+ function prefixedSpan(prefix, value) {
43
+ if (typeof value !== "number")
44
+ return undefined;
45
+ const base = spanClass[value];
46
+ if (!base)
47
+ return undefined;
48
+ return base.replace("col-span", `${prefix}:col-span`);
49
+ }
42
50
  /**
43
51
  * 12-column CSS grid. Use `container` on the parent and breakpoint spans on items.
44
52
  */
45
- function Grid({ container, item: _item, spacing = 2, columns: _columns = 12, xs, sm, md, lg, size, className, ...props }) {
53
+ function Grid({ container, item: _item, spacing = 2, columns: _columns = 12, xs, sm, md, lg, xl, size, className, ...props }) {
46
54
  const span = spanFor(typeof size === "number" ? size : xs);
47
- return (_jsx("div", { "data-slot": "grid", "data-container": container ? "" : undefined, "data-item": _item || !container ? "" : undefined, className: cn(container && "grid grid-cols-12", container && (gapClass[spacing] ?? "gap-2"), !container && (span ?? (size === "grow" ? "col-span-12" : "col-span-12")), sm && typeof sm === "number" && spanClass[sm]?.replace("col-span", "sm:col-span"), md && typeof md === "number" && spanClass[md]?.replace("col-span", "md:col-span"), lg && typeof lg === "number" && spanClass[lg]?.replace("col-span", "lg:col-span"), className), ...props }));
55
+ return (_jsx("div", { "data-slot": "grid", "data-container": container ? "" : undefined, "data-item": _item || !container ? "" : undefined, className: cn(container && "grid grid-cols-12", container && (gapClass[spacing] ?? "gap-2"), !container && (span ?? (size === "grow" ? "col-span-12" : "col-span-12")), prefixedSpan("sm", sm), prefixedSpan("md", md), prefixedSpan("lg", lg), prefixedSpan("xl", xl), className), ...props }));
48
56
  }
49
57
  Grid.displayName = "Grid";
50
58
  export { Box, Grid };
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const iconButtonVariants: (props?: ({
4
4
  variant?: "filled" | "outline" | "ghost" | "glass" | null | undefined;
5
- size?: "touch" | "sm" | "lg" | "md" | null | undefined;
5
+ size?: "touch" | "sm" | "md" | "lg" | null | undefined;
6
6
  shape?: "circular" | "rounded" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  export type IconButtonProps = React.ComponentPropsWithoutRef<"button"> & VariantProps<typeof iconButtonVariants> & {
@@ -13,7 +13,7 @@ export type IconButtonProps = React.ComponentPropsWithoutRef<"button"> & Variant
13
13
  */
14
14
  declare const IconButton: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
15
15
  variant?: "filled" | "outline" | "ghost" | "glass" | null | undefined;
16
- size?: "touch" | "sm" | "lg" | "md" | null | undefined;
16
+ size?: "touch" | "sm" | "md" | "lg" | null | undefined;
17
17
  shape?: "circular" | "rounded" | null | undefined;
18
18
  } & import("class-variance-authority/types").ClassProp) | undefined) => string> & {
19
19
  asChild?: boolean;
@@ -1,7 +1,16 @@
1
1
  import * as React from "react";
2
+ import { type NativeSelectSize } from "../lib/chips";
3
+ export type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
4
+ /**
5
+ * Visual size. `default` stays full-width for forms.
6
+ * `sm` is a compact pill for toolbars. `touch` is a 44px target.
7
+ * Omits the native HTML `size` (visible option rows) to avoid the clash.
8
+ */
9
+ size?: NativeSelectSize;
10
+ };
2
11
  /**
3
12
  * Styled native `<select>` — prefer compound `Select` for custom menus.
4
13
  */
5
- declare const NativeSelect: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>, "ref"> & React.RefAttributes<HTMLSelectElement>>;
14
+ declare const NativeSelect: React.ForwardRefExoticComponent<Omit<NativeSelectProps, "ref"> & React.RefAttributes<HTMLSelectElement>>;
6
15
  export { NativeSelect };
7
16
  //# sourceMappingURL=NativeSelect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NativeSelect.d.ts","sourceRoot":"","sources":["../../src/primitives/NativeSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;GAEG;AACH,QAAA,MAAM,YAAY,kLAWjB,CAAC;AAGF,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"NativeSelect.d.ts","sourceRoot":"","sources":["../../src/primitives/NativeSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7E;;;;OAIG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,YAAY,0GAYjB,CAAC;AAGF,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cn } from "../lib/cn";
4
- import { nativeSelectClass } from "../lib/chips";
4
+ import { nativeSelectSizeClass } from "../lib/chips";
5
5
  /**
6
6
  * Styled native `<select>` — prefer compound `Select` for custom menus.
7
7
  */
8
- const NativeSelect = React.forwardRef(({ className, children, ...props }, ref) => (_jsx("select", { ref: ref, "data-slot": "native-select", className: cn(nativeSelectClass, "mt-0", className), ...props, children: children })));
8
+ const NativeSelect = React.forwardRef(({ className, size = "default", children, ...props }, ref) => (_jsx("select", { ref: ref, "data-slot": "native-select", "data-size": size, className: cn(nativeSelectSizeClass(size), className), ...props, children: children })));
9
9
  NativeSelect.displayName = "NativeSelect";
10
10
  export { NativeSelect };
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const toggleButtonVariants: (props?: ({
4
- size?: "sm" | "lg" | "md" | null | undefined;
4
+ size?: "sm" | "md" | "lg" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  export type ToggleButtonGroupProps = Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "value" | "defaultValue"> & {
7
7
  exclusive?: boolean;
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
+ import { type SpatikaTheme } from "../lib/create-theme";
2
3
  import { type ThemeId } from "../lib/themes";
3
4
  export type SpatikaThemeContextValue = {
4
5
  theme: ThemeId;
@@ -25,12 +26,10 @@ export type SpatikaThemeProviderProps = {
25
26
  /** Optional element to theme instead of (or in addition to) document */
26
27
  target?: HTMLElement | null;
27
28
  className?: string;
29
+ /** Custom themes from `createTheme`. Switch them with `setTheme(id)`. */
30
+ customThemes?: readonly SpatikaTheme[];
28
31
  };
29
- /**
30
- * MUI-style theme provider for Spatika.
31
- * Wraps the app (or a subtree) and applies theme classes / data attributes.
32
- */
33
- export declare function SpatikaThemeProvider({ children, theme: themeProp, defaultTheme, storageKey, onThemeChange, syncDocument, target, className, }: SpatikaThemeProviderProps): import("react").JSX.Element;
32
+ export declare function SpatikaThemeProvider({ children, theme: themeProp, defaultTheme, storageKey, onThemeChange, syncDocument, target, className, customThemes, }: SpatikaThemeProviderProps): import("react").JSX.Element;
34
33
  /** Access current theme and setter from `SpatikaThemeProvider`. */
35
34
  export declare function useSpatikaTheme(): SpatikaThemeContextValue;
36
35
  //# sourceMappingURL=SpatikaThemeProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpatikaThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/SpatikaThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAML,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B,CAAC;AAIF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,SAAS,CAAC;IACpB,uBAAuB;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,KAAK,EAAE,SAAS,EAChB,YAA4B,EAC5B,UAAkB,EAClB,aAAa,EACb,YAAmB,EACnB,MAAM,EACN,SAAS,GACV,EAAE,yBAAyB,+BAyC3B;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,wBAAwB,CAM1D"}
1
+ {"version":3,"file":"SpatikaThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/SpatikaThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EASL,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B,CAAC;AAIF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,SAAS,CAAC;IACpB,uBAAuB;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CACxC,CAAC;AAgBF,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,KAAK,EAAE,SAAS,EAChB,YAA4B,EAC5B,UAAkB,EAClB,aAAa,EACb,YAAmB,EACnB,MAAM,EACN,SAAS,EACT,YAAkC,GACnC,EAAE,yBAAyB,+BA+C3B;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,wBAAwB,CAM1D"}
@@ -1,32 +1,47 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState, } from "react";
3
- import { applyTheme, DEFAULT_THEME, readStoredTheme, THEME_IDS, writeStoredTheme, } from "../lib/themes";
3
+ import { cn } from "../lib/cn";
4
+ import { findCustomTheme, injectCustomThemeStyles, } from "../lib/create-theme";
5
+ import { applyTheme, DEFAULT_THEME, isDarkTheme, isThemeId, readStoredTheme, THEME_IDS, writeStoredTheme, } from "../lib/themes";
4
6
  const SpatikaThemeContext = createContext(null);
7
+ function baseThemeClass(theme, custom) {
8
+ const base = custom?.extends ?? (isThemeId(theme) ? theme : DEFAULT_THEME);
9
+ return base === DEFAULT_THEME ? undefined : base;
10
+ }
5
11
  /**
6
- * MUI-style theme provider for Spatika.
12
+ * Theme provider for Spatika.
7
13
  * Wraps the app (or a subtree) and applies theme classes / data attributes.
8
14
  */
9
- export function SpatikaThemeProvider({ children, theme: themeProp, defaultTheme = DEFAULT_THEME, storageKey = false, onThemeChange, syncDocument = true, target, className, }) {
10
- const [uncontrolled, setUncontrolled] = useState(() => readStoredTheme(storageKey) ?? defaultTheme);
15
+ const EMPTY_CUSTOM_THEMES = [];
16
+ export function SpatikaThemeProvider({ children, theme: themeProp, defaultTheme = DEFAULT_THEME, storageKey = false, onThemeChange, syncDocument = true, target, className, customThemes = EMPTY_CUSTOM_THEMES, }) {
17
+ const customIds = useMemo(() => customThemes.map((item) => item.id), [customThemes]);
18
+ const [uncontrolled, setUncontrolled] = useState(() => readStoredTheme(storageKey, customIds) ?? defaultTheme);
11
19
  const theme = themeProp ?? uncontrolled;
20
+ const custom = findCustomTheme(theme, customThemes);
12
21
  const setTheme = useCallback((next) => {
13
22
  if (themeProp === undefined)
14
23
  setUncontrolled(next);
15
24
  onThemeChange?.(next);
16
25
  }, [onThemeChange, themeProp]);
17
26
  useLayoutEffect(() => {
27
+ injectCustomThemeStyles(customThemes);
18
28
  if (syncDocument)
19
- applyTheme(theme);
29
+ applyTheme(theme, undefined, custom);
20
30
  if (target)
21
- applyTheme(theme, target);
31
+ applyTheme(theme, target, custom);
22
32
  writeStoredTheme(storageKey, theme);
23
- }, [theme, syncDocument, target, storageKey]);
24
- const value = useMemo(() => ({
25
- theme,
26
- setTheme,
27
- themes: THEME_IDS,
28
- }), [theme, setTheme]);
29
- return (_jsx(SpatikaThemeContext.Provider, { value: value, children: _jsx("div", { className: className, "data-spk-theme": theme, "data-slot": "spatika-theme-provider", children: children }) }));
33
+ }, [theme, syncDocument, target, storageKey, custom, customThemes]);
34
+ const value = useMemo(() => {
35
+ const extras = customThemes.map((item) => item.id).filter((id) => !isThemeId(id));
36
+ return {
37
+ theme,
38
+ setTheme,
39
+ themes: extras.length ? [...THEME_IDS, ...extras] : THEME_IDS,
40
+ };
41
+ }, [theme, setTheme, customThemes]);
42
+ const dark = custom ? custom.colorScheme === "dark" : isDarkTheme(theme);
43
+ const themeClass = baseThemeClass(theme, custom);
44
+ return (_jsx(SpatikaThemeContext.Provider, { value: value, children: _jsx("div", { className: cn(className, themeClass), "data-spk-theme": theme, "data-slot": "spatika-theme-provider", style: { colorScheme: dark ? "dark" : "light" }, children: children }) }));
30
45
  }
31
46
  /** Access current theme and setter from `SpatikaThemeProvider`. */
32
47
  export function useSpatikaTheme() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spatika/react",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "license": "MIT",
5
5
  "description": "Spatika UI — glass React primitives and composites for production apps",
6
6
  "homepage": "https://spatika.sweyam.com",
@@ -59,7 +59,9 @@
59
59
  "react-dom": "^18.2.0 || ^19.0.0"
60
60
  },
61
61
  "dependencies": {
62
- "@spatika/tokens": "1.3.1",
62
+ "@spatika/charts": "1.5.0",
63
+ "@spatika/editor": "1.5.0",
64
+ "@spatika/tokens": "1.5.0",
63
65
  "class-variance-authority": "^0.7.1",
64
66
  "clsx": "^2.1.1",
65
67
  "cmdk": "^1.1.1",
@@ -30,12 +30,30 @@ export function App() {
30
30
 
31
31
  - Import `@spatika/tokens/styles.css` before any component
32
32
  - Wrap the tree in `SpatikaThemeProvider`
33
- - Themes: `mukta` | `neelam` | `usha` | `sandhya`
33
+ - Themes: `mukta` | `neelam` | `usha` | `sandhya`. Brand palettes: `createTheme` + `customThemes`
34
34
  - Prefer composites over restyling primitives
35
35
  - Token CSS variables only — no hardcoded hex
36
36
  - `size="touch"` on mobile primary actions
37
37
  - Never `transition-all` on glass
38
38
 
39
+ ## Customize
40
+
41
+ ```tsx
42
+ import { Button, SpatikaThemeProvider, createTheme } from "@spatika/react";
43
+
44
+ const brand = createTheme({
45
+ id: "brand",
46
+ extends: "mukta",
47
+ palette: { primary: "#7c3aed", primaryForeground: "#ffffff", ring: "#7c3aed" },
48
+ });
49
+
50
+ <SpatikaThemeProvider defaultTheme="brand" customThemes={[brand]}>
51
+ <Button>Brand</Button>
52
+ </SpatikaThemeProvider>
53
+ ```
54
+
55
+ Breakpoints: `BREAKPOINTS` (`xs` 0 / `sm` 640 / `md` 768 / `lg` 1024 / `xl` 1280). `Grid` `xs`–`xl`, `useIsMobile()`, `useBreakpoint()`. Guide: https://spatika.sweyam.com/docs/customize.md
56
+
39
57
  ## Pick a component
40
58
 
41
59
  | Need | Use |
@@ -51,9 +69,13 @@ export function App() {
51
69
  | Empty | `EmptyState` |
52
70
  | Modal | `Dialog` |
53
71
  | Toasts | `Toaster` + `useToast` |
54
- | Chart | `BarChart` / `LineChart` / `ChartContainer` |
72
+ | Chart | `BarChart` / `LineChart` / `ChartContainer` from `@spatika/charts` |
55
73
  | Calendar | `EventCalendar` |
56
74
 
75
+ Charts: install `@spatika/charts` (re-exported from `@spatika/react`). `onItemClick` for mark navigation, `syncId` to link hover across plots, `itemColors` for per-bar tints (inline fill so theme CSS cannot override), `renderTooltip` / `renderMark` for host chrome, `referenceLines` / `referenceAreas` for capacity guides, `fillHeight` or `ResponsiveContainer` when the parent sets height. Pie radii accept `"42%"`. Horizontal bars size the left gutter from category labels. Line/area series accept `strokeDasharray` and `connectNulls`. `yAxis.valueFormatter` formats ticks; `yAxis.reversed` flips the domain. `xAxis.tickAngle` rotates category labels. `stackOffset="expand"` is 100% stacked. `legendPosition`, `loading`, and `emptyText` cover layout and fetch states. Bar charts accept `showBarBackground`. Pie `labelLine` draws outside labels. SparkLineChart hover takes optional `labels`.
76
+
77
+ Prefer `toolbarDensity="compact"` and `showDateJump` for a dense product toolbar. `NativeSelect size="sm"` is the compact pill. `onVisibleRangeChange` is the fetch window (`end` exclusive). Use `showEventEditor={false}` when the host owns create/edit.
78
+
57
79
  ## App shell
58
80
 
59
81
  ```tsx
@@ -91,6 +113,7 @@ import { Home, Search, User } from "lucide-react";
91
113
 
92
114
  ## Next
93
115
 
94
- 1. Fetch the component markdown: `https://spatika.sweyam.com/docs/{slug}.md`
95
- 2. Read prop types from `node_modules/@spatika/react/dist/index.d.ts`
96
- 3. Icons in examples use `lucide-react`
116
+ 1. Fetch the customize guide: `https://spatika.sweyam.com/docs/customize.md`
117
+ 2. Fetch the component markdown: `https://spatika.sweyam.com/docs/{slug}.md`
118
+ 3. Read prop types from `node_modules/@spatika/react/dist/index.d.ts`
119
+ 4. Icons in examples use `lucide-react`
@@ -0,0 +1,293 @@
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 type { SchedulerEvent, SchedulerResource } from "../lib/scheduler";
6
+
7
+ const resources: SchedulerResource[] = [
8
+ { id: "work", title: "Work", color: "violet" },
9
+ { id: "health", title: "Health", color: "teal" },
10
+ ];
11
+
12
+ const events: SchedulerEvent[] = [
13
+ {
14
+ id: "run",
15
+ title: "Morning Run",
16
+ start: "2026-08-03T07:00:00",
17
+ end: "2026-08-03T07:45:00",
18
+ resourceId: "health",
19
+ },
20
+ {
21
+ id: "bday",
22
+ title: "Alice's Birthday",
23
+ start: "2026-08-06",
24
+ end: "2026-08-08",
25
+ allDay: true,
26
+ resourceId: "work",
27
+ color: "rose",
28
+ },
29
+ {
30
+ id: "review",
31
+ title: "Design Review",
32
+ start: "2026-08-09T14:00:00",
33
+ end: "2026-08-09T16:00:00",
34
+ resourceId: "work",
35
+ },
36
+ ];
37
+
38
+ describe("EventCalendar", () => {
39
+ it("renders the month heading, weekday labels, and events", () => {
40
+ render(
41
+ <EventCalendar
42
+ defaultDate={new Date(2026, 7, 15)}
43
+ events={events}
44
+ resources={resources}
45
+ locale="en-US"
46
+ />,
47
+ );
48
+ expect(screen.getByRole("heading", { name: /August 2026/i })).toBeInTheDocument();
49
+ expect(screen.getByText(/Sun/)).toBeInTheDocument();
50
+ expect(screen.getByText("Morning Run")).toBeInTheDocument();
51
+ expect(screen.getByText("Alice's Birthday")).toBeInTheDocument();
52
+ expect(screen.getByText("Work")).toBeInTheDocument();
53
+ });
54
+
55
+ it("jumps to today from the toolbar", async () => {
56
+ const user = userEvent.setup();
57
+ const onDateChange = vi.fn();
58
+ render(
59
+ <EventCalendar
60
+ defaultDate={new Date(2025, 0, 1)}
61
+ onDateChange={onDateChange}
62
+ events={events}
63
+ />,
64
+ );
65
+ await user.click(screen.getByRole("button", { name: "Today" }));
66
+ expect(onDateChange).toHaveBeenCalled();
67
+ const next = onDateChange.mock.calls[0][0] as Date;
68
+ const now = new Date();
69
+ expect(next.getFullYear()).toBe(now.getFullYear());
70
+ expect(next.getMonth()).toBe(now.getMonth());
71
+ });
72
+
73
+ it("fires onEventClick for a timed event", async () => {
74
+ const user = userEvent.setup();
75
+ const onEventClick = vi.fn();
76
+ render(
77
+ <EventCalendar
78
+ defaultDate={new Date(2026, 7, 15)}
79
+ events={events}
80
+ resources={resources}
81
+ locale="en-US"
82
+ onEventClick={onEventClick}
83
+ />,
84
+ );
85
+ await user.click(screen.getByText("Morning Run"));
86
+ expect(onEventClick).toHaveBeenCalledWith(expect.objectContaining({ id: "run" }));
87
+ });
88
+
89
+ it("switches to week view from the menu", async () => {
90
+ const user = userEvent.setup();
91
+ render(
92
+ <EventCalendar defaultDate={new Date(2026, 7, 15)} events={events} locale="en-US" />,
93
+ );
94
+ await user.click(screen.getByRole("button", { name: /month/i }));
95
+ await user.click(screen.getByRole("menuitem", { name: "Week" }));
96
+ expect(screen.getByRole("heading", { name: /2026/ })).toBeInTheDocument();
97
+ });
98
+
99
+ it("lists events in agenda view", () => {
100
+ render(
101
+ <EventCalendar
102
+ defaultView="agenda"
103
+ defaultDate={new Date(2026, 7, 3)}
104
+ events={events}
105
+ locale="en-US"
106
+ />,
107
+ );
108
+ expect(screen.getByRole("heading", { name: /Aug 3/ })).toBeInTheDocument();
109
+ expect(screen.getAllByText("No events").length).toBeGreaterThan(0);
110
+ expect(screen.getByText("Morning Run")).toBeInTheDocument();
111
+ expect(screen.getAllByText("Alice's Birthday").length).toBeGreaterThan(0);
112
+ });
113
+
114
+ it("opens the editor and saves title changes", async () => {
115
+ const user = userEvent.setup();
116
+ const onEventChange = vi.fn();
117
+ render(
118
+ <EventCalendar
119
+ defaultDate={new Date(2026, 7, 15)}
120
+ events={events}
121
+ resources={resources}
122
+ locale="en-US"
123
+ onEventChange={onEventChange}
124
+ />,
125
+ );
126
+ await user.click(screen.getByText("Morning Run"));
127
+ const title = screen.getByRole("textbox", { name: /title/i });
128
+ await user.clear(title);
129
+ await user.type(title, "Jog");
130
+ await user.click(screen.getByRole("button", { name: "Save" }));
131
+ expect(onEventChange).toHaveBeenCalledWith(
132
+ expect.objectContaining({ id: "run" }),
133
+ expect.objectContaining({ title: "Jog" }),
134
+ );
135
+ });
136
+
137
+ it("creates an event from a day slot", async () => {
138
+ const user = userEvent.setup();
139
+ const onEventCreate = vi.fn();
140
+ render(
141
+ <EventCalendar
142
+ defaultDate={new Date(2026, 7, 15)}
143
+ events={events}
144
+ locale="en-US"
145
+ onEventCreate={onEventCreate}
146
+ />,
147
+ );
148
+ await user.click(screen.getByRole("button", { name: "15" }));
149
+ expect(screen.getByRole("heading", { name: "New event" })).toBeInTheDocument();
150
+ await user.type(screen.getByRole("textbox", { name: /title/i }), "Focus time");
151
+ await user.click(screen.getByRole("button", { name: "Save" }));
152
+ expect(onEventCreate).toHaveBeenCalledWith(expect.objectContaining({ title: "Focus time" }));
153
+ });
154
+
155
+ it("skips the built-in editor when showEventEditor is false", async () => {
156
+ const user = userEvent.setup();
157
+ const onEventClick = vi.fn();
158
+ const onSlotClick = vi.fn();
159
+ render(
160
+ <EventCalendar
161
+ defaultDate={new Date(2026, 7, 15)}
162
+ events={events}
163
+ resources={resources}
164
+ locale="en-US"
165
+ showEventEditor={false}
166
+ onEventClick={onEventClick}
167
+ onSlotClick={onSlotClick}
168
+ />,
169
+ );
170
+ await user.click(screen.getByText("Morning Run"));
171
+ expect(onEventClick).toHaveBeenCalledWith(expect.objectContaining({ id: "run" }));
172
+ expect(screen.queryByRole("heading", { name: /event/i })).not.toBeInTheDocument();
173
+
174
+ await user.click(screen.getByRole("button", { name: "15" }));
175
+ expect(onSlotClick).toHaveBeenCalled();
176
+ expect(screen.queryByRole("heading", { name: "New event" })).not.toBeInTheDocument();
177
+ });
178
+
179
+ it("renders custom event chips and toolbar", async () => {
180
+ const user = userEvent.setup();
181
+ const onDateChange = vi.fn();
182
+ render(
183
+ <EventCalendar
184
+ defaultDate={new Date(2026, 7, 15)}
185
+ events={events}
186
+ resources={resources}
187
+ locale="en-US"
188
+ showEventEditor={false}
189
+ showPreferences={false}
190
+ toolbarTrailing={<button type="button">Jump year</button>}
191
+ renderEvent={(event) => <span>★ {event.title}</span>}
192
+ renderToolbar={({ title, onToday }) => (
193
+ <div>
194
+ <p>{title}</p>
195
+ <button type="button" onClick={onToday}>
196
+ Jump today
197
+ </button>
198
+ </div>
199
+ )}
200
+ onDateChange={onDateChange}
201
+ />,
202
+ );
203
+ expect(screen.getByText("★ Morning Run")).toBeInTheDocument();
204
+ expect(screen.queryByRole("button", { name: "Preferences" })).not.toBeInTheDocument();
205
+ expect(screen.queryByRole("button", { name: "Jump year" })).not.toBeInTheDocument();
206
+ await user.click(screen.getByRole("button", { name: "Jump today" }));
207
+ expect(onDateChange).toHaveBeenCalled();
208
+ });
209
+
210
+ it("keeps toolbarTrailing on the default toolbar", () => {
211
+ render(
212
+ <EventCalendar
213
+ defaultDate={new Date(2026, 7, 15)}
214
+ events={events}
215
+ locale="en-US"
216
+ showPreferences={false}
217
+ toolbarTrailing={<button type="button">Jump year</button>}
218
+ />,
219
+ );
220
+ expect(screen.getByRole("button", { name: "Jump year" })).toBeInTheDocument();
221
+ expect(screen.getByRole("heading", { name: /August 2026/i })).toBeInTheDocument();
222
+ });
223
+
224
+ it("renders a compact toolbar with equal month and year jumps", () => {
225
+ render(
226
+ <EventCalendar
227
+ defaultDate={new Date(2026, 7, 15)}
228
+ events={events}
229
+ locale="en-US"
230
+ showPreferences={false}
231
+ toolbarDensity="compact"
232
+ showDateJump
233
+ />,
234
+ );
235
+ expect(document.querySelector('[data-density="compact"]')).toBeTruthy();
236
+ expect(screen.getByLabelText("Month")).toHaveAttribute("data-size", "sm");
237
+ expect(screen.getByLabelText("Year")).toHaveAttribute("data-size", "sm");
238
+ expect(screen.getByLabelText("Month")).toHaveValue("7");
239
+ expect(screen.getByLabelText("Year")).toHaveValue("2026");
240
+ });
241
+
242
+ it("keeps adjacent-month day numbers at full muted contrast", () => {
243
+ render(
244
+ <EventCalendar
245
+ defaultDate={new Date(2026, 7, 15)}
246
+ events={[]}
247
+ locale="en-US"
248
+ showPreferences={false}
249
+ />,
250
+ );
251
+ const julyDay = document.querySelector('[data-scheduler-day="2026-07-26"] button');
252
+ expect(julyDay).toBeTruthy();
253
+ expect(julyDay?.className).toMatch(/text-muted-foreground/);
254
+ expect(julyDay?.className).not.toMatch(/\/70/);
255
+ });
256
+
257
+ it("sizes month event chips for touch-friendly targets", () => {
258
+ render(
259
+ <EventCalendar
260
+ defaultDate={new Date(2026, 7, 15)}
261
+ events={events}
262
+ locale="en-US"
263
+ showPreferences={false}
264
+ showEventEditor={false}
265
+ />,
266
+ );
267
+ const birthday = screen.getByRole("button", { name: "Alice's Birthday" });
268
+ expect(birthday.className).toMatch(/min-h-6/);
269
+ const timed = screen.getByRole("button", { name: /Morning Run/i });
270
+ expect(timed.className).toMatch(/min-h-6/);
271
+ });
272
+
273
+ it("reports the visible range for week view", () => {
274
+ const onVisibleRangeChange = vi.fn();
275
+ render(
276
+ <EventCalendar
277
+ defaultDate={new Date(2026, 7, 15)}
278
+ defaultView="week"
279
+ locale="en-US"
280
+ onVisibleRangeChange={onVisibleRangeChange}
281
+ />,
282
+ );
283
+ expect(onVisibleRangeChange).toHaveBeenCalled();
284
+ const range = onVisibleRangeChange.mock.calls[0][0] as {
285
+ view: string;
286
+ start: Date;
287
+ end: Date;
288
+ };
289
+ expect(range.view).toBe("week");
290
+ expect(range.start.getDay()).toBe(0);
291
+ expect(range.end.getTime() - range.start.getTime()).toBe(7 * 86_400_000);
292
+ });
293
+ });