@matthieumordrel/chart-studio 0.2.2 → 0.2.4
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.
- package/README.md +24 -0
- package/dist/core/chart-capabilities.d.mts +15 -0
- package/dist/core/chart-capabilities.mjs +23 -0
- package/dist/core/colors.mjs +5 -5
- package/dist/core/config-utils.mjs +3 -0
- package/dist/core/date-range-presets.d.mts +12 -0
- package/dist/core/date-range-presets.mjs +152 -0
- package/dist/core/pipeline-data-points.mjs +4 -1
- package/dist/core/types.d.mts +37 -6
- package/dist/core/use-chart.mjs +50 -25
- package/dist/ui/chart-canvas.d.mts +1 -1
- package/dist/ui/chart-canvas.mjs +305 -27
- package/dist/ui/chart-context.d.mts +3 -0
- package/dist/ui/chart-context.mjs +3 -0
- package/dist/ui/chart-date-range-badge.mjs +2 -2
- package/dist/ui/chart-date-range-panel.mjs +19 -101
- package/dist/ui/chart-date-range.mjs +3 -3
- package/dist/ui/chart-group-by-selector.d.mts +3 -1
- package/dist/ui/chart-group-by-selector.mjs +4 -1
- package/dist/ui/chart-metric-selector.mjs +2 -2
- package/dist/ui/chart-select.mjs +9 -10
- package/dist/ui/chart-source-switcher.d.mts +3 -1
- package/dist/ui/chart-source-switcher.mjs +4 -2
- package/dist/ui/chart-time-bucket-selector.d.mts +3 -1
- package/dist/ui/chart-time-bucket-selector.mjs +4 -1
- package/dist/ui/chart-toolbar-overflow.mjs +48 -26
- package/dist/ui/chart-type-selector.d.mts +7 -2
- package/dist/ui/chart-type-selector.mjs +155 -20
- package/dist/ui/chart-x-axis-selector.d.mts +3 -1
- package/dist/ui/chart-x-axis-selector.mjs +4 -1
- package/dist/ui/theme.css +54 -49
- package/package.json +7 -6
|
@@ -4,7 +4,7 @@ import { ChartDropdownPanel } from "./chart-dropdown.mjs";
|
|
|
4
4
|
import { ChartMetricPanel } from "./chart-metric-panel.mjs";
|
|
5
5
|
import { useRef, useState } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { ChevronDown,
|
|
7
|
+
import { ChevronDown, MoveVertical } from "lucide-react";
|
|
8
8
|
//#region src/ui/chart-metric-selector.tsx
|
|
9
9
|
/**
|
|
10
10
|
* Metric selector — trigger button + popover wrapping ChartMetricPanel.
|
|
@@ -28,7 +28,7 @@ function ChartMetricSelector({ className }) {
|
|
|
28
28
|
className: `inline-flex h-7 items-center gap-1.5 rounded-lg border px-2.5 text-xs font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20 ${isActive ? "border-primary/30 bg-primary/5 text-primary shadow-sm shadow-primary/5 hover:bg-primary/8" : "border-border/50 bg-background text-muted-foreground shadow-sm hover:border-border hover:bg-muted/30 hover:shadow hover:text-foreground"}`,
|
|
29
29
|
"aria-label": "Metric",
|
|
30
30
|
children: [
|
|
31
|
-
/* @__PURE__ */ jsx(
|
|
31
|
+
/* @__PURE__ */ jsx(MoveVertical, { className: "h-3 w-3" }),
|
|
32
32
|
/* @__PURE__ */ jsx("span", { children: label }),
|
|
33
33
|
isActive && !includeZeros && /* @__PURE__ */ jsx("span", {
|
|
34
34
|
className: "rounded bg-muted px-1 py-px text-[9px] font-normal text-muted-foreground",
|
package/dist/ui/chart-select.mjs
CHANGED
|
@@ -4,11 +4,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
import { ChevronDown } from "lucide-react";
|
|
5
5
|
//#region src/ui/chart-select.tsx
|
|
6
6
|
/**
|
|
7
|
-
* Custom select dropdown — premium replacement for native <select> elements.
|
|
8
|
-
* Uses fixed positioning so the options list works correctly even inside
|
|
9
|
-
* overflow-hidden containers (e.g. the toolbar overflow panel).
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
7
|
* Premium styled select dropdown with highlight-only selection styling.
|
|
13
8
|
*
|
|
14
9
|
* @property value - Currently selected value
|
|
@@ -17,7 +12,7 @@ import { ChevronDown } from "lucide-react";
|
|
|
17
12
|
* @property ariaLabel - Accessible label for the trigger
|
|
18
13
|
* @property className - Additional CSS classes
|
|
19
14
|
*/
|
|
20
|
-
function ChartSelect({ value, options, onChange, ariaLabel, className }) {
|
|
15
|
+
function ChartSelect({ value, options, onChange, ariaLabel, icon: Icon, hideIcon, className }) {
|
|
21
16
|
const [isOpen, setIsOpen] = useState(false);
|
|
22
17
|
const triggerRef = useRef(null);
|
|
23
18
|
const selected = options.find((o) => o.value === value);
|
|
@@ -37,10 +32,14 @@ function ChartSelect({ value, options, onChange, ariaLabel, className }) {
|
|
|
37
32
|
onClick: handleToggle,
|
|
38
33
|
className: "inline-flex h-7 items-center gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 text-xs font-medium text-foreground shadow-sm transition-all hover:border-border hover:bg-muted/30 hover:shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20",
|
|
39
34
|
"aria-label": ariaLabel,
|
|
40
|
-
children: [
|
|
41
|
-
className: "
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
children: [
|
|
36
|
+
Icon && !hideIcon && /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
|
|
37
|
+
/* @__PURE__ */ jsx("span", {
|
|
38
|
+
className: "truncate",
|
|
39
|
+
children: selected?.label ?? value
|
|
40
|
+
}),
|
|
41
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform ${isOpen ? "rotate-180" : ""}` })
|
|
42
|
+
]
|
|
44
43
|
}), /* @__PURE__ */ jsx(ChartDropdownPanel, {
|
|
45
44
|
isOpen,
|
|
46
45
|
onClose: () => setIsOpen(false),
|
|
@@ -14,9 +14,11 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
14
14
|
* - Multi source → dropdown to switch between sources
|
|
15
15
|
*/
|
|
16
16
|
declare function ChartSourceSwitcher({
|
|
17
|
-
className
|
|
17
|
+
className,
|
|
18
|
+
hideIcon
|
|
18
19
|
}: {
|
|
19
20
|
className?: string;
|
|
21
|
+
hideIcon?: boolean;
|
|
20
22
|
}): react_jsx_runtime0.JSX.Element;
|
|
21
23
|
//#endregion
|
|
22
24
|
export { ChartSourceSwitcher };
|
|
@@ -19,14 +19,14 @@ function formatCount(n) {
|
|
|
19
19
|
* - Single source → read-only badge: "[icon] Jobs · 1,247 records"
|
|
20
20
|
* - Multi source → dropdown to switch between sources
|
|
21
21
|
*/
|
|
22
|
-
function ChartSourceSwitcher({ className }) {
|
|
22
|
+
function ChartSourceSwitcher({ className, hideIcon }) {
|
|
23
23
|
const { hasMultipleSources, sources, activeSourceId, setActiveSource, recordCount } = useChartContext();
|
|
24
24
|
if (!hasMultipleSources) {
|
|
25
25
|
const label = sources[0]?.label ?? "Unnamed Source";
|
|
26
26
|
return /* @__PURE__ */ jsxs("div", {
|
|
27
27
|
className: `inline-flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground ${className ?? ""}`,
|
|
28
28
|
children: [
|
|
29
|
-
/* @__PURE__ */ jsx(Database, { className: "h-3 w-3 shrink-0" }),
|
|
29
|
+
!hideIcon && /* @__PURE__ */ jsx(Database, { className: "h-3 w-3 shrink-0" }),
|
|
30
30
|
/* @__PURE__ */ jsx("span", {
|
|
31
31
|
className: "font-medium text-foreground",
|
|
32
32
|
children: label
|
|
@@ -47,6 +47,8 @@ function ChartSourceSwitcher({ className }) {
|
|
|
47
47
|
})),
|
|
48
48
|
onChange: (v) => setActiveSource(v),
|
|
49
49
|
ariaLabel: "Data source",
|
|
50
|
+
icon: Database,
|
|
51
|
+
hideIcon,
|
|
50
52
|
className
|
|
51
53
|
});
|
|
52
54
|
}
|
|
@@ -7,9 +7,11 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
7
7
|
*/
|
|
8
8
|
/** Custom dropdown to select time granularity. */
|
|
9
9
|
declare function ChartTimeBucketSelector({
|
|
10
|
-
className
|
|
10
|
+
className,
|
|
11
|
+
hideIcon
|
|
11
12
|
}: {
|
|
12
13
|
className?: string;
|
|
14
|
+
hideIcon?: boolean;
|
|
13
15
|
}): react_jsx_runtime0.JSX.Element | null;
|
|
14
16
|
//#endregion
|
|
15
17
|
export { ChartTimeBucketSelector };
|
|
@@ -2,6 +2,7 @@ import { CHART_TYPE_CONFIG } from "../core/chart-capabilities.mjs";
|
|
|
2
2
|
import { useChartContext } from "./chart-context.mjs";
|
|
3
3
|
import { ChartSelect } from "./chart-select.mjs";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { Clock } from "lucide-react";
|
|
5
6
|
//#region src/ui/chart-time-bucket-selector.tsx
|
|
6
7
|
/**
|
|
7
8
|
* Time bucket selector — premium custom dropdown.
|
|
@@ -16,7 +17,7 @@ const BUCKET_LABELS = {
|
|
|
16
17
|
year: "Year"
|
|
17
18
|
};
|
|
18
19
|
/** Custom dropdown to select time granularity. */
|
|
19
|
-
function ChartTimeBucketSelector({ className }) {
|
|
20
|
+
function ChartTimeBucketSelector({ className, hideIcon }) {
|
|
20
21
|
const { chartType, isTimeSeries, timeBucket, setTimeBucket, availableTimeBuckets } = useChartContext();
|
|
21
22
|
if (!isTimeSeries || !CHART_TYPE_CONFIG[chartType].supportsTimeBucketing || availableTimeBuckets.length === 0) return null;
|
|
22
23
|
return /* @__PURE__ */ jsx(ChartSelect, {
|
|
@@ -27,6 +28,8 @@ function ChartTimeBucketSelector({ className }) {
|
|
|
27
28
|
})),
|
|
28
29
|
onChange: setTimeBucket,
|
|
29
30
|
ariaLabel: "Time granularity",
|
|
31
|
+
icon: Clock,
|
|
32
|
+
hideIcon,
|
|
30
33
|
className
|
|
31
34
|
});
|
|
32
35
|
}
|
|
@@ -7,22 +7,25 @@ import { ChartMetricPanel } from "./chart-metric-panel.mjs";
|
|
|
7
7
|
import { CONTROL_IDS, CONTROL_REGISTRY, SECTIONS } from "./toolbar-types.mjs";
|
|
8
8
|
import { useRef, useState } from "react";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import { ArrowLeft, ChevronRight, Ellipsis, Eraser } from "lucide-react";
|
|
10
|
+
import { ArrowLeft, BarChart3, Calendar, ChevronRight, Clock, Database, Ellipsis, Eraser, Filter, Layers, MoveHorizontal, MoveVertical } from "lucide-react";
|
|
11
11
|
//#region src/ui/chart-toolbar-overflow.tsx
|
|
12
|
-
/**
|
|
13
|
-
* Overflow menu for toolbar controls — Notion-inspired configuration panel.
|
|
14
|
-
*
|
|
15
|
-
* Renders an ellipsis button that opens a panel with all non-pinned controls.
|
|
16
|
-
* Simple controls (selects, toggle buttons) render inline in rows.
|
|
17
|
-
* Complex controls (metric, filters, date range) show as clickable rows that
|
|
18
|
-
* navigate to a full-page detail view within the panel (Notion-style drill-down).
|
|
19
|
-
*/
|
|
20
12
|
/** Controls that drill-down into a detail page instead of rendering inline. */
|
|
21
13
|
const COMPLEX_CONTROLS = new Set([
|
|
22
14
|
"metric",
|
|
23
15
|
"filters",
|
|
24
16
|
"dateRange"
|
|
25
17
|
]);
|
|
18
|
+
/** Icon for each control in the overflow menu. */
|
|
19
|
+
const CONTROL_ICONS = {
|
|
20
|
+
source: Database,
|
|
21
|
+
xAxis: MoveHorizontal,
|
|
22
|
+
chartType: BarChart3,
|
|
23
|
+
groupBy: Layers,
|
|
24
|
+
timeBucket: Clock,
|
|
25
|
+
metric: MoveVertical,
|
|
26
|
+
filters: Filter,
|
|
27
|
+
dateRange: Calendar
|
|
28
|
+
};
|
|
26
29
|
/**
|
|
27
30
|
* Ellipsis overflow menu with Notion-style drill-down navigation.
|
|
28
31
|
* Main view shows all controls. Clicking a complex control replaces
|
|
@@ -157,16 +160,13 @@ function DetailPage({ controlId, onBack }) {
|
|
|
157
160
|
* Used for selects and toggle buttons that work inline without popovers.
|
|
158
161
|
*/
|
|
159
162
|
function SimpleControlRow({ controlId }) {
|
|
160
|
-
const
|
|
161
|
-
const
|
|
163
|
+
const Component = CONTROL_REGISTRY[controlId].component;
|
|
164
|
+
const Icon = CONTROL_ICONS[controlId];
|
|
162
165
|
return /* @__PURE__ */ jsxs("div", {
|
|
163
|
-
className: "flex items-center gap-3 rounded-lg px-
|
|
164
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
165
|
-
className: "
|
|
166
|
-
children:
|
|
167
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
168
|
-
className: "ml-auto flex min-w-0 items-center",
|
|
169
|
-
children: /* @__PURE__ */ jsx(Component, {})
|
|
166
|
+
className: "flex items-center gap-3 rounded-lg px-2 py-1.5",
|
|
167
|
+
children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("div", {
|
|
168
|
+
className: "flex min-w-0 items-center",
|
|
169
|
+
children: /* @__PURE__ */ jsx(Component, { hideIcon: true })
|
|
170
170
|
})]
|
|
171
171
|
});
|
|
172
172
|
}
|
|
@@ -175,17 +175,14 @@ function SimpleControlRow({ controlId }) {
|
|
|
175
175
|
* Shows the control label, current value summary, and a chevron.
|
|
176
176
|
*/
|
|
177
177
|
function ComplexControlRow({ controlId, onNavigate }) {
|
|
178
|
-
const
|
|
178
|
+
const Icon = CONTROL_ICONS[controlId];
|
|
179
179
|
return /* @__PURE__ */ jsxs("button", {
|
|
180
180
|
onClick: onNavigate,
|
|
181
181
|
className: "flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-colors hover:bg-muted/50",
|
|
182
182
|
children: [
|
|
183
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
183
184
|
/* @__PURE__ */ jsx("div", {
|
|
184
|
-
className: "
|
|
185
|
-
children: entry.label
|
|
186
|
-
}),
|
|
187
|
-
/* @__PURE__ */ jsx("div", {
|
|
188
|
-
className: "ml-auto truncate text-xs text-foreground",
|
|
185
|
+
className: "truncate text-xs text-foreground",
|
|
189
186
|
children: /* @__PURE__ */ jsx(ControlSummary, { controlId })
|
|
190
187
|
}),
|
|
191
188
|
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground" })
|
|
@@ -194,14 +191,39 @@ function ComplexControlRow({ controlId, onNavigate }) {
|
|
|
194
191
|
}
|
|
195
192
|
/** Summary text for a complex control in the main menu. */
|
|
196
193
|
function ControlSummary({ controlId }) {
|
|
197
|
-
const { metric, columns, filters,
|
|
194
|
+
const { metric, columns, filters, dateRange, dateRangePreset } = useChartContext();
|
|
198
195
|
switch (controlId) {
|
|
199
196
|
case "metric": return /* @__PURE__ */ jsx("span", { children: getMetricLabel(metric, columns) });
|
|
200
197
|
case "filters": {
|
|
201
198
|
const count = [...filters.values()].reduce((sum, set) => sum + set.size, 0);
|
|
202
199
|
return /* @__PURE__ */ jsx("span", { children: count > 0 ? `${count} active` : "None" });
|
|
203
200
|
}
|
|
204
|
-
case "dateRange":
|
|
201
|
+
case "dateRange": {
|
|
202
|
+
const label = resolvePresetLabel(dateRangePreset);
|
|
203
|
+
if (dateRange?.min && dateRange?.max) {
|
|
204
|
+
const fmt = (d) => d.toLocaleDateString("en-US", {
|
|
205
|
+
month: "short",
|
|
206
|
+
day: "numeric",
|
|
207
|
+
year: "2-digit"
|
|
208
|
+
});
|
|
209
|
+
return /* @__PURE__ */ jsxs("span", { children: [
|
|
210
|
+
label,
|
|
211
|
+
/* @__PURE__ */ jsx("span", {
|
|
212
|
+
className: "text-muted-foreground/40",
|
|
213
|
+
children: " · "
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ jsxs("span", {
|
|
216
|
+
className: "font-normal",
|
|
217
|
+
children: [
|
|
218
|
+
fmt(dateRange.min),
|
|
219
|
+
" – ",
|
|
220
|
+
fmt(dateRange.max)
|
|
221
|
+
]
|
|
222
|
+
})
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
return /* @__PURE__ */ jsx("span", { children: label });
|
|
226
|
+
}
|
|
205
227
|
default: return null;
|
|
206
228
|
}
|
|
207
229
|
}
|
|
@@ -2,9 +2,14 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
2
2
|
|
|
3
3
|
//#region src/ui/chart-type-selector.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Chart type selector — inline toggle buttons
|
|
5
|
+
* Chart type selector — inline toggle buttons with variant dropdown.
|
|
6
|
+
*
|
|
7
|
+
* Primary types: Bar, Line, Area, Pie, Donut.
|
|
8
|
+
* Types with variants show a small chevron that opens a dropdown:
|
|
9
|
+
* Bar → Stacked, Grouped, 100%
|
|
10
|
+
* Area → Stacked, 100%
|
|
6
11
|
*/
|
|
7
|
-
/** Inline toggle buttons
|
|
12
|
+
/** Inline toggle buttons with variant dropdown for chart type selection. */
|
|
8
13
|
declare function ChartTypeSelector({
|
|
9
14
|
className
|
|
10
15
|
}: {
|
|
@@ -1,32 +1,167 @@
|
|
|
1
1
|
import { useChartContext } from "./chart-context.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { ChartDropdownPanel } from "./chart-dropdown.mjs";
|
|
3
|
+
import { useMemo, useRef, useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ChevronDown } from "lucide-react";
|
|
3
6
|
//#region src/ui/chart-type-selector.tsx
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Chart type selector — inline toggle buttons with variant dropdown.
|
|
9
|
+
*
|
|
10
|
+
* Primary types: Bar, Line, Area, Pie, Donut.
|
|
11
|
+
* Types with variants show a small chevron that opens a dropdown:
|
|
12
|
+
* Bar → Stacked, Grouped, 100%
|
|
13
|
+
* Area → Stacked, 100%
|
|
14
|
+
*/
|
|
15
|
+
const CHART_TYPE_GROUPS = [
|
|
16
|
+
{
|
|
17
|
+
primary: "bar",
|
|
18
|
+
label: "Bar",
|
|
19
|
+
variants: [
|
|
20
|
+
{
|
|
21
|
+
type: "bar",
|
|
22
|
+
label: "Stacked"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "grouped-bar",
|
|
26
|
+
label: "Grouped"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "percent-bar",
|
|
30
|
+
label: "100%"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
primary: "line",
|
|
36
|
+
label: "Line",
|
|
37
|
+
variants: []
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
primary: "area",
|
|
41
|
+
label: "Area",
|
|
42
|
+
variants: [{
|
|
43
|
+
type: "area",
|
|
44
|
+
label: "Stacked"
|
|
45
|
+
}, {
|
|
46
|
+
type: "percent-area",
|
|
47
|
+
label: "100%"
|
|
48
|
+
}]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
primary: "pie",
|
|
52
|
+
label: "Pie",
|
|
53
|
+
variants: []
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
primary: "donut",
|
|
57
|
+
label: "Donut",
|
|
58
|
+
variants: []
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
function buildVisibleGroups(availableChartTypes) {
|
|
62
|
+
const available = new Set(availableChartTypes);
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const group of CHART_TYPE_GROUPS) if (group.variants.length === 0) {
|
|
65
|
+
if (available.has(group.primary)) result.push({
|
|
66
|
+
...group,
|
|
67
|
+
visibleVariants: []
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
const visibleVariants = group.variants.filter((v) => available.has(v.type));
|
|
71
|
+
if (visibleVariants.length > 0) result.push({
|
|
72
|
+
...group,
|
|
73
|
+
visibleVariants
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function findGroupForType(chartType) {
|
|
79
|
+
return CHART_TYPE_GROUPS.find((g) => g.primary === chartType || g.variants.some((v) => v.type === chartType));
|
|
80
|
+
}
|
|
81
|
+
/** Inline toggle buttons with variant dropdown for chart type selection. */
|
|
13
82
|
function ChartTypeSelector({ className }) {
|
|
14
83
|
const { chartType, setChartType, availableChartTypes } = useChartContext();
|
|
15
|
-
|
|
84
|
+
const [openGroup, setOpenGroup] = useState(null);
|
|
85
|
+
const visibleGroups = useMemo(() => buildVisibleGroups(availableChartTypes), [availableChartTypes]);
|
|
86
|
+
const activeGroup = useMemo(() => {
|
|
87
|
+
const staticGroup = findGroupForType(chartType);
|
|
88
|
+
if (!staticGroup) return void 0;
|
|
89
|
+
return visibleGroups.find((g) => g.primary === staticGroup.primary);
|
|
90
|
+
}, [chartType, visibleGroups]);
|
|
91
|
+
if (visibleGroups.length <= 1 && (activeGroup?.visibleVariants.length ?? 0) <= 1) return null;
|
|
16
92
|
return /* @__PURE__ */ jsx("div", {
|
|
17
|
-
className: `inline-flex items-center rounded-lg border border-border/50 bg-
|
|
93
|
+
className: `inline-flex items-center rounded-lg border border-border/50 bg-background p-0.5 shadow-sm ${className ?? ""}`,
|
|
18
94
|
role: "tablist",
|
|
19
95
|
"aria-label": "Chart type",
|
|
20
|
-
children:
|
|
21
|
-
const isActive =
|
|
22
|
-
return /* @__PURE__ */ jsx(
|
|
96
|
+
children: visibleGroups.map((group) => {
|
|
97
|
+
const isActive = activeGroup?.primary === group.primary;
|
|
98
|
+
return /* @__PURE__ */ jsx(ChartTypeButton, {
|
|
99
|
+
group,
|
|
100
|
+
isActive,
|
|
101
|
+
hasVariants: group.visibleVariants.length > 1,
|
|
102
|
+
isDropdownOpen: openGroup === group.primary,
|
|
103
|
+
chartType,
|
|
104
|
+
onSelect: () => {
|
|
105
|
+
if (isActive) return;
|
|
106
|
+
if (availableChartTypes.includes(group.primary)) setChartType(group.primary);
|
|
107
|
+
else if (group.visibleVariants.length > 0) setChartType(group.visibleVariants[0].type);
|
|
108
|
+
},
|
|
109
|
+
onToggleDropdown: () => setOpenGroup(openGroup === group.primary ? null : group.primary),
|
|
110
|
+
onSelectVariant: (type) => {
|
|
111
|
+
setChartType(type);
|
|
112
|
+
setOpenGroup(null);
|
|
113
|
+
},
|
|
114
|
+
onCloseDropdown: () => setOpenGroup(null)
|
|
115
|
+
}, group.primary);
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function ChartTypeButton({ group, isActive, hasVariants, isDropdownOpen, chartType, onSelect, onToggleDropdown, onSelectVariant, onCloseDropdown }) {
|
|
120
|
+
const triggerRef = useRef(null);
|
|
121
|
+
if (!hasVariants) return /* @__PURE__ */ jsx("button", {
|
|
122
|
+
role: "tab",
|
|
123
|
+
"aria-selected": isActive,
|
|
124
|
+
onClick: onSelect,
|
|
125
|
+
className: `rounded-md px-2.5 py-1 text-xs font-medium transition-all ${isActive ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted/40 hover:text-foreground"}`,
|
|
126
|
+
children: group.label
|
|
127
|
+
});
|
|
128
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
129
|
+
ref: triggerRef,
|
|
130
|
+
className: `relative flex items-center rounded-md transition-all ${isActive ? "bg-primary/10" : "hover:bg-muted/40"}`,
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ jsx("button", {
|
|
23
133
|
role: "tab",
|
|
24
134
|
"aria-selected": isActive,
|
|
25
|
-
onClick: () =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
135
|
+
onClick: () => {
|
|
136
|
+
if (isActive) onToggleDropdown();
|
|
137
|
+
else onSelect();
|
|
138
|
+
},
|
|
139
|
+
className: `py-1 pl-2.5 pr-1 text-xs font-medium transition-colors ${isActive ? "text-primary" : "text-muted-foreground hover:text-foreground"}`,
|
|
140
|
+
children: group.label
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ jsx("button", {
|
|
143
|
+
"aria-label": `${group.label} options`,
|
|
144
|
+
onClick: (e) => {
|
|
145
|
+
e.stopPropagation();
|
|
146
|
+
if (!isActive) onSelect();
|
|
147
|
+
onToggleDropdown();
|
|
148
|
+
},
|
|
149
|
+
className: `py-1 pr-2 pl-0.5 transition-colors ${isActive ? "text-primary/60 hover:text-primary" : "text-muted-foreground/40 hover:text-muted-foreground"}`,
|
|
150
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: `h-2.5 w-2.5 transition-transform ${isDropdownOpen ? "rotate-180" : ""}` })
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ jsx(ChartDropdownPanel, {
|
|
153
|
+
isOpen: isDropdownOpen,
|
|
154
|
+
onClose: onCloseDropdown,
|
|
155
|
+
triggerRef,
|
|
156
|
+
minWidth: "trigger",
|
|
157
|
+
className: "p-1",
|
|
158
|
+
children: group.visibleVariants.map((variant) => /* @__PURE__ */ jsx("button", {
|
|
159
|
+
onClick: () => onSelectVariant(variant.type),
|
|
160
|
+
className: `flex w-full items-center rounded-md px-2.5 py-1.5 text-xs transition-colors ${variant.type === chartType ? "bg-primary/8 font-medium text-primary" : "text-foreground hover:bg-muted/60"}`,
|
|
161
|
+
children: variant.label
|
|
162
|
+
}, variant.type))
|
|
163
|
+
})
|
|
164
|
+
]
|
|
30
165
|
});
|
|
31
166
|
}
|
|
32
167
|
//#endregion
|
|
@@ -6,9 +6,11 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
/** Custom dropdown to select the X-axis column. */
|
|
8
8
|
declare function ChartXAxisSelector({
|
|
9
|
-
className
|
|
9
|
+
className,
|
|
10
|
+
hideIcon
|
|
10
11
|
}: {
|
|
11
12
|
className?: string;
|
|
13
|
+
hideIcon?: boolean;
|
|
12
14
|
}): react_jsx_runtime0.JSX.Element | null;
|
|
13
15
|
//#endregion
|
|
14
16
|
export { ChartXAxisSelector };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useChartContext } from "./chart-context.mjs";
|
|
2
2
|
import { ChartSelect } from "./chart-select.mjs";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { MoveHorizontal } from "lucide-react";
|
|
4
5
|
//#region src/ui/chart-x-axis-selector.tsx
|
|
5
6
|
/**
|
|
6
7
|
* X-axis selector — premium custom dropdown for choosing which column drives the X-axis.
|
|
7
8
|
*/
|
|
8
9
|
/** Custom dropdown to select the X-axis column. */
|
|
9
|
-
function ChartXAxisSelector({ className }) {
|
|
10
|
+
function ChartXAxisSelector({ className, hideIcon }) {
|
|
10
11
|
const { xAxisId, setXAxis, availableXAxes } = useChartContext();
|
|
11
12
|
if (availableXAxes.length <= 1) return null;
|
|
12
13
|
const options = availableXAxes.map((col) => ({
|
|
@@ -18,6 +19,8 @@ function ChartXAxisSelector({ className }) {
|
|
|
18
19
|
options,
|
|
19
20
|
onChange: (v) => setXAxis(v),
|
|
20
21
|
ariaLabel: "X-axis",
|
|
22
|
+
icon: MoveHorizontal,
|
|
23
|
+
hideIcon,
|
|
21
24
|
className
|
|
22
25
|
});
|
|
23
26
|
}
|
package/dist/ui/theme.css
CHANGED
|
@@ -1,62 +1,67 @@
|
|
|
1
1
|
@source "./";
|
|
2
2
|
|
|
3
3
|
@theme inline {
|
|
4
|
-
--color-background:
|
|
5
|
-
--color-foreground:
|
|
6
|
-
--color-card:
|
|
7
|
-
--color-card-foreground:
|
|
8
|
-
--color-popover:
|
|
9
|
-
--color-popover-foreground:
|
|
10
|
-
--color-primary:
|
|
11
|
-
--color-primary-foreground:
|
|
12
|
-
--color-muted:
|
|
13
|
-
--color-muted-foreground:
|
|
14
|
-
--color-border:
|
|
15
|
-
--color-input:
|
|
16
|
-
--color-ring:
|
|
4
|
+
--color-background: var(--cs-background);
|
|
5
|
+
--color-foreground: var(--cs-foreground);
|
|
6
|
+
--color-card: var(--cs-card);
|
|
7
|
+
--color-card-foreground: var(--cs-card-foreground);
|
|
8
|
+
--color-popover: var(--cs-popover);
|
|
9
|
+
--color-popover-foreground: var(--cs-popover-foreground);
|
|
10
|
+
--color-primary: var(--cs-primary);
|
|
11
|
+
--color-primary-foreground: var(--cs-primary-foreground);
|
|
12
|
+
--color-muted: var(--cs-muted);
|
|
13
|
+
--color-muted-foreground: var(--cs-muted-foreground);
|
|
14
|
+
--color-border: var(--cs-border);
|
|
15
|
+
--color-input: var(--cs-input);
|
|
16
|
+
--color-ring: var(--cs-ring);
|
|
17
|
+
--radius-sm: calc(var(--cs-radius) - 4px);
|
|
18
|
+
--radius-md: calc(var(--cs-radius) - 2px);
|
|
19
|
+
--radius-lg: var(--cs-radius);
|
|
20
|
+
--radius-xl: calc(var(--cs-radius) + 4px);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
:root {
|
|
20
24
|
color-scheme: light;
|
|
21
|
-
--cs-
|
|
22
|
-
--cs-
|
|
23
|
-
--cs-
|
|
24
|
-
--cs-card
|
|
25
|
-
--cs-
|
|
26
|
-
--cs-popover
|
|
27
|
-
--cs-
|
|
28
|
-
--cs-primary
|
|
29
|
-
--cs-
|
|
30
|
-
--cs-muted
|
|
31
|
-
--cs-
|
|
32
|
-
--cs-
|
|
33
|
-
--cs-
|
|
34
|
-
--cs-
|
|
35
|
-
--cs-chart-
|
|
36
|
-
--cs-chart-
|
|
37
|
-
--cs-chart-
|
|
38
|
-
--cs-chart-
|
|
25
|
+
--cs-radius: var(--radius, 0.25rem);
|
|
26
|
+
--cs-background: var(--background, oklch(0.980 0.002 264.545));
|
|
27
|
+
--cs-foreground: var(--foreground, oklch(0.128 0.027 261.594));
|
|
28
|
+
--cs-card: var(--card, oklch(1.000 0 0));
|
|
29
|
+
--cs-card-foreground: var(--card-foreground, oklch(0.128 0.027 261.594));
|
|
30
|
+
--cs-popover: var(--popover, oklch(1.000 0 0));
|
|
31
|
+
--cs-popover-foreground: var(--popover-foreground, oklch(0.128 0.027 261.594));
|
|
32
|
+
--cs-primary: var(--primary, oklch(0.501 0.228 277.992));
|
|
33
|
+
--cs-primary-foreground: var(--primary-foreground, oklch(1.000 0 0));
|
|
34
|
+
--cs-muted: var(--muted, oklch(0.948 0.004 264.536));
|
|
35
|
+
--cs-muted-foreground: var(--muted-foreground, oklch(0.550 0.023 264.362));
|
|
36
|
+
--cs-border: var(--border, oklch(0.920 0.006 264.529));
|
|
37
|
+
--cs-input: var(--input, oklch(0.920 0.006 264.529));
|
|
38
|
+
--cs-ring: var(--ring, oklch(0.501 0.228 277.992));
|
|
39
|
+
--cs-chart-1: oklch(0.501 0.228 277.992);
|
|
40
|
+
--cs-chart-2: oklch(0.550 0.235 302.715);
|
|
41
|
+
--cs-chart-3: oklch(0.609 0.206 354.673);
|
|
42
|
+
--cs-chart-4: oklch(0.635 0.109 178.228);
|
|
43
|
+
--cs-chart-5: oklch(0.732 0.166 58.213);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
:root[data-theme='dark'],
|
|
42
47
|
.dark {
|
|
43
48
|
color-scheme: dark;
|
|
44
|
-
--cs-background: var(--background,
|
|
45
|
-
--cs-foreground: var(--foreground,
|
|
46
|
-
--cs-card: var(--card,
|
|
47
|
-
--cs-card-foreground: var(--card-foreground,
|
|
48
|
-
--cs-popover: var(--popover,
|
|
49
|
-
--cs-popover-foreground: var(--popover-foreground,
|
|
50
|
-
--cs-primary: var(--primary,
|
|
51
|
-
--cs-primary-foreground: var(--primary-foreground, 0 0
|
|
52
|
-
--cs-muted: var(--muted,
|
|
53
|
-
--cs-muted-foreground: var(--muted-foreground,
|
|
54
|
-
--cs-border: var(--border,
|
|
55
|
-
--cs-input: var(--input,
|
|
56
|
-
--cs-ring: var(--ring,
|
|
57
|
-
--cs-chart-1:
|
|
58
|
-
--cs-chart-2:
|
|
59
|
-
--cs-chart-3:
|
|
60
|
-
--cs-chart-4:
|
|
61
|
-
--cs-chart-5:
|
|
49
|
+
--cs-background: var(--background, oklch(0.171 0.015 273.761));
|
|
50
|
+
--cs-foreground: var(--foreground, oklch(0.944 0.005 264.534));
|
|
51
|
+
--cs-card: var(--card, oklch(0.203 0.018 273.739));
|
|
52
|
+
--cs-card-foreground: var(--card-foreground, oklch(0.944 0.005 264.534));
|
|
53
|
+
--cs-popover: var(--popover, oklch(0.224 0.019 273.793));
|
|
54
|
+
--cs-popover-foreground: var(--popover-foreground, oklch(0.944 0.005 264.534));
|
|
55
|
+
--cs-primary: var(--primary, oklch(0.597 0.196 282.474));
|
|
56
|
+
--cs-primary-foreground: var(--primary-foreground, oklch(1.000 0 0));
|
|
57
|
+
--cs-muted: var(--muted, oklch(0.246 0.018 274.033));
|
|
58
|
+
--cs-muted-foreground: var(--muted-foreground, oklch(0.618 0.025 264.372));
|
|
59
|
+
--cs-border: var(--border, oklch(0.258 0.016 274.161));
|
|
60
|
+
--cs-input: var(--input, oklch(0.287 0.019 274.112));
|
|
61
|
+
--cs-ring: var(--ring, oklch(0.597 0.196 282.474));
|
|
62
|
+
--cs-chart-1: oklch(0.625 0.188 283.272);
|
|
63
|
+
--cs-chart-2: oklch(0.660 0.198 305.491);
|
|
64
|
+
--cs-chart-3: oklch(0.785 0.132 215.571);
|
|
65
|
+
--cs-chart-4: oklch(0.789 0.176 158.514);
|
|
66
|
+
--cs-chart-5: oklch(0.815 0.143 77.593);
|
|
62
67
|
}
|