@matthieumordrel/chart-studio 0.2.0 → 0.2.2

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 (175) hide show
  1. package/README.md +1 -1
  2. package/dist/core/chart-capabilities.d.mts +48 -0
  3. package/dist/core/chart-capabilities.mjs +55 -0
  4. package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
  5. package/dist/core/colors.mjs +55 -0
  6. package/dist/core/config-utils.mjs +79 -0
  7. package/dist/core/date-utils.mjs +49 -0
  8. package/dist/core/define-chart-schema.d.mts +106 -0
  9. package/dist/core/define-chart-schema.mjs +47 -0
  10. package/dist/core/formatting.mjs +349 -0
  11. package/dist/core/infer-columns.d.mts +9 -0
  12. package/dist/core/infer-columns.mjs +481 -0
  13. package/dist/core/metric-utils.d.mts +13 -0
  14. package/dist/core/metric-utils.mjs +121 -0
  15. package/dist/core/pipeline-data-points.mjs +212 -0
  16. package/dist/core/pipeline-helpers.mjs +85 -0
  17. package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
  18. package/dist/core/pipeline.mjs +153 -0
  19. package/dist/core/types.d.mts +957 -0
  20. package/dist/core/use-chart-options.d.mts +64 -0
  21. package/dist/core/use-chart-options.mjs +7 -0
  22. package/dist/core/use-chart-resolvers.mjs +34 -0
  23. package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
  24. package/dist/core/use-chart.mjs +299 -0
  25. package/dist/index.d.mts +10 -0
  26. package/dist/index.mjs +8 -0
  27. package/dist/ui/chart-axis-ticks.mjs +65 -0
  28. package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
  29. package/dist/ui/chart-canvas.mjs +461 -0
  30. package/dist/ui/chart-context.d.mts +92 -0
  31. package/dist/ui/chart-context.mjs +112 -0
  32. package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
  33. package/dist/ui/chart-date-range-badge.mjs +49 -0
  34. package/dist/ui/chart-date-range-panel.d.mts +18 -0
  35. package/dist/ui/chart-date-range-panel.mjs +208 -0
  36. package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
  37. package/dist/ui/chart-date-range.mjs +67 -0
  38. package/dist/ui/chart-debug.d.mts +17 -0
  39. package/dist/ui/chart-debug.mjs +169 -0
  40. package/dist/ui/chart-dropdown.mjs +92 -0
  41. package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
  42. package/dist/ui/chart-filters-panel.mjs +132 -0
  43. package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
  44. package/dist/ui/chart-filters.mjs +48 -0
  45. package/dist/ui/chart-group-by-selector.d.mts +14 -0
  46. package/dist/ui/chart-group-by-selector.mjs +29 -0
  47. package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
  48. package/dist/ui/chart-metric-panel.mjs +172 -0
  49. package/dist/ui/chart-metric-selector.d.mts +16 -0
  50. package/dist/ui/chart-metric-selector.mjs +50 -0
  51. package/dist/ui/chart-select.mjs +62 -0
  52. package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
  53. package/dist/ui/chart-source-switcher.mjs +54 -0
  54. package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
  55. package/dist/ui/chart-time-bucket-selector.mjs +34 -0
  56. package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
  57. package/dist/ui/chart-toolbar-overflow.mjs +209 -0
  58. package/dist/ui/chart-toolbar.d.mts +29 -0
  59. package/dist/ui/chart-toolbar.mjs +56 -0
  60. package/dist/ui/chart-type-selector.d.mts +14 -0
  61. package/dist/ui/chart-type-selector.mjs +33 -0
  62. package/dist/ui/chart-x-axis-selector.d.mts +14 -0
  63. package/dist/ui/chart-x-axis-selector.mjs +25 -0
  64. package/dist/ui/index.d.mts +19 -0
  65. package/dist/ui/index.mjs +18 -0
  66. package/dist/ui/toolbar-types.d.mts +7 -0
  67. package/dist/ui/toolbar-types.mjs +83 -0
  68. package/package.json +11 -10
  69. package/dist/core/chart-capabilities.d.ts +0 -60
  70. package/dist/core/chart-capabilities.d.ts.map +0 -1
  71. package/dist/core/chart-capabilities.js +0 -54
  72. package/dist/core/colors.d.ts.map +0 -1
  73. package/dist/core/colors.js +0 -54
  74. package/dist/core/config-utils.d.ts +0 -43
  75. package/dist/core/config-utils.d.ts.map +0 -1
  76. package/dist/core/config-utils.js +0 -80
  77. package/dist/core/date-utils.d.ts +0 -29
  78. package/dist/core/date-utils.d.ts.map +0 -1
  79. package/dist/core/date-utils.js +0 -58
  80. package/dist/core/define-chart-schema.d.ts +0 -105
  81. package/dist/core/define-chart-schema.d.ts.map +0 -1
  82. package/dist/core/define-chart-schema.js +0 -44
  83. package/dist/core/formatting.d.ts +0 -47
  84. package/dist/core/formatting.d.ts.map +0 -1
  85. package/dist/core/formatting.js +0 -396
  86. package/dist/core/index.d.ts +0 -17
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/index.js +0 -12
  89. package/dist/core/infer-columns.d.ts +0 -6
  90. package/dist/core/infer-columns.d.ts.map +0 -1
  91. package/dist/core/infer-columns.js +0 -512
  92. package/dist/core/metric-utils.d.ts +0 -43
  93. package/dist/core/metric-utils.d.ts.map +0 -1
  94. package/dist/core/metric-utils.js +0 -141
  95. package/dist/core/pipeline-data-points.d.ts +0 -23
  96. package/dist/core/pipeline-data-points.d.ts.map +0 -1
  97. package/dist/core/pipeline-data-points.js +0 -235
  98. package/dist/core/pipeline-helpers.d.ts +0 -38
  99. package/dist/core/pipeline-helpers.d.ts.map +0 -1
  100. package/dist/core/pipeline-helpers.js +0 -97
  101. package/dist/core/pipeline.d.ts.map +0 -1
  102. package/dist/core/pipeline.js +0 -156
  103. package/dist/core/types.d.ts +0 -1109
  104. package/dist/core/types.d.ts.map +0 -1
  105. package/dist/core/types.js +0 -14
  106. package/dist/core/use-chart-options.d.ts +0 -66
  107. package/dist/core/use-chart-options.d.ts.map +0 -1
  108. package/dist/core/use-chart-options.js +0 -4
  109. package/dist/core/use-chart-resolvers.d.ts +0 -14
  110. package/dist/core/use-chart-resolvers.d.ts.map +0 -1
  111. package/dist/core/use-chart-resolvers.js +0 -41
  112. package/dist/core/use-chart.d.ts.map +0 -1
  113. package/dist/core/use-chart.js +0 -265
  114. package/dist/index.d.ts +0 -36
  115. package/dist/index.d.ts.map +0 -1
  116. package/dist/index.js +0 -35
  117. package/dist/ui/chart-axis-ticks.d.ts +0 -35
  118. package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
  119. package/dist/ui/chart-axis-ticks.js +0 -79
  120. package/dist/ui/chart-canvas.d.ts.map +0 -1
  121. package/dist/ui/chart-canvas.js +0 -337
  122. package/dist/ui/chart-context.d.ts +0 -89
  123. package/dist/ui/chart-context.d.ts.map +0 -1
  124. package/dist/ui/chart-context.js +0 -128
  125. package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
  126. package/dist/ui/chart-date-range-badge.js +0 -30
  127. package/dist/ui/chart-date-range-panel.d.ts +0 -25
  128. package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
  129. package/dist/ui/chart-date-range-panel.js +0 -125
  130. package/dist/ui/chart-date-range.d.ts.map +0 -1
  131. package/dist/ui/chart-date-range.js +0 -37
  132. package/dist/ui/chart-debug.d.ts +0 -10
  133. package/dist/ui/chart-debug.d.ts.map +0 -1
  134. package/dist/ui/chart-debug.js +0 -126
  135. package/dist/ui/chart-dropdown.d.ts +0 -35
  136. package/dist/ui/chart-dropdown.d.ts.map +0 -1
  137. package/dist/ui/chart-dropdown.js +0 -76
  138. package/dist/ui/chart-filters-panel.d.ts.map +0 -1
  139. package/dist/ui/chart-filters-panel.js +0 -46
  140. package/dist/ui/chart-filters.d.ts.map +0 -1
  141. package/dist/ui/chart-filters.js +0 -26
  142. package/dist/ui/chart-group-by-selector.d.ts +0 -8
  143. package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
  144. package/dist/ui/chart-group-by-selector.js +0 -19
  145. package/dist/ui/chart-metric-panel.d.ts.map +0 -1
  146. package/dist/ui/chart-metric-panel.js +0 -118
  147. package/dist/ui/chart-metric-selector.d.ts +0 -10
  148. package/dist/ui/chart-metric-selector.d.ts.map +0 -1
  149. package/dist/ui/chart-metric-selector.js +0 -27
  150. package/dist/ui/chart-select.d.ts +0 -25
  151. package/dist/ui/chart-select.d.ts.map +0 -1
  152. package/dist/ui/chart-select.js +0 -35
  153. package/dist/ui/chart-source-switcher.d.ts.map +0 -1
  154. package/dist/ui/chart-source-switcher.js +0 -31
  155. package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
  156. package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
  157. package/dist/ui/chart-time-bucket-selector.js +0 -25
  158. package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
  159. package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
  160. package/dist/ui/chart-toolbar-overflow.js +0 -109
  161. package/dist/ui/chart-toolbar.d.ts +0 -45
  162. package/dist/ui/chart-toolbar.d.ts.map +0 -1
  163. package/dist/ui/chart-toolbar.js +0 -44
  164. package/dist/ui/chart-type-selector.d.ts +0 -8
  165. package/dist/ui/chart-type-selector.d.ts.map +0 -1
  166. package/dist/ui/chart-type-selector.js +0 -22
  167. package/dist/ui/chart-x-axis-selector.d.ts +0 -8
  168. package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
  169. package/dist/ui/chart-x-axis-selector.js +0 -14
  170. package/dist/ui/index.d.ts +0 -25
  171. package/dist/ui/index.d.ts.map +0 -1
  172. package/dist/ui/index.js +0 -23
  173. package/dist/ui/toolbar-types.d.ts +0 -43
  174. package/dist/ui/toolbar-types.d.ts.map +0 -1
  175. package/dist/ui/toolbar-types.js +0 -50
@@ -0,0 +1,132 @@
1
+ import { useChartContext } from "./chart-context.mjs";
2
+ import { useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { ChevronDown, Eraser } from "lucide-react";
5
+ //#region src/ui/chart-filters-panel.tsx
6
+ /**
7
+ * Filters panel content — reusable by both ChartFilters (inside a popover)
8
+ * and ChartToolbarOverflow (rendered inline).
9
+ *
10
+ * Shows filterable columns as collapsible sections with checkbox options.
11
+ * Each column section can be expanded/collapsed independently.
12
+ */
13
+ /** Maximum number of options to show per column before collapsing. */
14
+ const MAX_VISIBLE_OPTIONS = 6;
15
+ /**
16
+ * Filters panel content (no popover wrapper).
17
+ *
18
+ * @property className - Additional CSS classes
19
+ * @property showHeader - When true (default), shows "X filter(s) active · Filters" + clear icon.
20
+ * Set false when the header is rendered by the parent (e.g. overflow DetailPage).
21
+ */
22
+ function ChartFiltersPanel({ className, showHeader = true }) {
23
+ const { availableFilters, filters, toggleFilter, clearAllFilters } = useChartContext();
24
+ const activeCount = [...filters.values()].reduce((sum, set) => sum + set.size, 0);
25
+ if (availableFilters.length === 0) return /* @__PURE__ */ jsx("div", {
26
+ className,
27
+ children: /* @__PURE__ */ jsx("div", {
28
+ className: "py-6 text-center text-xs text-muted-foreground",
29
+ children: "No filterable columns"
30
+ })
31
+ });
32
+ return /* @__PURE__ */ jsxs("div", {
33
+ className,
34
+ children: [showHeader && /* @__PURE__ */ jsxs("div", {
35
+ className: "mb-3 flex items-center justify-between gap-2",
36
+ children: [/* @__PURE__ */ jsxs("div", {
37
+ className: "truncate text-xs font-semibold text-foreground",
38
+ children: [activeCount > 0 && /* @__PURE__ */ jsxs("span", {
39
+ className: "text-muted-foreground",
40
+ children: [
41
+ activeCount,
42
+ " filter",
43
+ activeCount !== 1 ? "s" : "",
44
+ " active ·",
45
+ " "
46
+ ]
47
+ }), "Filters"]
48
+ }), activeCount > 0 && /* @__PURE__ */ jsx("button", {
49
+ onClick: () => clearAllFilters(),
50
+ className: "shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
51
+ "aria-label": "Clear all filters",
52
+ children: /* @__PURE__ */ jsx(Eraser, { className: "h-3.5 w-3.5" })
53
+ })]
54
+ }), /* @__PURE__ */ jsx("div", {
55
+ className: "space-y-1",
56
+ children: availableFilters.map((filter) => /* @__PURE__ */ jsx(FilterSection, {
57
+ filter,
58
+ activeValues: filters.get(filter.columnId),
59
+ onToggle: (value) => toggleFilter(filter.columnId, value)
60
+ }, filter.columnId))
61
+ })]
62
+ });
63
+ }
64
+ /** A collapsible filter column section with checkable options. */
65
+ function FilterSection({ filter, activeValues, onToggle }) {
66
+ const [isOpen, setIsOpen] = useState(true);
67
+ const [expanded, setExpanded] = useState(false);
68
+ const visibleOptions = expanded ? filter.options : filter.options.slice(0, MAX_VISIBLE_OPTIONS);
69
+ const hasMore = filter.options.length > MAX_VISIBLE_OPTIONS;
70
+ const activeCount = activeValues?.size ?? 0;
71
+ return /* @__PURE__ */ jsxs("div", {
72
+ className: "rounded-lg",
73
+ children: [/* @__PURE__ */ jsxs("button", {
74
+ onClick: () => setIsOpen(!isOpen),
75
+ className: "flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-muted/50",
76
+ children: [
77
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${isOpen ? "" : "-rotate-90"}` }),
78
+ /* @__PURE__ */ jsx("span", {
79
+ className: "text-[11px] font-semibold text-foreground",
80
+ children: filter.label
81
+ }),
82
+ activeCount > 0 && /* @__PURE__ */ jsx("span", {
83
+ className: "ml-auto flex h-4 min-w-4 items-center justify-center rounded-full bg-primary/10 px-1.5 text-[9px] font-semibold text-primary",
84
+ children: activeCount
85
+ })
86
+ ]
87
+ }), isOpen && /* @__PURE__ */ jsxs("div", {
88
+ className: "pb-1 pl-2 pr-1 pt-0.5",
89
+ children: [/* @__PURE__ */ jsx("div", {
90
+ className: "space-y-px",
91
+ children: visibleOptions.map((option) => {
92
+ const isActive = activeValues?.has(option.value) ?? false;
93
+ return /* @__PURE__ */ jsxs("button", {
94
+ onClick: () => onToggle(option.value),
95
+ className: `flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-xs transition-colors ${isActive ? "bg-primary/8 text-primary" : "text-foreground hover:bg-muted/60"}`,
96
+ children: [
97
+ /* @__PURE__ */ jsx("div", {
98
+ className: `flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors ${isActive ? "border-primary bg-primary" : "border-muted-foreground/30 bg-background"}`,
99
+ children: isActive && /* @__PURE__ */ jsx("svg", {
100
+ xmlns: "http://www.w3.org/2000/svg",
101
+ width: "10",
102
+ height: "10",
103
+ viewBox: "0 0 24 24",
104
+ fill: "none",
105
+ stroke: "white",
106
+ strokeWidth: "3",
107
+ strokeLinecap: "round",
108
+ strokeLinejoin: "round",
109
+ children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
110
+ })
111
+ }),
112
+ /* @__PURE__ */ jsx("span", {
113
+ className: "flex-1 truncate",
114
+ children: option.label
115
+ }),
116
+ /* @__PURE__ */ jsx("span", {
117
+ className: "shrink-0 tabular-nums text-[10px] text-muted-foreground/70",
118
+ children: option.count
119
+ })
120
+ ]
121
+ }, option.value);
122
+ })
123
+ }), hasMore && /* @__PURE__ */ jsx("button", {
124
+ onClick: () => setExpanded(!expanded),
125
+ className: "mt-1 w-full rounded-md px-2 py-1 text-left text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
126
+ children: expanded ? "Show less" : `Show ${filter.options.length - MAX_VISIBLE_OPTIONS} more…`
127
+ })]
128
+ })]
129
+ });
130
+ }
131
+ //#endregion
132
+ export { ChartFiltersPanel };
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-filters.d.ts
1
4
  /**
2
5
  * Chart filters — compact button that shows active filter count.
3
6
  * Clicking reveals a popover wrapping ChartFiltersPanel.
@@ -6,7 +9,10 @@
6
9
  * Compact filter button + dropdown for all filterable columns.
7
10
  * Shows a count badge when filters are active.
8
11
  */
9
- export declare function ChartFilters({ className }: {
10
- className?: string;
11
- }): import("react/jsx-runtime").JSX.Element | null;
12
- //# sourceMappingURL=chart-filters.d.ts.map
12
+ declare function ChartFilters({
13
+ className
14
+ }: {
15
+ className?: string;
16
+ }): react_jsx_runtime0.JSX.Element | null;
17
+ //#endregion
18
+ export { ChartFilters };
@@ -0,0 +1,48 @@
1
+ import { useChartContext } from "./chart-context.mjs";
2
+ import { ChartDropdownPanel } from "./chart-dropdown.mjs";
3
+ import { ChartFiltersPanel } from "./chart-filters-panel.mjs";
4
+ import { useRef, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Filter } from "lucide-react";
7
+ //#region src/ui/chart-filters.tsx
8
+ /**
9
+ * Chart filters — compact button that shows active filter count.
10
+ * Clicking reveals a popover wrapping ChartFiltersPanel.
11
+ */
12
+ /**
13
+ * Compact filter button + dropdown for all filterable columns.
14
+ * Shows a count badge when filters are active.
15
+ */
16
+ function ChartFilters({ className }) {
17
+ const { availableFilters, filters } = useChartContext();
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ const triggerRef = useRef(null);
20
+ if (availableFilters.length === 0) return null;
21
+ const activeCount = [...filters.values()].reduce((sum, set) => sum + set.size, 0);
22
+ const isActive = activeCount > 0;
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ className,
25
+ children: [/* @__PURE__ */ jsxs("button", {
26
+ ref: triggerRef,
27
+ onClick: () => setIsOpen(!isOpen),
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
+ children: [
30
+ /* @__PURE__ */ jsx(Filter, { className: "h-3 w-3" }),
31
+ "Filters",
32
+ isActive && /* @__PURE__ */ jsx("span", {
33
+ className: "flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold text-primary-foreground",
34
+ children: activeCount
35
+ })
36
+ ]
37
+ }), /* @__PURE__ */ jsx(ChartDropdownPanel, {
38
+ isOpen,
39
+ onClose: () => setIsOpen(false),
40
+ triggerRef,
41
+ width: 288,
42
+ className: "max-h-[420px] overflow-y-auto overscroll-contain p-3",
43
+ children: /* @__PURE__ */ jsx(ChartFiltersPanel, {})
44
+ })]
45
+ });
46
+ }
47
+ //#endregion
48
+ export { ChartFilters };
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-group-by-selector.d.ts
4
+ /**
5
+ * GroupBy selector — premium custom dropdown replacing native <select>.
6
+ */
7
+ /** Custom dropdown to select the groupBy column. */
8
+ declare function ChartGroupBySelector({
9
+ className
10
+ }: {
11
+ className?: string;
12
+ }): react_jsx_runtime0.JSX.Element | null;
13
+ //#endregion
14
+ export { ChartGroupBySelector };
@@ -0,0 +1,29 @@
1
+ import { CHART_TYPE_CONFIG } from "../core/chart-capabilities.mjs";
2
+ import { useChartContext } from "./chart-context.mjs";
3
+ import { ChartSelect } from "./chart-select.mjs";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/ui/chart-group-by-selector.tsx
6
+ /**
7
+ * GroupBy selector — premium custom dropdown replacing native <select>.
8
+ */
9
+ /** Custom dropdown to select the groupBy column. */
10
+ function ChartGroupBySelector({ className }) {
11
+ const { chartType, groupById, setGroupBy, availableGroupBys } = useChartContext();
12
+ if (!CHART_TYPE_CONFIG[chartType].supportsGrouping || availableGroupBys.length === 0) return null;
13
+ const options = [{
14
+ value: "",
15
+ label: "No grouping"
16
+ }, ...availableGroupBys.map((col) => ({
17
+ value: col.id,
18
+ label: col.label
19
+ }))];
20
+ return /* @__PURE__ */ jsx(ChartSelect, {
21
+ value: groupById ?? "",
22
+ options,
23
+ onChange: (v) => setGroupBy(v || null),
24
+ ariaLabel: "Group by",
25
+ className
26
+ });
27
+ }
28
+ //#endregion
29
+ export { ChartGroupBySelector };
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-metric-panel.d.ts
1
4
  /**
2
5
  * Metric panel content — reusable by both ChartMetricSelector (inside a popover)
3
6
  * and ChartToolbarOverflow (rendered inline).
@@ -11,8 +14,12 @@
11
14
  * @property onClose - Optional callback when user makes a definitive selection (e.g. "Count")
12
15
  * @property className - Additional CSS classes
13
16
  */
14
- export declare function ChartMetricPanel({ onClose, className }: {
15
- onClose?: () => void;
16
- className?: string;
17
- }): import("react/jsx-runtime").JSX.Element;
18
- //# sourceMappingURL=chart-metric-panel.d.ts.map
17
+ declare function ChartMetricPanel({
18
+ onClose,
19
+ className
20
+ }: {
21
+ onClose?: () => void;
22
+ className?: string;
23
+ }): react_jsx_runtime0.JSX.Element;
24
+ //#endregion
25
+ export { ChartMetricPanel };
@@ -0,0 +1,172 @@
1
+ import { DEFAULT_METRIC, isAggregateMetric } from "../core/metric-utils.mjs";
2
+ import { useChartContext } from "./chart-context.mjs";
3
+ import { useMemo } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { ArrowDownToLine, ArrowUpToLine, Divide, Hash, Sigma } from "lucide-react";
6
+ //#region src/ui/chart-metric-panel.tsx
7
+ const AGGREGATE_OPTIONS = [
8
+ {
9
+ fn: "sum",
10
+ label: "Sum",
11
+ shortLabel: "Sum",
12
+ icon: /* @__PURE__ */ jsx(Sigma, { className: "h-3.5 w-3.5 shrink-0" })
13
+ },
14
+ {
15
+ fn: "avg",
16
+ label: "Average",
17
+ shortLabel: "Avg",
18
+ icon: /* @__PURE__ */ jsx(Divide, { className: "h-3.5 w-3.5 shrink-0" })
19
+ },
20
+ {
21
+ fn: "min",
22
+ label: "Minimum",
23
+ shortLabel: "Min",
24
+ icon: /* @__PURE__ */ jsx(ArrowDownToLine, { className: "h-3.5 w-3.5 shrink-0" })
25
+ },
26
+ {
27
+ fn: "max",
28
+ label: "Maximum",
29
+ shortLabel: "Max",
30
+ icon: /* @__PURE__ */ jsx(ArrowUpToLine, { className: "h-3.5 w-3.5 shrink-0" })
31
+ }
32
+ ];
33
+ /** Aggregates where the "Exclude zeros" toggle is relevant */
34
+ const ZERO_TOGGLE_AGGREGATES = new Set([
35
+ "avg",
36
+ "min",
37
+ "max"
38
+ ]);
39
+ /**
40
+ * Extract number columns from the chart column list.
41
+ */
42
+ function getNumberColumns(columns) {
43
+ return columns.filter((column) => column.type === "number");
44
+ }
45
+ /**
46
+ * Group the allowed aggregate metrics by number column while preserving column order.
47
+ */
48
+ function getMetricColumnGroups(availableMetrics, columns) {
49
+ const aggregateMap = /* @__PURE__ */ new Map();
50
+ for (const metric of availableMetrics) {
51
+ if (metric.kind !== "aggregate") continue;
52
+ const aggregates = aggregateMap.get(metric.columnId) ?? /* @__PURE__ */ new Set();
53
+ aggregates.add(metric.aggregate);
54
+ aggregateMap.set(metric.columnId, aggregates);
55
+ }
56
+ return getNumberColumns(columns).filter((column) => aggregateMap.has(column.id)).map((column) => ({
57
+ columnId: column.id,
58
+ label: column.label,
59
+ aggregates: [...aggregateMap.get(column.id) ?? /* @__PURE__ */ new Set()]
60
+ }));
61
+ }
62
+ /** A single number column section with aggregate buttons + optional zero toggle. */
63
+ function MetricColumnGroup({ group, isActiveColumn, activeAggregate, includeZeros, onSelectAggregate, onToggleZeros }) {
64
+ const isZeroToggleEnabled = isActiveColumn && activeAggregate !== null && ZERO_TOGGLE_AGGREGATES.has(activeAggregate);
65
+ const isZeroToggleDisabled = isActiveColumn && activeAggregate === "sum";
66
+ return /* @__PURE__ */ jsxs("div", {
67
+ className: "space-y-2",
68
+ children: [/* @__PURE__ */ jsxs("div", {
69
+ className: "flex items-center justify-between",
70
+ children: [/* @__PURE__ */ jsx("div", {
71
+ className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",
72
+ children: group.label
73
+ }), isActiveColumn && /* @__PURE__ */ jsxs("button", {
74
+ onClick: isZeroToggleDisabled ? void 0 : onToggleZeros,
75
+ className: `flex items-center gap-1.5 text-[10px] transition-colors ${isZeroToggleDisabled ? "cursor-not-allowed text-muted-foreground/30" : "text-muted-foreground hover:text-foreground"}`,
76
+ disabled: isZeroToggleDisabled,
77
+ title: isZeroToggleDisabled ? "Not applicable for Sum" : "Exclude zero values from calculation",
78
+ children: [/* @__PURE__ */ jsx("div", {
79
+ className: `flex h-3 w-3 shrink-0 items-center justify-center rounded-sm border transition-colors ${isZeroToggleEnabled && !includeZeros ? "border-primary bg-primary" : isZeroToggleDisabled ? "border-muted-foreground/20" : "border-muted-foreground/30"}`,
80
+ children: isZeroToggleEnabled && !includeZeros && /* @__PURE__ */ jsx("svg", {
81
+ xmlns: "http://www.w3.org/2000/svg",
82
+ width: "8",
83
+ height: "8",
84
+ viewBox: "0 0 24 24",
85
+ fill: "none",
86
+ stroke: "white",
87
+ strokeWidth: "3",
88
+ strokeLinecap: "round",
89
+ strokeLinejoin: "round",
90
+ children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
91
+ })
92
+ }), "Excl. zeros"]
93
+ })]
94
+ }), /* @__PURE__ */ jsx("div", {
95
+ className: "grid grid-cols-2 gap-1.5",
96
+ children: AGGREGATE_OPTIONS.filter((opt) => group.aggregates.includes(opt.fn)).map((opt) => {
97
+ return /* @__PURE__ */ jsxs("button", {
98
+ onClick: () => onSelectAggregate(opt.fn),
99
+ className: `inline-flex h-8 items-center justify-center gap-2 rounded-md border text-xs transition-colors ${isActiveColumn && activeAggregate === opt.fn ? "border-primary/50 bg-primary/10 font-medium text-primary" : "border-transparent bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground"}`,
100
+ title: opt.label,
101
+ children: [opt.icon, opt.shortLabel]
102
+ }, opt.fn);
103
+ })
104
+ })]
105
+ });
106
+ }
107
+ /**
108
+ * Metric panel content (no popover wrapper).
109
+ *
110
+ * @property onClose - Optional callback when user makes a definitive selection (e.g. "Count")
111
+ * @property className - Additional CSS classes
112
+ */
113
+ function ChartMetricPanel({ onClose, className }) {
114
+ const { metric, setMetric, columns, availableMetrics } = useChartContext();
115
+ const countMetricEnabled = useMemo(() => availableMetrics.some((candidate) => candidate.kind === "count"), [availableMetrics]);
116
+ const metricColumnGroups = useMemo(() => getMetricColumnGroups(availableMetrics, columns), [availableMetrics, columns]);
117
+ const isCount = metric.kind === "count";
118
+ const includeZeros = isAggregateMetric(metric) ? metric.includeZeros ?? true : true;
119
+ const handleSelectCount = () => {
120
+ if (!countMetricEnabled) return;
121
+ setMetric(DEFAULT_METRIC);
122
+ onClose?.();
123
+ };
124
+ const handleSelectAggregate = (columnId, fn) => {
125
+ setMetric({
126
+ kind: "aggregate",
127
+ columnId,
128
+ aggregate: fn,
129
+ includeZeros: isAggregateMetric(metric) && metric.columnId === columnId ? includeZeros : true
130
+ });
131
+ };
132
+ const handleToggleZeros = () => {
133
+ if (!isAggregateMetric(metric)) return;
134
+ setMetric({
135
+ ...metric,
136
+ includeZeros: !includeZeros
137
+ });
138
+ };
139
+ return /* @__PURE__ */ jsxs("div", {
140
+ className,
141
+ children: [
142
+ countMetricEnabled && /* @__PURE__ */ jsxs("button", {
143
+ onClick: handleSelectCount,
144
+ className: `flex w-full items-center gap-2.5 rounded-lg px-2.5 py-2 text-left text-xs transition-colors ${isCount ? "bg-primary/10 font-medium text-primary" : "text-foreground hover:bg-muted"}`,
145
+ children: [/* @__PURE__ */ jsx("div", {
146
+ className: `flex h-6 w-6 items-center justify-center rounded-md ${isCount ? "bg-primary/15 text-primary" : "bg-muted text-muted-foreground"}`,
147
+ children: /* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5" })
148
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
149
+ className: "font-medium",
150
+ children: "Count"
151
+ }), /* @__PURE__ */ jsx("div", {
152
+ className: "text-[10px] text-muted-foreground",
153
+ children: "Number of items"
154
+ })] })]
155
+ }),
156
+ countMetricEnabled && metricColumnGroups.length > 0 && /* @__PURE__ */ jsx("div", { className: "my-4 border-t border-border" }),
157
+ /* @__PURE__ */ jsx("div", {
158
+ className: "space-y-4",
159
+ children: metricColumnGroups.map((group) => /* @__PURE__ */ jsx(MetricColumnGroup, {
160
+ group,
161
+ isActiveColumn: isAggregateMetric(metric) && metric.columnId === group.columnId,
162
+ activeAggregate: isAggregateMetric(metric) && metric.columnId === group.columnId ? metric.aggregate : null,
163
+ includeZeros,
164
+ onSelectAggregate: (fn) => handleSelectAggregate(group.columnId, fn),
165
+ onToggleZeros: handleToggleZeros
166
+ }, group.columnId))
167
+ })
168
+ ]
169
+ });
170
+ }
171
+ //#endregion
172
+ export { ChartMetricPanel };
@@ -0,0 +1,16 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-metric-selector.d.ts
4
+ /**
5
+ * Metric selector — trigger button + popover wrapping ChartMetricPanel.
6
+ *
7
+ * Follows the same dropdown pattern as ChartFilters / ChartDateRange.
8
+ */
9
+ /** Styled popover to select the Y-axis metric with grouped aggregate buttons. */
10
+ declare function ChartMetricSelector({
11
+ className
12
+ }: {
13
+ className?: string;
14
+ }): react_jsx_runtime0.JSX.Element | null;
15
+ //#endregion
16
+ export { ChartMetricSelector };
@@ -0,0 +1,50 @@
1
+ import { getMetricLabel, isAggregateMetric } from "../core/metric-utils.mjs";
2
+ import { useChartContext } from "./chart-context.mjs";
3
+ import { ChartDropdownPanel } from "./chart-dropdown.mjs";
4
+ import { ChartMetricPanel } from "./chart-metric-panel.mjs";
5
+ import { useRef, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { ChevronDown, TrendingUpDown } from "lucide-react";
8
+ //#region src/ui/chart-metric-selector.tsx
9
+ /**
10
+ * Metric selector — trigger button + popover wrapping ChartMetricPanel.
11
+ *
12
+ * Follows the same dropdown pattern as ChartFilters / ChartDateRange.
13
+ */
14
+ /** Styled popover to select the Y-axis metric with grouped aggregate buttons. */
15
+ function ChartMetricSelector({ className }) {
16
+ const { metric, availableMetrics, columns } = useChartContext();
17
+ const [isOpen, setIsOpen] = useState(false);
18
+ const triggerRef = useRef(null);
19
+ if (availableMetrics.length <= 1) return null;
20
+ const isActive = !(metric.kind === "count");
21
+ const includeZeros = isAggregateMetric(metric) ? metric.includeZeros ?? true : true;
22
+ const label = getMetricLabel(metric, columns);
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ className,
25
+ children: [/* @__PURE__ */ jsxs("button", {
26
+ ref: triggerRef,
27
+ onClick: () => setIsOpen(!isOpen),
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
+ "aria-label": "Metric",
30
+ children: [
31
+ /* @__PURE__ */ jsx(TrendingUpDown, { className: "h-3 w-3" }),
32
+ /* @__PURE__ */ jsx("span", { children: label }),
33
+ isActive && !includeZeros && /* @__PURE__ */ jsx("span", {
34
+ className: "rounded bg-muted px-1 py-px text-[9px] font-normal text-muted-foreground",
35
+ children: "excl. 0"
36
+ }),
37
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/50 transition-transform ${isOpen ? "rotate-180" : ""}` })
38
+ ]
39
+ }), /* @__PURE__ */ jsx(ChartDropdownPanel, {
40
+ isOpen,
41
+ onClose: () => setIsOpen(false),
42
+ triggerRef,
43
+ width: 288,
44
+ className: "p-4",
45
+ children: /* @__PURE__ */ jsx(ChartMetricPanel, { onClose: () => setIsOpen(false) })
46
+ })]
47
+ });
48
+ }
49
+ //#endregion
50
+ export { ChartMetricSelector };
@@ -0,0 +1,62 @@
1
+ import { ChartDropdownPanel } from "./chart-dropdown.mjs";
2
+ import { useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { ChevronDown } from "lucide-react";
5
+ //#region src/ui/chart-select.tsx
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
+ * Premium styled select dropdown with highlight-only selection styling.
13
+ *
14
+ * @property value - Currently selected value
15
+ * @property options - Array of { value, label } options
16
+ * @property onChange - Callback when selection changes
17
+ * @property ariaLabel - Accessible label for the trigger
18
+ * @property className - Additional CSS classes
19
+ */
20
+ function ChartSelect({ value, options, onChange, ariaLabel, className }) {
21
+ const [isOpen, setIsOpen] = useState(false);
22
+ const triggerRef = useRef(null);
23
+ const selected = options.find((o) => o.value === value);
24
+ /** Toggle the dropdown. */
25
+ const handleToggle = () => {
26
+ setIsOpen((current) => !current);
27
+ };
28
+ /** Select an option and close the dropdown. */
29
+ const handleSelect = (optionValue) => {
30
+ onChange(optionValue);
31
+ setIsOpen(false);
32
+ };
33
+ return /* @__PURE__ */ jsxs("div", {
34
+ className,
35
+ children: [/* @__PURE__ */ jsxs("button", {
36
+ ref: triggerRef,
37
+ onClick: handleToggle,
38
+ 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
+ "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" : ""}` })]
44
+ }), /* @__PURE__ */ jsx(ChartDropdownPanel, {
45
+ isOpen,
46
+ onClose: () => setIsOpen(false),
47
+ triggerRef,
48
+ minWidth: "trigger",
49
+ className: "p-1",
50
+ children: options.map((option) => /* @__PURE__ */ jsx("button", {
51
+ onClick: () => handleSelect(option.value),
52
+ className: `flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors ${option.value === value ? "bg-primary/8 font-medium text-primary" : "text-foreground hover:bg-muted/60"}`,
53
+ children: /* @__PURE__ */ jsx("span", {
54
+ className: "truncate",
55
+ children: option.label
56
+ })
57
+ }, option.value))
58
+ })]
59
+ });
60
+ }
61
+ //#endregion
62
+ export { ChartSelect };
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-source-switcher.d.ts
1
4
  /**
2
5
  * Data source control — adapts based on single vs multi-source.
3
6
  *
@@ -10,7 +13,10 @@
10
13
  * - Single source → read-only badge: "[icon] Jobs · 1,247 records"
11
14
  * - Multi source → dropdown to switch between sources
12
15
  */
13
- export declare function ChartSourceSwitcher({ className }: {
14
- className?: string;
15
- }): import("react/jsx-runtime").JSX.Element;
16
- //# sourceMappingURL=chart-source-switcher.d.ts.map
16
+ declare function ChartSourceSwitcher({
17
+ className
18
+ }: {
19
+ className?: string;
20
+ }): react_jsx_runtime0.JSX.Element;
21
+ //#endregion
22
+ export { ChartSourceSwitcher };
@@ -0,0 +1,54 @@
1
+ import { useChartContext } from "./chart-context.mjs";
2
+ import { ChartSelect } from "./chart-select.mjs";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Database } from "lucide-react";
5
+ //#region src/ui/chart-source-switcher.tsx
6
+ /**
7
+ * Data source control — adapts based on single vs multi-source.
8
+ *
9
+ * Single source: read-only badge showing source label + record count.
10
+ * Multi source: dropdown to switch between data sources.
11
+ */
12
+ /** Format a number with locale-aware separators (e.g. 1,247). */
13
+ function formatCount(n) {
14
+ return n.toLocaleString("en-US");
15
+ }
16
+ /**
17
+ * Data source display/switcher.
18
+ *
19
+ * - Single source → read-only badge: "[icon] Jobs · 1,247 records"
20
+ * - Multi source → dropdown to switch between sources
21
+ */
22
+ function ChartSourceSwitcher({ className }) {
23
+ const { hasMultipleSources, sources, activeSourceId, setActiveSource, recordCount } = useChartContext();
24
+ if (!hasMultipleSources) {
25
+ const label = sources[0]?.label ?? "Unnamed Source";
26
+ return /* @__PURE__ */ jsxs("div", {
27
+ className: `inline-flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground ${className ?? ""}`,
28
+ children: [
29
+ /* @__PURE__ */ jsx(Database, { className: "h-3 w-3 shrink-0" }),
30
+ /* @__PURE__ */ jsx("span", {
31
+ className: "font-medium text-foreground",
32
+ children: label
33
+ }),
34
+ /* @__PURE__ */ jsx("span", {
35
+ className: "text-muted-foreground/40",
36
+ children: "·"
37
+ }),
38
+ /* @__PURE__ */ jsxs("span", { children: [formatCount(recordCount), " records"] })
39
+ ]
40
+ });
41
+ }
42
+ return /* @__PURE__ */ jsx(ChartSelect, {
43
+ value: activeSourceId,
44
+ options: sources.map((source) => ({
45
+ value: source.id,
46
+ label: `Source: ${source.label}`
47
+ })),
48
+ onChange: (v) => setActiveSource(v),
49
+ ariaLabel: "Data source",
50
+ className
51
+ });
52
+ }
53
+ //#endregion
54
+ export { ChartSourceSwitcher };
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-time-bucket-selector.d.ts
4
+ /**
5
+ * Time bucket selector — premium custom dropdown.
6
+ * Only renders when the X-axis is a date column.
7
+ */
8
+ /** Custom dropdown to select time granularity. */
9
+ declare function ChartTimeBucketSelector({
10
+ className
11
+ }: {
12
+ className?: string;
13
+ }): react_jsx_runtime0.JSX.Element | null;
14
+ //#endregion
15
+ export { ChartTimeBucketSelector };