@matthieumordrel/chart-studio 0.2.3 → 0.2.5

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 (48) hide show
  1. package/README.md +42 -42
  2. package/dist/core/chart-capabilities.d.mts +15 -0
  3. package/dist/core/chart-capabilities.mjs +23 -0
  4. package/dist/core/colors.mjs +5 -5
  5. package/dist/core/config-utils.mjs +6 -2
  6. package/dist/core/date-range-presets.d.mts +12 -0
  7. package/dist/core/date-range-presets.mjs +152 -0
  8. package/dist/core/define-chart-schema.d.mts +26 -94
  9. package/dist/core/define-chart-schema.mjs +26 -34
  10. package/dist/core/infer-columns.d.mts +2 -2
  11. package/dist/core/infer-columns.mjs +4 -2
  12. package/dist/core/metric-utils.mjs +13 -5
  13. package/dist/core/pipeline-data-points.mjs +4 -1
  14. package/dist/core/schema-builder.mjs +335 -0
  15. package/dist/core/schema-builder.types.d.mts +279 -0
  16. package/dist/core/types.d.mts +61 -17
  17. package/dist/core/use-chart-options.d.mts +7 -4
  18. package/dist/core/use-chart.d.mts +4 -4
  19. package/dist/core/use-chart.mjs +70 -40
  20. package/dist/index.d.mts +2 -2
  21. package/dist/index.mjs +1 -1
  22. package/dist/ui/chart-canvas.d.mts +8 -4
  23. package/dist/ui/chart-canvas.mjs +347 -29
  24. package/dist/ui/chart-context.d.mts +11 -4
  25. package/dist/ui/chart-context.mjs +3 -0
  26. package/dist/ui/chart-date-range-badge.mjs +2 -2
  27. package/dist/ui/chart-date-range-panel.mjs +19 -101
  28. package/dist/ui/chart-date-range.mjs +3 -3
  29. package/dist/ui/chart-debug.d.mts +6 -2
  30. package/dist/ui/chart-debug.mjs +5 -1
  31. package/dist/ui/chart-group-by-selector.d.mts +3 -1
  32. package/dist/ui/chart-group-by-selector.mjs +4 -1
  33. package/dist/ui/chart-metric-selector.mjs +2 -2
  34. package/dist/ui/chart-select.mjs +9 -10
  35. package/dist/ui/chart-source-switcher.d.mts +3 -1
  36. package/dist/ui/chart-source-switcher.mjs +4 -2
  37. package/dist/ui/chart-time-bucket-selector.d.mts +3 -1
  38. package/dist/ui/chart-time-bucket-selector.mjs +4 -1
  39. package/dist/ui/chart-toolbar-overflow.mjs +48 -26
  40. package/dist/ui/chart-toolbar.d.mts +6 -2
  41. package/dist/ui/chart-toolbar.mjs +4 -0
  42. package/dist/ui/chart-type-selector.d.mts +7 -2
  43. package/dist/ui/chart-type-selector.mjs +155 -20
  44. package/dist/ui/chart-x-axis-selector.d.mts +3 -1
  45. package/dist/ui/chart-x-axis-selector.mjs +4 -1
  46. package/dist/ui/percent-stacked.mjs +36 -0
  47. package/dist/ui/theme.css +54 -49
  48. package/package.json +7 -6
@@ -1,86 +1,14 @@
1
+ import { DATE_RANGE_PRESETS, getPresetLabel } from "../core/date-range-presets.mjs";
1
2
  import { useChartContext } from "./chart-context.mjs";
2
- import { useMemo } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/ui/chart-date-range-panel.tsx
5
5
  /**
6
6
  * Date range panel content — reusable by both ChartDateRange (inside a popover)
7
7
  * and ChartToolbarOverflow (rendered inline).
8
8
  *
9
- * Shows preset buttons (All time, Last 7 days, etc.), a reference date
9
+ * Shows preset buttons (Auto, All time, Last 7 days, etc.), a reference date
10
10
  * column picker, and custom date inputs.
11
11
  */
12
- /**
13
- * Build the list of date range presets relative to "now".
14
- *
15
- * "All time" → null (no date filtering)
16
- * Other presets → { from: Date, to: null } (bounded filter)
17
- */
18
- function getPresets() {
19
- return [
20
- {
21
- label: "All time",
22
- buildFilter: () => null
23
- },
24
- {
25
- label: "Last 7 days",
26
- buildFilter: () => ({
27
- from: daysAgo(7),
28
- to: null
29
- })
30
- },
31
- {
32
- label: "Last 30 days",
33
- buildFilter: () => ({
34
- from: daysAgo(30),
35
- to: null
36
- })
37
- },
38
- {
39
- label: "Last 3 months",
40
- buildFilter: () => ({
41
- from: monthsAgo(3),
42
- to: null
43
- })
44
- },
45
- {
46
- label: "Last 6 months",
47
- buildFilter: () => ({
48
- from: monthsAgo(6),
49
- to: null
50
- })
51
- },
52
- {
53
- label: "Last 12 months",
54
- buildFilter: () => ({
55
- from: monthsAgo(12),
56
- to: null
57
- })
58
- },
59
- {
60
- label: "Year to date",
61
- buildFilter: () => ({
62
- from: startOfYear(),
63
- to: null
64
- })
65
- }
66
- ];
67
- }
68
- function daysAgo(n) {
69
- const d = /* @__PURE__ */ new Date();
70
- d.setDate(d.getDate() - n);
71
- d.setHours(0, 0, 0, 0);
72
- return d;
73
- }
74
- function monthsAgo(n) {
75
- const d = /* @__PURE__ */ new Date();
76
- d.setMonth(d.getMonth() - n);
77
- d.setHours(0, 0, 0, 0);
78
- return d;
79
- }
80
- function startOfYear() {
81
- const d = /* @__PURE__ */ new Date();
82
- return new Date(d.getFullYear(), 0, 1);
83
- }
84
12
  /** Format a Date as YYYY-MM-DD for native date input value. */
85
13
  function toInputValue(date) {
86
14
  if (!date) return "";
@@ -93,24 +21,14 @@ function fromInputValue(value) {
93
21
  return Number.isNaN(d.getTime()) ? null : d;
94
22
  }
95
23
  /**
96
- * Determine which preset label matches the current filter, or "Custom".
97
- * null → "All time" (no date filtering)
98
- * Compares dates at day-level precision.
24
+ * Resolve the display label for the current date range state.
25
+ *
26
+ * When a preset is active, returns the preset label.
27
+ * When no preset is active (custom range), returns "Custom".
99
28
  */
100
- function resolvePresetLabel(filter) {
101
- if (filter === null) return "All time";
102
- const presets = getPresets();
103
- for (const preset of presets) {
104
- const pf = preset.buildFilter();
105
- if (pf === null) continue;
106
- if (sameDay(pf.from, filter.from) && sameDay(pf.to, filter.to)) return preset.label;
107
- }
108
- return "Custom";
109
- }
110
- function sameDay(a, b) {
111
- if (!a && !b) return true;
112
- if (!a || !b) return false;
113
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
29
+ function resolvePresetLabel(dateRangePreset) {
30
+ if (dateRangePreset === null) return "Custom";
31
+ return getPresetLabel(dateRangePreset);
114
32
  }
115
33
  /**
116
34
  * Date range panel content (no popover wrapper).
@@ -119,13 +37,11 @@ function sameDay(a, b) {
119
37
  * @property className - Additional CSS classes
120
38
  */
121
39
  function ChartDateRangePanel({ onClose, className }) {
122
- const { dateRangeFilter, setDateRangeFilter, referenceDateId, setReferenceDateId, availableDateColumns } = useChartContext();
123
- const presets = useMemo(() => getPresets(), []);
124
- const activeLabel = resolvePresetLabel(dateRangeFilter);
40
+ const { dateRangePreset, setDateRangePreset, dateRangeFilter, setDateRangeFilter, referenceDateId, setReferenceDateId, availableDateColumns } = useChartContext();
125
41
  const hasMultipleDateColumns = availableDateColumns.length > 1;
126
- const handlePreset = (preset) => {
127
- setDateRangeFilter(preset.buildFilter());
128
- if (preset.label !== "Custom") onClose?.();
42
+ const handlePreset = (presetId) => {
43
+ setDateRangePreset(presetId);
44
+ onClose?.();
129
45
  };
130
46
  const handleCustomFrom = (value) => {
131
47
  setDateRangeFilter({
@@ -166,12 +82,14 @@ function ChartDateRangePanel({ onClose, className }) {
166
82
  children: "Range"
167
83
  }), /* @__PURE__ */ jsx("div", {
168
84
  className: "grid grid-cols-2 gap-1",
169
- children: presets.map((preset) => {
85
+ children: DATE_RANGE_PRESETS.map((preset) => {
86
+ const isActive = dateRangePreset === preset.id;
170
87
  return /* @__PURE__ */ jsx("button", {
171
- onClick: () => handlePreset(preset),
172
- className: `rounded-md px-2 py-1.5 text-left text-xs transition-colors ${activeLabel === preset.label ? "bg-primary/10 font-medium text-primary" : "text-foreground hover:bg-muted"}`,
88
+ onClick: () => handlePreset(preset.id),
89
+ title: preset.description,
90
+ className: `rounded-md px-2 py-1.5 text-left text-xs transition-colors ${isActive ? "bg-primary/10 font-medium text-primary" : "text-foreground hover:bg-muted"}`,
173
91
  children: preset.label
174
- }, preset.label);
92
+ }, preset.id);
175
93
  })
176
94
  })]
177
95
  }),
@@ -24,12 +24,12 @@ function formatDate(date) {
24
24
  * Also serves as the reference date column picker when multiple date columns exist.
25
25
  */
26
26
  function ChartDateRange({ className }) {
27
- const { dateRange, dateRangeFilter, availableDateColumns } = useChartContext();
27
+ const { dateRange, dateRangePreset, availableDateColumns } = useChartContext();
28
28
  const [isOpen, setIsOpen] = useState(false);
29
29
  const triggerRef = useRef(null);
30
30
  if (availableDateColumns.length === 0) return null;
31
- const activeLabel = resolvePresetLabel(dateRangeFilter);
32
- const isFiltered = dateRangeFilter !== null;
31
+ const activeLabel = resolvePresetLabel(dateRangePreset);
32
+ const isFiltered = dateRangePreset !== "all-time";
33
33
  const hasRange = dateRange?.min && dateRange?.max;
34
34
  return /* @__PURE__ */ jsxs("div", {
35
35
  className,
@@ -5,12 +5,16 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
5
  * Debug panel — shows raw data, transformed data, series, and current state.
6
6
  * Drop `<ChartDebug />` inside a `<Chart>` to inspect what's happening.
7
7
  */
8
- /** Debug panel that renders chart internals as formatted JSON. */
8
+ /** Debug panel that renders chart internals as formatted JSON.
9
+ *
10
+ * @param className - Additional CSS classes for the debug panel.
11
+ * @param defaultOpen - Whether the debug panel should be open by default. (default: false)
12
+ */
9
13
  declare function ChartDebug({
10
14
  className,
11
15
  defaultOpen
12
16
  }: {
13
- className?: string;
17
+ /** Additional CSS classes for the debug panel. */className?: string; /** Whether the debug panel should be open by default. (default: false) */
14
18
  defaultOpen?: boolean;
15
19
  }): react_jsx_runtime0.JSX.Element;
16
20
  //#endregion
@@ -112,7 +112,11 @@ function TabButton({ tab, isActive, onClick }) {
112
112
  children: tab.label
113
113
  });
114
114
  }
115
- /** Debug panel that renders chart internals as formatted JSON. */
115
+ /** Debug panel that renders chart internals as formatted JSON.
116
+ *
117
+ * @param className - Additional CSS classes for the debug panel.
118
+ * @param defaultOpen - Whether the debug panel should be open by default. (default: false)
119
+ */
116
120
  function ChartDebug({ className, defaultOpen = false }) {
117
121
  const chart = useChartContext();
118
122
  const [isOpen, setIsOpen] = useState(defaultOpen);
@@ -6,9 +6,11 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
6
6
  */
7
7
  /** Custom dropdown to select the groupBy column. */
8
8
  declare function ChartGroupBySelector({
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 { ChartGroupBySelector };
@@ -2,12 +2,13 @@ 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 { Layers } from "lucide-react";
5
6
  //#region src/ui/chart-group-by-selector.tsx
6
7
  /**
7
8
  * GroupBy selector — premium custom dropdown replacing native <select>.
8
9
  */
9
10
  /** Custom dropdown to select the groupBy column. */
10
- function ChartGroupBySelector({ className }) {
11
+ function ChartGroupBySelector({ className, hideIcon }) {
11
12
  const { chartType, groupById, setGroupBy, availableGroupBys } = useChartContext();
12
13
  if (!CHART_TYPE_CONFIG[chartType].supportsGrouping || availableGroupBys.length === 0) return null;
13
14
  const options = [{
@@ -22,6 +23,8 @@ function ChartGroupBySelector({ className }) {
22
23
  options,
23
24
  onChange: (v) => setGroupBy(v || null),
24
25
  ariaLabel: "Group by",
26
+ icon: Layers,
27
+ hideIcon,
25
28
  className
26
29
  });
27
30
  }
@@ -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, TrendingUpDown } from "lucide-react";
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(TrendingUpDown, { className: "h-3 w-3" }),
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",
@@ -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: [/* @__PURE__ */ jsx("span", {
41
- className: "truncate",
42
- children: selected?.label ?? value
43
- }), /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform ${isOpen ? "rotate-180" : ""}` })]
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 entry = CONTROL_REGISTRY[controlId];
161
- const Component = entry.component;
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-1 py-1.5",
164
- children: [/* @__PURE__ */ jsx("div", {
165
- className: "shrink-0 text-xs text-muted-foreground",
166
- children: entry.label
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 entry = CONTROL_REGISTRY[controlId];
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: "shrink-0 text-xs text-muted-foreground",
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, dateRangeFilter } = useChartContext();
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": return /* @__PURE__ */ jsx("span", { children: resolvePresetLabel(dateRangeFilter) });
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
  }
@@ -10,8 +10,8 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
10
10
  * @property hidden - Control IDs to completely hide (not in toolbar, not in overflow)
11
11
  */
12
12
  type ChartToolbarProps = {
13
- className?: string;
14
- pinned?: readonly ControlId[];
13
+ /** Additional CSS classes for the toolbar container. */className?: string; /** Control IDs to always show in the toolbar row (outside the overflow menu) */
14
+ pinned?: readonly ControlId[]; /** Control IDs to completely hide (not in toolbar, not in overflow) */
15
15
  hidden?: readonly ControlId[];
16
16
  };
17
17
  /**
@@ -19,6 +19,10 @@ type ChartToolbarProps = {
19
19
  *
20
20
  * Controls are rendered in registry order. Each sub-component still
21
21
  * auto-hides when not relevant (e.g. time bucket only shows for date X-axis).
22
+ *
23
+ * @param className - Additional CSS classes for the toolbar container
24
+ * @param pinned - Control IDs to always show in the toolbar row (outside the overflow menu)
25
+ * @param hidden - Control IDs to completely hide (not in toolbar, not in overflow)
22
26
  */
23
27
  declare function ChartToolbar({
24
28
  className,
@@ -32,6 +32,10 @@ const DEFAULT_PINNED_CONTROLS = ["dateRange"];
32
32
  *
33
33
  * Controls are rendered in registry order. Each sub-component still
34
34
  * auto-hides when not relevant (e.g. time bucket only shows for date X-axis).
35
+ *
36
+ * @param className - Additional CSS classes for the toolbar container
37
+ * @param pinned - Control IDs to always show in the toolbar row (outside the overflow menu)
38
+ * @param hidden - Control IDs to completely hide (not in toolbar, not in overflow)
35
39
  */
36
40
  function ChartToolbar({ className, pinned = DEFAULT_PINNED_CONTROLS, hidden = [] }) {
37
41
  const pinnedSet = useMemo(() => new Set(pinned), [pinned]);
@@ -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 for bar/line/area/pie/donut.
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 to select the chart type. */
12
+ /** Inline toggle buttons with variant dropdown for chart type selection. */
8
13
  declare function ChartTypeSelector({
9
14
  className
10
15
  }: {