@spatika/react 1.4.0 → 1.6.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 (93) hide show
  1. package/AGENTS.md +6 -3
  2. package/README.md +1 -1
  3. package/dist/composites/EventCalendar.js +3 -3
  4. package/dist/composites/GlassCard.d.ts +1 -1
  5. package/dist/composites/RichTextEditor.d.ts +1 -4
  6. package/dist/composites/RichTextEditor.d.ts.map +1 -1
  7. package/dist/composites/RichTextEditor.js +12 -19
  8. package/dist/composites/SurfaceCard.d.ts +1 -1
  9. package/dist/composites/Tag.d.ts +2 -2
  10. package/dist/index.d.ts +8 -22
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -11
  13. package/dist/lib/breakpoints.d.ts +17 -0
  14. package/dist/lib/breakpoints.d.ts.map +1 -0
  15. package/dist/lib/breakpoints.js +23 -0
  16. package/dist/lib/create-theme.d.ts +162 -0
  17. package/dist/lib/create-theme.d.ts.map +1 -0
  18. package/dist/lib/create-theme.js +138 -0
  19. package/dist/lib/scheduler.js +2 -2
  20. package/dist/lib/themes.d.ts +17 -7
  21. package/dist/lib/themes.d.ts.map +1 -1
  22. package/dist/lib/themes.js +22 -8
  23. package/dist/lib/use-media-query.d.ts +9 -0
  24. package/dist/lib/use-media-query.d.ts.map +1 -1
  25. package/dist/lib/use-media-query.js +28 -1
  26. package/dist/primitives/Alert.d.ts +1 -1
  27. package/dist/primitives/Badge.d.ts +2 -2
  28. package/dist/primitives/Button.d.ts +2 -2
  29. package/dist/primitives/Container.d.ts +2 -2
  30. package/dist/primitives/Fab.d.ts +4 -4
  31. package/dist/primitives/Grid.d.ts +2 -1
  32. package/dist/primitives/Grid.d.ts.map +1 -1
  33. package/dist/primitives/Grid.js +10 -2
  34. package/dist/primitives/IconButton.d.ts +2 -2
  35. package/dist/primitives/ToggleButton.d.ts +1 -1
  36. package/dist/theme/SpatikaThemeProvider.d.ts +4 -5
  37. package/dist/theme/SpatikaThemeProvider.d.ts.map +1 -1
  38. package/dist/theme/SpatikaThemeProvider.js +28 -13
  39. package/package.json +4 -2
  40. package/skills/spatika-ui/SKILL.md +25 -6
  41. package/src/composites/EventCalendar.test.tsx +293 -0
  42. package/src/composites/EventCalendar.tsx +3 -3
  43. package/src/composites/RichTextEditor.test.tsx +51 -0
  44. package/src/composites/RichTextEditor.tsx +20 -84
  45. package/src/composites/chart-ui.test.tsx +21 -0
  46. package/src/composites/scheduler.test.tsx +0 -258
  47. package/src/index.test.ts +6 -0
  48. package/src/index.ts +138 -72
  49. package/src/lib/breakpoints.test.ts +29 -0
  50. package/src/lib/breakpoints.ts +28 -0
  51. package/src/lib/create-theme.test.ts +79 -0
  52. package/src/lib/create-theme.ts +249 -0
  53. package/src/lib/scheduler.test.ts +10 -0
  54. package/src/lib/scheduler.ts +2 -2
  55. package/src/lib/themes.test.ts +17 -0
  56. package/src/lib/themes.ts +43 -12
  57. package/src/lib/use-media-query.test.tsx +80 -0
  58. package/src/lib/use-media-query.ts +35 -1
  59. package/src/primitives/Grid.test.tsx +55 -0
  60. package/src/primitives/Grid.tsx +13 -3
  61. package/src/primitives/mui-more.test.tsx +17 -0
  62. package/src/theme/SpatikaThemeProvider.test.tsx +38 -0
  63. package/src/theme/SpatikaThemeProvider.tsx +43 -15
  64. package/src/composites/ChartDataGrid.test.tsx +0 -57
  65. package/src/composites/ChartDataGrid.tsx +0 -196
  66. package/src/composites/MapChart.test.tsx +0 -39
  67. package/src/composites/MapChart.tsx +0 -130
  68. package/src/composites/ScatterWebGL.test.tsx +0 -25
  69. package/src/composites/ScatterWebGL.tsx +0 -79
  70. package/src/composites/cartesian-charts.test.tsx +0 -136
  71. package/src/composites/cartesian-charts.tsx +0 -1355
  72. package/src/composites/chart-composition.test.tsx +0 -68
  73. package/src/composites/chart-composition.tsx +0 -1039
  74. package/src/composites/chart-interaction.ts +0 -136
  75. package/src/composites/chart-ui.tsx +0 -336
  76. package/src/composites/charts-3d.test.tsx +0 -25
  77. package/src/composites/charts-3d.tsx +0 -318
  78. package/src/composites/charts.test.tsx +0 -254
  79. package/src/composites/flow-charts.test.tsx +0 -98
  80. package/src/composites/flow-charts.tsx +0 -641
  81. package/src/composites/radial-charts.test.tsx +0 -89
  82. package/src/composites/radial-charts.tsx +0 -643
  83. package/src/lib/chart-export.test.ts +0 -20
  84. package/src/lib/chart-export.ts +0 -99
  85. package/src/lib/charts-platform.test.ts +0 -93
  86. package/src/lib/charts.test.ts +0 -132
  87. package/src/lib/charts.ts +0 -1021
  88. package/src/lib/geo.test.ts +0 -42
  89. package/src/lib/geo.ts +0 -138
  90. package/src/lib/iso.test.ts +0 -22
  91. package/src/lib/iso.ts +0 -102
  92. package/src/lib/webgl-scatter.test.ts +0 -36
  93. package/src/lib/webgl-scatter.ts +0 -230
@@ -1,136 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import { cn } from "../lib/cn";
3
-
4
- export type ChartTooltipItem = {
5
- color: string;
6
- label: string;
7
- value: string;
8
- };
9
-
10
- export type ChartHover = {
11
- x: number;
12
- y: number;
13
- title?: string;
14
- items: ChartTooltipItem[];
15
- dataIndex?: number;
16
- seriesId?: string;
17
- category?: string | number | Date;
18
- } | null;
19
-
20
- /** Payload fired when a chart mark (bar, slice, point, cell) is activated. */
21
- export type ChartItemEvent = {
22
- seriesId: string;
23
- seriesLabel: string;
24
- dataIndex: number;
25
- category: string | number | Date;
26
- value: number | null;
27
- color: string;
28
- };
29
-
30
- /** Geometry plus item identity, used by `renderMark` on line and area series. */
31
- export type ChartMarkRenderContext = ChartItemEvent & {
32
- x: number;
33
- y: number;
34
- };
35
-
36
- /**
37
- * Overlay guide on a cartesian plot.
38
- * Set `y` for a horizontal value line, or `xIndex` for a vertical category line.
39
- */
40
- export type ChartReferenceLine = {
41
- y?: number;
42
- xIndex?: number;
43
- yAxisId?: string;
44
- color?: string;
45
- label?: string;
46
- strokeDasharray?: string;
47
- };
48
-
49
- export type ChartTooltipRenderer = (hover: NonNullable<ChartHover>) => ReactNode;
50
-
51
- export type ChartMarkBind = {
52
- className: string;
53
- role?: "button";
54
- tabIndex?: number;
55
- "aria-label"?: string;
56
- onClick?: (event: { stopPropagation(): void }) => void;
57
- onKeyDown?: (event: { key: string; preventDefault(): void }) => void;
58
- onMouseEnter?: (event: { nativeEvent: { offsetX: number; offsetY: number } }) => void;
59
- onMouseLeave?: () => void;
60
- };
61
-
62
- /** Per-category color, falling back to the series token. */
63
- export function seriesItemColor(
64
- color: string,
65
- itemColors: Array<string | null | undefined> | undefined,
66
- index: number,
67
- ): string {
68
- return itemColors?.[index] || color;
69
- }
70
-
71
- export function chartItemAriaLabel(event: ChartItemEvent): string {
72
- const category = String(event.category);
73
- if (event.seriesLabel && event.seriesLabel !== category) {
74
- return `${event.seriesLabel} ${category}`;
75
- }
76
- return category;
77
- }
78
-
79
- /**
80
- * Pointer, keyboard, and hover bindings for an SVG mark.
81
- * Adds a button role only when `onItemClick` is set so legends stay the sole named buttons.
82
- */
83
- export function bindChartMark({
84
- event,
85
- hover,
86
- onItemClick,
87
- setHover,
88
- clearHover,
89
- onHighlight,
90
- }: {
91
- event: ChartItemEvent;
92
- hover: Pick<NonNullable<ChartHover>, "title" | "items"> & Partial<NonNullable<ChartHover>>;
93
- onItemClick?: (event: ChartItemEvent) => void;
94
- setHover: (hover: ChartHover) => void;
95
- clearHover: () => void;
96
- onHighlight?: (item: { seriesId: string; dataIndex: number } | null) => void;
97
- }): ChartMarkBind {
98
- const clickable = Boolean(onItemClick);
99
- const applyHover = (x: number, y: number) => {
100
- onHighlight?.({ seriesId: event.seriesId, dataIndex: event.dataIndex });
101
- setHover({
102
- x: hover.x ?? x,
103
- y: hover.y ?? y,
104
- title: hover.title,
105
- items: hover.items,
106
- dataIndex: event.dataIndex,
107
- seriesId: event.seriesId,
108
- category: event.category,
109
- });
110
- };
111
- return {
112
- className: cn("spk-chart-mark", clickable && "spk-chart-mark--interactive"),
113
- role: clickable ? "button" : undefined,
114
- tabIndex: clickable ? 0 : undefined,
115
- "aria-label": clickable ? chartItemAriaLabel(event) : undefined,
116
- onClick: clickable
117
- ? (native) => {
118
- native.stopPropagation();
119
- onItemClick?.(event);
120
- }
121
- : undefined,
122
- onKeyDown: clickable
123
- ? (native) => {
124
- if (native.key === "Enter" || native.key === " ") {
125
- native.preventDefault();
126
- onItemClick?.(event);
127
- }
128
- }
129
- : undefined,
130
- onMouseEnter: (native) => applyHover(native.nativeEvent.offsetX, native.nativeEvent.offsetY),
131
- onMouseLeave: () => {
132
- onHighlight?.(null);
133
- clearHover();
134
- },
135
- };
136
- }
@@ -1,336 +0,0 @@
1
- import {
2
- useEffect,
3
- useRef,
4
- useState,
5
- type ReactNode,
6
- type RefObject,
7
- } from "react";
8
- import { cn } from "../lib/cn";
9
- import {
10
- DEFAULT_CHART_MARGIN,
11
- chartColor,
12
- formatAxisValue,
13
- formatChartNumber,
14
- type ChartAxisConfig,
15
- type ChartMargin,
16
- } from "../lib/charts";
17
- import {
18
- type ChartHover,
19
- type ChartTooltipRenderer,
20
- } from "./chart-interaction";
21
-
22
- export type { ChartHover, ChartTooltipItem } from "./chart-interaction";
23
-
24
- export type ChartLegendItem = {
25
- id: string;
26
- label: string;
27
- color: string;
28
- };
29
-
30
- export type ChartFrameProps = {
31
- slot: string;
32
- className?: string;
33
- width?: number;
34
- height?: number;
35
- /** When true, plot height follows the parent instead of the numeric `height`. */
36
- fillHeight?: boolean;
37
- margin?: ChartMargin;
38
- legend?: ChartLegendItem[];
39
- hiddenIds?: Set<string>;
40
- onToggleSeries?: (id: string) => void;
41
- hover?: ChartHover;
42
- renderTooltip?: ChartTooltipRenderer;
43
- "aria-label"?: string;
44
- children: (plot: { width: number; height: number; m: Required<ChartMargin> }) => ReactNode;
45
- };
46
-
47
- export function resolveMargin(margin?: ChartMargin): Required<ChartMargin> {
48
- return {
49
- top: margin?.top ?? DEFAULT_CHART_MARGIN.top,
50
- right: margin?.right ?? DEFAULT_CHART_MARGIN.right,
51
- bottom: margin?.bottom ?? DEFAULT_CHART_MARGIN.bottom,
52
- left: margin?.left ?? DEFAULT_CHART_MARGIN.left,
53
- };
54
- }
55
-
56
- function useElementSize(
57
- ref: RefObject<HTMLElement | null>,
58
- fallbackWidth: number,
59
- fallbackHeight: number,
60
- ) {
61
- const [size, setSize] = useState({ width: fallbackWidth, height: fallbackHeight });
62
-
63
- useEffect(() => {
64
- const el = ref.current;
65
- if (!el) return;
66
- const apply = () => {
67
- setSize({
68
- width: el.clientWidth || fallbackWidth,
69
- height: el.clientHeight || fallbackHeight,
70
- });
71
- };
72
- apply();
73
- if (typeof ResizeObserver === "undefined") return;
74
- const observer = new ResizeObserver(apply);
75
- observer.observe(el);
76
- return () => observer.disconnect();
77
- }, [fallbackWidth, fallbackHeight, ref]);
78
-
79
- return size;
80
- }
81
-
82
- export function ChartFrame({
83
- slot,
84
- className,
85
- width,
86
- height = 280,
87
- fillHeight = false,
88
- margin,
89
- legend,
90
- hiddenIds,
91
- onToggleSeries,
92
- hover,
93
- renderTooltip,
94
- "aria-label": ariaLabel,
95
- children,
96
- }: ChartFrameProps) {
97
- const surfaceRef = useRef<HTMLDivElement>(null);
98
- const measured = useElementSize(surfaceRef, width ?? 320, height);
99
- const plotWidth = width ?? measured.width;
100
- const plotHeight = fillHeight ? measured.height : height;
101
- const m = resolveMargin(margin);
102
-
103
- return (
104
- <div
105
- data-slot={slot}
106
- className={cn("spk-chart", fillHeight && "spk-chart--fill", className)}
107
- role="img"
108
- aria-label={ariaLabel}
109
- >
110
- {legend?.length ? (
111
- <ChartLegend items={legend} hiddenIds={hiddenIds} onToggle={onToggleSeries} />
112
- ) : null}
113
- <div
114
- ref={surfaceRef}
115
- className="spk-chart-surface"
116
- style={fillHeight ? undefined : { height: plotHeight }}
117
- >
118
- {plotWidth > 0 && plotHeight > 0 ? children({ width: plotWidth, height: plotHeight, m }) : null}
119
- <ChartTooltip hover={hover ?? null} boundsWidth={plotWidth} render={renderTooltip} />
120
- </div>
121
- </div>
122
- );
123
- }
124
-
125
- export function ChartLegend({
126
- items,
127
- hiddenIds,
128
- onToggle,
129
- }: {
130
- items: ChartLegendItem[];
131
- hiddenIds?: Set<string>;
132
- onToggle?: (id: string) => void;
133
- }) {
134
- return (
135
- <div className="spk-chart-legend">
136
- {items.map((item) => (
137
- <button
138
- key={item.id}
139
- type="button"
140
- className="spk-chart-legend-item"
141
- data-hidden={hiddenIds?.has(item.id) ? "true" : undefined}
142
- onClick={() => onToggle?.(item.id)}
143
- >
144
- <span className="spk-chart-swatch" style={{ background: item.color }} />
145
- {item.label}
146
- </button>
147
- ))}
148
- </div>
149
- );
150
- }
151
-
152
- export function ChartTooltip({
153
- hover,
154
- boundsWidth,
155
- render,
156
- }: {
157
- hover: ChartHover;
158
- boundsWidth: number;
159
- render?: ChartTooltipRenderer;
160
- }) {
161
- if (!hover || !hover.items.length) return null;
162
- const custom = render?.(hover);
163
- if (render && custom == null) return null;
164
- const left = clampTooltip(hover.x, boundsWidth);
165
- return (
166
- <div className="spk-chart-tooltip" style={{ left, top: hover.y }} role="tooltip">
167
- {render ? (
168
- custom
169
- ) : (
170
- <>
171
- {hover.title ? <p className="spk-chart-tooltip-title">{hover.title}</p> : null}
172
- <ul>
173
- {hover.items.map((item) => (
174
- <li key={item.label}>
175
- <span className="spk-chart-swatch" style={{ background: item.color }} />
176
- <span>{item.label}</span>
177
- <strong>{item.value}</strong>
178
- </li>
179
- ))}
180
- </ul>
181
- </>
182
- )}
183
- </div>
184
- );
185
- }
186
-
187
- function clampTooltip(x: number, width: number) {
188
- return clampNum(x, 72, Math.max(72, width - 72));
189
- }
190
-
191
- function clampNum(value: number, min: number, max: number) {
192
- return Math.min(max, Math.max(min, value));
193
- }
194
-
195
- export function CartesianGrid({
196
- ticks,
197
- scale,
198
- left,
199
- right,
200
- top,
201
- bottom,
202
- }: {
203
- ticks: number[];
204
- scale: (value: number) => number;
205
- left: number;
206
- right: number;
207
- top: number;
208
- bottom: number;
209
- }) {
210
- return (
211
- <g data-slot="chart-grid">
212
- {ticks.map((tick) => (
213
- <line
214
- key={tick}
215
- className="spk-chart-grid"
216
- x1={left}
217
- x2={right}
218
- y1={scale(tick)}
219
- y2={scale(tick)}
220
- />
221
- ))}
222
- <line className="spk-chart-axis" x1={left} x2={right} y1={bottom} y2={bottom} />
223
- <line className="spk-chart-axis" x1={left} x2={left} y1={top} y2={bottom} />
224
- </g>
225
- );
226
- }
227
-
228
- export function ValueAxis({
229
- ticks,
230
- scale,
231
- left,
232
- width,
233
- }: {
234
- ticks: number[];
235
- scale: (value: number) => number;
236
- left: number;
237
- width?: number;
238
- }) {
239
- return (
240
- <g data-slot="chart-y-axis">
241
- {ticks.map((tick) => (
242
- <text
243
- key={tick}
244
- className="spk-chart-tick"
245
- x={left - 8}
246
- y={scale(tick)}
247
- textAnchor="end"
248
- dominantBaseline="middle"
249
- >
250
- {formatChartNumber(tick)}
251
- </text>
252
- ))}
253
- {width ? (
254
- <text
255
- className="spk-chart-axis-label"
256
- x={12}
257
- y={width / 2}
258
- transform={`rotate(-90 12 ${width / 2})`}
259
- textAnchor="middle"
260
- />
261
- ) : null}
262
- </g>
263
- );
264
- }
265
-
266
- export function CategoryAxis({
267
- labels,
268
- position,
269
- y,
270
- }: {
271
- labels: Array<string | number | Date>;
272
- position: (index: number) => number;
273
- y: number;
274
- }) {
275
- const maxLabels = labels.length > 12 ? Math.ceil(labels.length / 8) : 1;
276
- return (
277
- <g data-slot="chart-x-axis">
278
- {labels.map((label, index) => {
279
- if (index % maxLabels !== 0) return null;
280
- return (
281
- <text
282
- key={`${index}-${String(label)}`}
283
- className="spk-chart-tick"
284
- x={position(index)}
285
- y={y + 16}
286
- textAnchor="middle"
287
- >
288
- {formatAxisValue(label)}
289
- </text>
290
- );
291
- })}
292
- </g>
293
- );
294
- }
295
-
296
- export function useHiddenSeries(ids: string[]) {
297
- const [hidden, setHidden] = useState<Set<string>>(new Set());
298
- const toggle = (id: string) => {
299
- setHidden((current) => {
300
- const next = new Set(current);
301
- if (next.has(id)) next.delete(id);
302
- else next.add(id);
303
- return next;
304
- });
305
- };
306
- const visible = ids.filter((id) => !hidden.has(id));
307
- return { hidden, toggle, visible };
308
- }
309
-
310
- export function seriesMeta(
311
- series: Array<{ id?: string; label?: string; color?: string }>,
312
- colors?: string[],
313
- ): ChartLegendItem[] {
314
- return series.map((item, index) => ({
315
- id: item.id ?? item.label ?? `series-${index}`,
316
- label: item.label ?? item.id ?? `Series ${index + 1}`,
317
- color: item.color ?? chartColor(index, colors),
318
- }));
319
- }
320
-
321
- export function useChartHover() {
322
- const [hover, setHover] = useState<ChartHover>(null);
323
- return { hover, setHover, clear: () => setHover(null) };
324
- }
325
-
326
- export function categoryLabels(axis?: ChartAxisConfig, length = 0): Array<string | number | Date> {
327
- if (axis?.data?.length) return axis.data;
328
- return Array.from({ length }, (_, i) => i + 1);
329
- }
330
-
331
- export function formatSeriesValue(value: number | [number, number] | undefined) {
332
- if (value == null) return "—";
333
- if (Array.isArray(value)) return `${formatChartNumber(value[0])} – ${formatChartNumber(value[1])}`;
334
- return formatChartNumber(value);
335
- }
336
-
@@ -1,25 +0,0 @@
1
- import { render } from "@testing-library/react";
2
- import { describe, expect, it } from "vitest";
3
- import { BarChart3D, PieChart3D } from "./charts-3d";
4
-
5
- describe("charts-3d", () => {
6
- it("renders isometric bars and an extruded pie", () => {
7
- const { container } = render(
8
- <div>
9
- <BarChart3D
10
- width={240}
11
- height={160}
12
- xAxis={[{ data: ["A", "B"] }]}
13
- series={[{ label: "Views", data: [3, 5] }]}
14
- />
15
- <PieChart3D
16
- width={200}
17
- height={160}
18
- series={[{ data: [{ value: 2, label: "A" }, { value: 3, label: "B" }] }]}
19
- />
20
- </div>,
21
- );
22
- expect(container.querySelector('[data-slot="bar-chart-3d"]')).toBeTruthy();
23
- expect(container.querySelector('[data-slot="pie-chart-3d"]')).toBeTruthy();
24
- });
25
- });