doom-design-system 0.4.12 → 0.4.13

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.
@@ -1,3 +1,15 @@
1
+ import { ChartFooter } from "./ChartFooter";
2
+ import { ChartHeader } from "./ChartHeader";
3
+ import { ChartLegend } from "./ChartLegend";
4
+ import { ChartPlot } from "./ChartPlot";
5
+ import { ChartRoot } from "./ChartRoot";
1
6
  import { ChartProps } from "./types";
2
- export type { ChartConfig, ChartProps, DrawContext, LegendConfig, LegendItem, LegendPosition, } from "./types";
3
- export declare function Chart<T>({ data, type, variant, x, y, d3Config, className, style, renderTooltip, flat, title, subtitle, legend, render, withFrame, onValueChange, }: ChartProps<T>): import("react/jsx-runtime").JSX.Element;
7
+ export type { ChartConfig, ChartProps, DrawContext, LegendConfig, LegendItem, } from "./types";
8
+ declare function ChartInternal<T>(props: ChartProps<T>): import("react/jsx-runtime").JSX.Element;
9
+ export declare const Chart: typeof ChartInternal & {
10
+ Root: typeof ChartRoot;
11
+ Header: typeof ChartHeader;
12
+ Footer: typeof ChartFooter;
13
+ Legend: typeof ChartLegend;
14
+ Plot: typeof ChartPlot;
15
+ };
@@ -1,292 +1,29 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import clsx from "clsx";
4
- import { pointer, select } from "d3-selection";
5
- import React, { useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
6
- import { Card } from "../Card/Card.js";
7
3
  import { Flex, Stack } from "../Layout/Layout.js";
8
- import { Text } from "../Text/Text.js";
9
- import styles from "./Chart.module.css";
10
- // Theme-compatible color palette for auto-assigned legend colors
11
- const LEGEND_PALETTE = [
12
- "var(--primary)",
13
- "var(--secondary)",
14
- "var(--accent)",
15
- "var(--success)",
16
- "var(--warning)",
17
- "var(--error)",
18
- ];
19
- import { createScales, drawAxes, drawBars, drawGrid, drawLineArea, setupGradient, } from "./renderers.js";
20
- export function Chart({ data, type = "line", variant = "default", x, y, d3Config, className, style, renderTooltip, flat, title, subtitle, legend, render, withFrame = true, onValueChange, }) {
21
- var _a, _b, _c, _d;
22
- // Constants
23
- const MOBILE_ASPECT_RATIO = 0.75; // 4:3 aspect ratio for mobile
24
- const TOOLTIP_OFFSET = 20;
25
- const HIDE_DELAY_MS = 16;
26
- const MOBILE_BREAKPOINT = 480;
27
- const svgRef = useRef(null);
28
- const containerRef = useRef(null);
29
- const wrapperRef = useRef(null);
30
- const tooltipRef = useRef(null);
31
- const tooltipPosRef = useRef({ x: 0, y: 0, isTouch: false });
32
- const hideTimeoutRef = useRef(null);
33
- const gradientId = React.useId().replace(/:/g, "");
34
- const [activeData, setActiveData] = useState(null);
35
- const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
36
- const [containerWidth, setContainerWidth] = useState(0);
37
- const isMobile = containerWidth > 0 && containerWidth < MOBILE_BREAKPOINT;
38
- // Determine legend position based on device
39
- const legendPosition = isMobile
40
- ? ((_b = (_a = legend === null || legend === void 0 ? void 0 : legend.position) === null || _a === void 0 ? void 0 : _a.mobile) !== null && _b !== void 0 ? _b : "bottom")
41
- : ((_d = (_c = legend === null || legend === void 0 ? void 0 : legend.position) === null || _c === void 0 ? void 0 : _c.default) !== null && _d !== void 0 ? _d : "top");
42
- // Calculate auto height for mobile if not explicitly set
43
- const autoHeight = useMemo(() => {
44
- if (d3Config === null || d3Config === void 0 ? void 0 : d3Config.height) {
45
- return d3Config.height;
46
- }
47
- // On mobile, use aspect ratio for balanced proportions
48
- if (isMobile && containerWidth > 0) {
49
- return Math.round(containerWidth * MOBILE_ASPECT_RATIO);
50
- }
51
- return 400; // Default height
52
- }, [d3Config === null || d3Config === void 0 ? void 0 : d3Config.height, isMobile, containerWidth]);
53
- useEffect(() => {
54
- onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(activeData !== null && activeData !== void 0 ? activeData : null);
55
- }, [activeData, onValueChange]);
56
- // Effective y-axis label (hidden on mobile for space)
57
- const effectiveYAxisLabel = isMobile ? undefined : d3Config === null || d3Config === void 0 ? void 0 : d3Config.yAxisLabel;
58
- const config = useMemo(() => (Object.assign(Object.assign({ margin: {
59
- top: isMobile ? 10 : 20,
60
- right: isMobile ? 10 : 20,
61
- bottom: isMobile ? 40 : (d3Config === null || d3Config === void 0 ? void 0 : d3Config.xAxisLabel) ? 60 : 30,
62
- // On mobile, use minimal left margin since y-axis label is hidden
63
- left: isMobile ? 35 : (d3Config === null || d3Config === void 0 ? void 0 : d3Config.yAxisLabel) ? 90 : 60,
64
- }, curve: undefined, showAxes: true, grid: false, withGradient: type === "area", showDots: false }, d3Config), {
65
- // Override yAxisLabel for mobile
66
- yAxisLabel: effectiveYAxisLabel })), [d3Config, type, isMobile, effectiveYAxisLabel]);
67
- const calculateTooltipTransform = (tx, ty, isTouch = false) => {
68
- if (!tooltipRef.current || !wrapperRef.current) {
69
- return "";
70
- }
71
- const rect = wrapperRef.current.getBoundingClientRect();
72
- const tooltipWidth = tooltipRef.current.offsetWidth;
73
- const absX = rect.left + tx;
74
- let xOffset = TOOLTIP_OFFSET;
75
- // Check right edge
76
- if (absX + tooltipWidth + TOOLTIP_OFFSET > window.innerWidth - 10) {
77
- xOffset = -TOOLTIP_OFFSET - tooltipWidth;
78
- }
79
- // Check left edge (after potential flip)
80
- if (rect.left + tx + xOffset < 10) {
81
- xOffset = 10 - (rect.left + tx);
82
- }
83
- // Apply Y offset for touch to avoid finger occlusion
84
- // Shift tooltip 50px above touch point to clear the finger
85
- const effectiveY = isTouch ? ty - 50 : ty;
86
- return `translate3d(${tx + xOffset}px, calc(${effectiveY}px - 50%), 0)`;
87
- };
88
- useLayoutEffect(() => {
89
- if (activeData && tooltipRef.current && wrapperRef.current) {
90
- const { x, y, isTouch } = tooltipPosRef.current;
91
- const transform = calculateTooltipTransform(x, y, !!isTouch);
92
- if (transform) {
93
- tooltipRef.current.style.transform = transform;
94
- }
95
- }
96
- }, [activeData]);
97
- useEffect(() => {
98
- return () => {
99
- if (hideTimeoutRef.current) {
100
- clearTimeout(hideTimeoutRef.current);
101
- }
102
- };
103
- }, []);
104
- // Track container width for auto aspect ratio
105
- useEffect(() => {
106
- if (!containerRef.current) {
107
- return;
108
- }
109
- const containerObserver = new ResizeObserver((entries) => {
110
- const entry = entries[0];
111
- if (entry) {
112
- setContainerWidth(entry.contentRect.width);
113
- }
114
- });
115
- containerObserver.observe(containerRef.current);
116
- return () => containerObserver.disconnect();
117
- }, []);
118
- useEffect(() => {
119
- if (!wrapperRef.current) {
120
- return;
121
- }
122
- const resizeObserver = new ResizeObserver((entries) => {
123
- const entry = entries[0];
124
- if (entry) {
125
- setDimensions({
126
- width: entry.contentRect.width,
127
- height: entry.contentRect.height,
128
- });
129
- }
130
- });
131
- resizeObserver.observe(wrapperRef.current);
132
- return () => resizeObserver.disconnect();
133
- }, []);
134
- useEffect(() => {
135
- if (!svgRef.current ||
136
- !data.length ||
137
- dimensions.width === 0 ||
138
- dimensions.height === 0) {
139
- return;
140
- }
141
- const { width, height } = dimensions;
142
- const margin = config.margin;
143
- if (width <= 0 || height <= 0) {
144
- return;
145
- }
146
- const svg = select(svgRef.current);
147
- svg.selectAll("*").remove();
148
- const g = svg
149
- .attr("width", width)
150
- .attr("height", height)
151
- .append("g")
152
- .attr("transform", `translate(${margin.left},${margin.top})`);
153
- if (config.withGradient) {
154
- setupGradient(svg, gradientId);
155
- }
156
- const { xScale, yScale, innerWidth, innerHeight } = createScales(data, width, height, margin, x, y, type);
157
- if (innerWidth <= 0 || innerHeight <= 0) {
158
- return;
159
- }
160
- if (config.grid) {
161
- drawGrid(g, yScale, innerWidth, styles.grid, isMobile);
162
- }
163
- if (config.showAxes) {
164
- drawAxes(g, xScale, yScale, innerWidth, innerHeight, margin, config, styles, isMobile);
165
- }
166
- const updateTooltip = (tx, ty, data, isTouch = false) => {
167
- if (hideTimeoutRef.current) {
168
- clearTimeout(hideTimeoutRef.current);
169
- hideTimeoutRef.current = null;
170
- }
171
- tooltipPosRef.current = { x: tx, y: ty, isTouch };
172
- if (tooltipRef.current && wrapperRef.current) {
173
- const transform = calculateTooltipTransform(tx, ty, isTouch);
174
- if (transform) {
175
- tooltipRef.current.style.transform = transform;
176
- }
177
- }
178
- setActiveData((prev) => (prev === data ? prev : data));
179
- };
180
- const showTooltip = (event, data) => {
181
- const [px, py] = select(svgRef.current).select("g").empty()
182
- ? [0, 0]
183
- : pointer(event, g.node());
184
- const tx = px + margin.left;
185
- const ty = py + margin.top;
186
- updateTooltip(tx, ty, data);
187
- };
188
- const hideTooltip = () => {
189
- hideTimeoutRef.current = setTimeout(() => setActiveData(null), HIDE_DELAY_MS);
190
- };
191
- const ctx = {
192
- g,
193
- data,
194
- xScale,
195
- yScale,
196
- x,
197
- y,
198
- innerWidth,
199
- innerHeight,
200
- margin,
201
- config,
202
- styles,
203
- gradientId,
204
- setHoverState: (state) => {
205
- var _a;
206
- if (!state) {
207
- hideTooltip();
208
- }
209
- else {
210
- updateTooltip(state.x, state.y, state.data, (_a = state.isTouch) !== null && _a !== void 0 ? _a : false);
211
- }
212
- },
213
- showTooltip,
214
- hideTooltip,
215
- type,
216
- isMobile,
217
- resolveInteraction: (event) => {
218
- if (event.cancelable && event.type.startsWith("touch")) {
219
- event.preventDefault();
220
- }
221
- let el = null;
222
- if (event.touches && event.touches.length > 0) {
223
- const touch = event.touches[0];
224
- el = document.elementFromPoint(touch.clientX, touch.clientY);
225
- }
226
- else {
227
- el = event.currentTarget;
228
- }
229
- if (!el) {
230
- return null;
231
- }
232
- const data = select(el).datum();
233
- if (Array.isArray(data)) {
234
- return null;
235
- }
236
- return data ? { element: el, data } : null;
237
- },
238
- };
239
- if (render) {
240
- render(ctx);
241
- }
242
- else if (type === "bar") {
243
- drawBars(ctx);
244
- }
245
- else {
246
- drawLineArea(ctx);
247
- }
248
- }, [data, config, type, gradientId, dimensions, render, isMobile, x, y]);
249
- // Render legend items
250
- // Render legend items
251
- const renderLegend = (isBottom = false, isMobile = false, layout = "horizontal") => {
252
- var _a;
253
- const paddingRight = isMobile ? 28 : 30;
254
- if (!((_a = legend === null || legend === void 0 ? void 0 : legend.data) === null || _a === void 0 ? void 0 : _a.length)) {
255
- return null;
256
- }
257
- // Standard chart (no custom render) supports only 1 series,
258
- // so we limit the legend to 1 item to match the single curve/bar series.
259
- // If a custom render function is provided, we respect all legend items.
260
- const activeData = !render && legend.data.length > 1 ? legend.data.slice(0, 1) : legend.data;
261
- const isVertical = layout === "vertical";
262
- return (_jsx(Flex, { className: clsx(styles.legend, isBottom && styles.legendBottom), gap: isVertical ? 2 : 4, style: Object.assign(Object.assign({ flexWrap: "wrap", flexDirection: isVertical ? "column" : "row", alignItems: isVertical ? "flex-start" : "center" }, (!isBottom &&
263
- !isVertical && {
264
- paddingRight: config.margin.left - paddingRight,
265
- })), (isBottom && !isMobile && { marginLeft: config.margin.left })), children: activeData.map((item, index) => (_jsxs(Flex, { align: "center", className: styles.legendItem, gap: 1, children: [_jsx("span", { className: styles.legendDot, style: {
266
- backgroundColor: item.color || LEGEND_PALETTE[index % LEGEND_PALETTE.length],
267
- } }), _jsx(Text, { className: styles.legendLabel, variant: "small", children: item.label })] }, item.label))) }));
268
- };
269
- // Header with title/subtitle on left, legend on right (when position is top)
270
- const renderHeader = (isMobile, legendPosition) => {
271
- var _a;
272
- const hasTitle = title || subtitle;
273
- const hasLegendTop = legendPosition === "top" && ((_a = legend === null || legend === void 0 ? void 0 : legend.data) === null || _a === void 0 ? void 0 : _a.length);
274
- let marginLeft = isMobile ? 28 : 30;
275
- if (legendPosition && legendPosition === "left") {
276
- marginLeft = config.margin.left;
277
- }
278
- if (!hasTitle && !hasLegendTop) {
279
- return null;
280
- }
281
- return (_jsxs(Flex, { align: "center", className: styles.chartHeader, justify: "space-between", style: {
282
- marginLeft: config.margin.left - marginLeft,
283
- flex: 1, // Allow header to fill available space or share it
284
- }, children: [hasTitle ? (_jsxs(Stack, { gap: 2, children: [title && (_jsx("div", { children: typeof title === "string" ? (_jsx(Text, { style: { margin: 0 }, variant: "h5", children: title })) : (title) })), subtitle && (_jsx(Text, { className: styles.subtitle, variant: "small", children: subtitle }))] })) : (_jsx("div", {})), hasLegendTop && renderLegend(false, isMobile)] }));
285
- };
286
- return (_jsx("div", { ref: containerRef, className: clsx(styles.chartContainer, variant === "solid" && styles.solid, flat && styles.flat, isMobile && styles.mobile, !withFrame && styles.frameless, className), style: Object.assign({ height: autoHeight, width: config.width || "100%" }, style), children: _jsxs(Stack, { gap: isMobile ? 2 : 4, style: { flex: 1, minHeight: 0 }, children: [_jsx(Flex, { justify: "space-between", style: { width: "100%" }, children: renderHeader(isMobile, legendPosition) }), _jsxs(Flex, { gap: 8, style: { flex: 1, minHeight: 0 }, children: [legendPosition === "left" && (_jsx("div", { className: styles.legendVertical, children: renderLegend(false, isMobile, "vertical") })), _jsxs("div", { ref: wrapperRef, className: styles.responsiveWrapper, children: [_jsx("svg", { ref: svgRef, className: styles.chart, style: { display: "block", width: "100%", height: "100%" } }), activeData && (_jsx("div", { ref: tooltipRef, className: styles.tooltipWrapper, style: { pointerEvents: "none" }, children: renderTooltip ? (renderTooltip(activeData)) : (_jsxs(Card, { className: styles.tooltipCard, children: [_jsx("div", { style: { marginBottom: 4 }, children: _jsx(Text, { style: {
287
- color: "var(--muted-foreground)",
288
- textTransform: "uppercase",
289
- fontSize: "10px",
290
- letterSpacing: "0.5px",
291
- }, variant: "h6", children: x(activeData) }) }), _jsx("div", { children: _jsx(Text, { style: { margin: 0 }, variant: "h4", children: y(activeData) }) })] })) }))] }), legendPosition === "right" && (_jsx("div", { className: styles.legendVertical, children: renderLegend(false, isMobile, "vertical") }))] }), legendPosition === "bottom" && renderLegend(true, isMobile)] }) }));
4
+ import { useChartContext } from "./ChartContext.js";
5
+ import { ChartFooter } from "./ChartFooter.js";
6
+ import { ChartHeader } from "./ChartHeader.js";
7
+ import { ChartLegend } from "./ChartLegend.js";
8
+ import { ChartPlot } from "./ChartPlot.js";
9
+ import { ChartRoot } from "./ChartRoot.js";
10
+ function ChartInternal(props) {
11
+ const { title, subtitle, withLegend, type, render, renderTooltip } = props;
12
+ return (_jsx(ChartRoot, Object.assign({}, props, { children: _jsx(StandardChartLayout, { render: render, renderTooltip: renderTooltip, subtitle: subtitle, title: title, type: type, withLegend: withLegend }) })));
292
13
  }
14
+ function StandardChartLayout({ title, subtitle, withLegend, type, render, renderTooltip, }) {
15
+ // Standard Layout:
16
+ // - Header (Title + Subtitle + Legend Top Right)
17
+ // - Plot
18
+ // - (No side/bottom legends in standard mode - usage Composition for that)
19
+ const { legendItems } = useChartContext();
20
+ const showLegend = withLegend && legendItems.length > 0;
21
+ return (_jsxs(Stack, { style: { flex: 1, minHeight: 0 }, children: [_jsx(Flex, { justify: "space-between", style: { width: "100%" }, children: _jsx(ChartHeader, { subtitle: subtitle, title: title, children: showLegend && (_jsx(ChartLegend, { align: "end", items: legendItems, layout: "horizontal" })) }) }), _jsx(Flex, { gap: 8, style: { flex: 1, minHeight: 0 }, children: _jsx(ChartPlot, { render: render, renderTooltip: renderTooltip, type: type }) })] }));
22
+ }
23
+ export const Chart = Object.assign(ChartInternal, {
24
+ Root: ChartRoot,
25
+ Header: ChartHeader,
26
+ Footer: ChartFooter,
27
+ Legend: ChartLegend,
28
+ Plot: ChartPlot,
29
+ });
@@ -18,8 +18,8 @@
18
18
  }
19
19
  .chart :global .tick text {
20
20
  fill: var(--foreground);
21
- font-size: 11px;
22
- font-weight: 700;
21
+ font-size: var(--text-xs);
22
+ font-weight: var(--font-bold);
23
23
  text-transform: uppercase;
24
24
  }
25
25
 
@@ -58,7 +58,7 @@
58
58
 
59
59
  .cursorLine {
60
60
  stroke: var(--muted-foreground);
61
- stroke-width: 2px;
61
+ stroke-width: var(--border-width);
62
62
  stroke-dasharray: 4 4;
63
63
  opacity: 0;
64
64
  pointer-events: none;
@@ -89,7 +89,7 @@
89
89
  border: var(--border-width) solid var(--card-border);
90
90
  border-radius: var(--radius);
91
91
  box-shadow: var(--shadow-hard);
92
- padding: 1rem;
92
+ padding: var(--spacing-lg);
93
93
  overflow: visible;
94
94
  display: flex;
95
95
  flex-direction: column;
@@ -131,13 +131,7 @@
131
131
  border-color: var(--solid-fg);
132
132
  }
133
133
  .chartContainer.mobile {
134
- padding: 0.75rem;
135
- }
136
- .chartContainer.mobile .axisLabel {
137
- font-size: 0.75rem;
138
- }
139
- .chartContainer.mobile :global(.tick) text {
140
- font-size: 0.75rem;
134
+ padding: var(--spacing-sm);
141
135
  }
142
136
 
143
137
  .responsiveWrapper {
@@ -159,15 +153,15 @@
159
153
  }
160
154
 
161
155
  .tooltipCard {
162
- padding: 8px 12px;
163
- font-size: 12px;
156
+ padding: var(--spacing-sm) var(--spacing-md);
157
+ font-size: var(--text-xs);
164
158
  white-space: nowrap;
165
159
  }
166
160
 
167
161
  .axisLabel {
168
162
  fill: var(--foreground);
169
- font-size: 11px;
170
- font-weight: 700;
163
+ font-size: var(--text-xs);
164
+ font-weight: var(--font-bold);
171
165
  text-transform: uppercase;
172
166
  text-anchor: middle;
173
167
  }
@@ -211,6 +205,6 @@
211
205
  display: flex;
212
206
  flex-direction: column;
213
207
  align-self: center;
214
- gap: 8px;
215
- padding: 0 8px;
208
+ gap: var(--spacing-sm);
209
+ padding: 0 var(--spacing-sm);
216
210
  }
@@ -0,0 +1,27 @@
1
+ import { ChartConfig, LegendItem } from "./types";
2
+ export interface ChartContextValue<T = unknown> {
3
+ data: T[];
4
+ dimensions: {
5
+ containerWidth: number;
6
+ isMobile: boolean;
7
+ };
8
+ config: ChartConfig & {
9
+ margin: {
10
+ top: number;
11
+ right: number;
12
+ bottom: number;
13
+ left: number;
14
+ };
15
+ };
16
+ colorPalette: string[];
17
+ activeData: T | null;
18
+ setActiveData: (data: T | null) => void;
19
+ x: (d: T) => string | number;
20
+ y: (d: T) => number;
21
+ legendItems: LegendItem[];
22
+ type?: "line" | "area" | "bar";
23
+ render?: (context: any) => void;
24
+ registerSeries?: (item: LegendItem) => () => void;
25
+ }
26
+ export declare const ChartContext: import("react").Context<ChartContextValue<unknown> | null>;
27
+ export declare function useChartContext<T = unknown>(): ChartContextValue<T>;
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from "react";
2
+ export const ChartContext = createContext(null);
3
+ export function useChartContext() {
4
+ const context = useContext(ChartContext);
5
+ if (!context) {
6
+ throw new Error("Chart components must be used within a Chart.Root");
7
+ }
8
+ return context;
9
+ }
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ export interface ChartFooterProps {
3
+ className?: string;
4
+ style?: React.CSSProperties;
5
+ children?: React.ReactNode;
6
+ align?: "start" | "center" | "end" | "between";
7
+ }
8
+ export declare function ChartFooter({ className, style, children, align, }: ChartFooterProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import clsx from "clsx";
4
+ import { Flex } from "../Layout/Layout.js";
5
+ import styles from "./Chart.module.css";
6
+ export function ChartFooter({ className, style, children, align = "center", }) {
7
+ if (!children) {
8
+ return null;
9
+ }
10
+ const justifyMap = {
11
+ start: "flex-start",
12
+ center: "center",
13
+ end: "flex-end",
14
+ between: "space-between",
15
+ };
16
+ return (_jsx(Flex, { align: "center", className: clsx(styles.chartFooter, className), justify: justifyMap[align], style: Object.assign({ width: "100%", marginTop: "var(--spacing-md)" }, style), children: children }));
17
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export interface ChartHeaderProps {
3
+ title?: React.ReactNode;
4
+ subtitle?: string;
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ children?: React.ReactNode;
8
+ }
9
+ export declare function ChartHeader({ title, subtitle, className, style, children, }: ChartHeaderProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import clsx from "clsx";
4
+ import { Flex, Stack } from "../Layout/Layout.js";
5
+ import { Text } from "../Text/Text.js";
6
+ // Use relative import for styles to ensure sharing
7
+ import styles from "./Chart.module.css";
8
+ export function ChartHeader({ title, subtitle, className, style, children, }) {
9
+ const hasTitle = title || subtitle;
10
+ if (!hasTitle && !children) {
11
+ return null;
12
+ }
13
+ return (_jsxs(Flex, { align: "center", className: clsx(styles.chartHeader, className), justify: "space-between", style: Object.assign({ width: "100%" }, style), children: [hasTitle ? (_jsxs(Stack, { gap: 2, children: [title && (_jsx("div", { children: typeof title === "string" ? (_jsx(Text, { style: { margin: 0 }, variant: "h5", children: title })) : (title) })), subtitle && (_jsx(Text, { className: styles.subtitle, variant: "small", children: subtitle }))] })) : (_jsx("div", {})), children] }));
14
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { LegendItem } from "./types";
3
+ export interface ChartLegendProps {
4
+ items?: LegendItem[] | ((items: LegendItem[]) => LegendItem[]);
5
+ layout?: "horizontal" | "vertical";
6
+ align?: "start" | "center" | "end";
7
+ className?: string;
8
+ style?: React.CSSProperties;
9
+ }
10
+ export declare function ChartLegend({ items, layout, align, className, style, }: ChartLegendProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import clsx from "clsx";
4
+ import { useMemo } from "react";
5
+ import { Flex } from "../Layout/Layout.js";
6
+ import { Text } from "../Text/Text.js";
7
+ import styles from "./Chart.module.css";
8
+ import { useChartContext } from "./ChartContext.js";
9
+ export function ChartLegend({ items, layout = "horizontal", align = "start", className, style, }) {
10
+ const { legendItems, type, render } = useChartContext();
11
+ const contextItems = useMemo(() => {
12
+ if (render || (type === "bar" && legendItems.length > 1)) {
13
+ return legendItems;
14
+ }
15
+ return legendItems.slice(0, 1);
16
+ }, [legendItems, type, render]);
17
+ const activeItems = typeof items === "function" ? items(contextItems) : (items !== null && items !== void 0 ? items : contextItems);
18
+ if (!(activeItems === null || activeItems === void 0 ? void 0 : activeItems.length)) {
19
+ return null;
20
+ }
21
+ const isVertical = layout === "vertical";
22
+ const alignMap = {
23
+ start: "flex-start",
24
+ center: "center",
25
+ end: "flex-end",
26
+ };
27
+ return (_jsx(Flex, { className: clsx(styles.legend, className), gap: isVertical ? 2 : 4, style: Object.assign({ flexWrap: "wrap", flexDirection: isVertical ? "column" : "row", justifyContent: isVertical ? "flex-start" : alignMap[align], alignItems: isVertical ? alignMap[align] : "center" }, style), children: activeItems.map((item, index) => (_jsxs(Flex, { align: "center", className: styles.legendItem, gap: 1, children: [_jsx("span", { className: styles.legendDot, style: {
28
+ backgroundColor: item.color,
29
+ } }), _jsx(Text, { className: styles.legendLabel, variant: "small", children: item.label })] }, item.label || index))) }));
30
+ }
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { ChartProps } from "./types";
3
+ export interface ChartPlotProps<T> {
4
+ type?: ChartProps<T>["type"];
5
+ render?: ChartProps<T>["render"];
6
+ renderTooltip?: ChartProps<T>["renderTooltip"];
7
+ className?: string;
8
+ style?: React.CSSProperties;
9
+ label?: string;
10
+ color?: string;
11
+ }
12
+ export declare function ChartPlot<T>({ type, render, renderTooltip, className, style, label, color, }: ChartPlotProps<T>): import("react/jsx-runtime").JSX.Element;