@nadicodeai/ui 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/components/brand-icons/apple.d.ts +18 -0
  3. package/dist/components/brand-icons/apple.d.ts.map +1 -0
  4. package/dist/components/brand-icons/apple.js +22 -0
  5. package/dist/components/brand-icons/brand-icon.d.ts +22 -0
  6. package/dist/components/brand-icons/brand-icon.d.ts.map +1 -0
  7. package/dist/components/brand-icons/brand-icon.js +26 -0
  8. package/dist/components/brand-icons/linux.d.ts +18 -0
  9. package/dist/components/brand-icons/linux.d.ts.map +1 -0
  10. package/dist/components/brand-icons/linux.js +25 -0
  11. package/dist/components/brand-icons/windows.d.ts +18 -0
  12. package/dist/components/brand-icons/windows.d.ts.map +1 -0
  13. package/dist/components/brand-icons/windows.js +22 -0
  14. package/dist/components/brand-icons.d.ts +5 -0
  15. package/dist/components/brand-icons.d.ts.map +1 -0
  16. package/dist/components/brand-icons.js +10 -0
  17. package/dist/components/button.js +1 -1
  18. package/dist/components/card-marketing.js +2 -2
  19. package/dist/components/card.d.ts +4 -3
  20. package/dist/components/card.d.ts.map +1 -1
  21. package/dist/components/card.js +12 -2
  22. package/dist/components/chart-breakdown-table.js +1 -1
  23. package/dist/components/chart-bullet-bar.d.ts +18 -2
  24. package/dist/components/chart-bullet-bar.d.ts.map +1 -1
  25. package/dist/components/chart-bullet-bar.js +62 -9
  26. package/dist/components/chart-date-range-control.d.ts +11 -1
  27. package/dist/components/chart-date-range-control.d.ts.map +1 -1
  28. package/dist/components/chart-date-range-control.js +13 -3
  29. package/dist/components/chart-entity-colors.d.ts +7 -1
  30. package/dist/components/chart-entity-colors.d.ts.map +1 -1
  31. package/dist/components/chart-entity-colors.js +7 -1
  32. package/dist/components/chart-kpi-stat.d.ts.map +1 -1
  33. package/dist/components/chart-kpi-stat.js +1 -1
  34. package/dist/components/chart-time-series.d.ts.map +1 -1
  35. package/dist/components/chart-time-series.js +62 -4
  36. package/dist/components/chart.js +2 -2
  37. package/dist/components/code-editor-mockup.js +1 -1
  38. package/dist/components/nav-bar.js +1 -1
  39. package/dist/components/radio-group.d.ts +1 -1
  40. package/dist/components/radio-group.d.ts.map +1 -1
  41. package/dist/components/radio-group.js +2 -2
  42. package/dist/components/theme-mode-switcher.d.ts +10 -3
  43. package/dist/components/theme-mode-switcher.d.ts.map +1 -1
  44. package/dist/components/theme-mode-switcher.js +14 -7
  45. package/dist/eslint/index.js +91 -0
  46. package/dist/eslint/rules/jsx-class-utility.js +231 -0
  47. package/dist/eslint/rules/no-deprecated-zod-api.js +156 -0
  48. package/dist/eslint/rules/no-design-system-src-import.js +11 -0
  49. package/dist/eslint/rules/no-invalid-token-utility.js +46 -0
  50. package/dist/eslint/rules/no-native-visible-control.js +86 -0
  51. package/dist/eslint/rules/no-raw-color-utility.js +109 -0
  52. package/dist/eslint/rules/no-raw-focus-ring-width.js +30 -0
  53. package/dist/eslint/rules/no-raw-logo-import.js +41 -0
  54. package/dist/eslint/rules/no-shadcn-appearance-override.js +191 -0
  55. package/dist/eslint/rules/no-token-category-bypass.js +53 -0
  56. package/dist/eslint/rules/no-ui-src-import.js +10 -0
  57. package/dist/eslint/rules/no-unpinned-vercel-functions-import.js +100 -0
  58. package/dist/eslint/rules/src-import-boundary.js +207 -0
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -0
  62. package/docs/consuming-cross-repo.md +23 -0
  63. package/docs/contract.md +10 -0
  64. package/package.json +9 -3
@@ -1 +1 @@
1
- {"version":3,"file":"chart-entity-colors.d.ts","sourceRoot":"","sources":["../../src/components/chart-entity-colors.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,yBAAyB,yIAOrB,CAAC;AAEX;;;GAGG;AACH,QAAA,MAAM,wBAAwB,4BAA4B,CAAC;AAE3D,KAAK,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD;;;;GAIG;AACH,iBAAS,oBAAoB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2B1E;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,WAAW,EAAE,CAAC;AAClG,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"chart-entity-colors.d.ts","sourceRoot":"","sources":["../../src/components/chart-entity-colors.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,yBAAyB,yIAOrB,CAAC;AAEX;;;;;;;;;GASG;AACH,QAAA,MAAM,wBAAwB,oBAAoB,CAAC;AAEnD,KAAK,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD;;;;GAIG;AACH,iBAAS,oBAAoB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2B1E;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,WAAW,EAAE,CAAC;AAClG,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -24,8 +24,14 @@ const CHART_ENTITY_COLOR_TOKENS = [
24
24
  /**
25
25
  * The neutral register for folded "everything else" entities. A muted ink
26
26
  * token, never an extra categorical hue.
27
+ *
28
+ * `--nc-muted` and not the `--muted-foreground` role: the role resolves to the
29
+ * body-text tone, which in dark is near-white (L 0.83) — as a mark it becomes
30
+ * the loudest thing in the chart, so the tail outshouts every named entity.
31
+ * `--nc-muted` is authored as a mid grey in both modes (L 0.53 light / 0.65
32
+ * dark), which is what a folded tail should read as either way.
27
33
  */
28
- const CHART_ENTITY_OTHER_COLOR = "var(--muted-foreground)";
34
+ const CHART_ENTITY_OTHER_COLOR = "var(--nc-muted)";
29
35
  /**
30
36
  * Assigns `var(--nc-chart-1..6)` in fixed order by FIRST APPEARANCE of each
31
37
  * stable entity id. Duplicate ids keep their first assignment. Entities past
@@ -1 +1 @@
1
- {"version":3,"file":"chart-kpi-stat.d.ts","sourceRoot":"","sources":["../../src/components/chart-kpi-stat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAS,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;GAWG;AAEH,KAAK,YAAY,GAAG;IAClB,yCAAyC;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,iBAAS,OAAO,CAAC,EACf,KAAK,EACL,KAAK,EACL,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACjD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B,qBA6BA;AAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"chart-kpi-stat.d.ts","sourceRoot":"","sources":["../../src/components/chart-kpi-stat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAS,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;GAWG;AAEH,KAAK,YAAY,GAAG;IAClB,yCAAyC;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,iBAAS,OAAO,CAAC,EACf,KAAK,EACL,KAAK,EACL,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACjD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B,qBAkCA;AAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,CAAC"}
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { Delta } from "./chart-delta.js";
4
4
  function KpiStat({ label, value, delta, sparkline, className, ...props }) {
5
- return (_jsxs("div", { "data-slot": "kpi-stat", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx("div", { "data-slot": "kpi-stat-label", className: "font-mono text-xs text-muted-foreground uppercase", children: label }), _jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("div", { "data-slot": "kpi-stat-value", className: "text-3xl leading-none font-semibold text-foreground", children: value }), sparkline ? (_jsx("div", { "data-slot": "kpi-stat-sparkline", "aria-hidden": "true", children: sparkline })) : null] }), delta ? (_jsx(Delta, { value: delta.value, direction: delta.direction, upIsGood: delta.upIsGood, vsLabel: delta.vsLabel })) : null] }));
5
+ return (_jsxs("div", { "data-slot": "kpi-stat", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx("div", { "data-slot": "kpi-stat-label", className: "text-xs font-medium text-muted-foreground", children: label }), _jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("div", { "data-slot": "kpi-stat-value", className: "text-3xl leading-none font-semibold text-foreground", children: value }), sparkline ? (_jsx("div", { "data-slot": "kpi-stat-sparkline", "aria-hidden": "true", children: sparkline })) : null] }), delta ? (_jsx(Delta, { value: delta.value, direction: delta.direction, upIsGood: delta.upIsGood, vsLabel: delta.vsLabel })) : null] }));
6
6
  }
7
7
  export { KpiStat };
@@ -1 +1 @@
1
- {"version":3,"file":"chart-time-series.d.ts","sourceRoot":"","sources":["../../src/components/chart-time-series.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAY/B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAsD1C,KAAK,gBAAgB,GAAG;IACtB,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,KAAK,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACxE,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsJF,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBA+CjB;AAED,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAwDjB;AAED,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"chart-time-series.d.ts","sourceRoot":"","sources":["../../src/components/chart-time-series.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAY/B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AA+G1C,KAAK,gBAAgB,GAAG;IACtB,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,KAAK,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACxE,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpC,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsJF,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBAyDjB;AAED,iBAAS,cAAc,CAAC,EACtB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,cAAc,EACd,KAAK,EACL,MAAiB,EACjB,cAA8B,EAC9B,UAAU,EACV,UAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,qBA6EjB;AAED,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC"}
@@ -43,6 +43,59 @@ import { CHART_ENTITY_OTHER_COLOR, entityColor, } from "./chart-entity-colors.js
43
43
  const CHART_SURFACE_FOCUS_CLASS = "[&_.recharts-surface:focus-visible]:outline-2 " +
44
44
  "[&_.recharts-surface:focus-visible]:outline-ring " +
45
45
  "[&_.recharts-surface:focus-visible]:outline-offset-2";
46
+ /**
47
+ * The x ticks land at ONE constant stride across the range, and the stride is
48
+ * the smallest one whose labels still clear each other at the width the chart
49
+ * actually has. Recharts' default `preserveEnd` fitting is greedy on label
50
+ * width, so a 30-day axis draws the narrow early labels daily and then switches
51
+ * to every other day once "Jul 10" is wider than "Jul 1" — the rhythm breaks
52
+ * mid-chart, differently per locale. `equidistantPreserveStart` evaluates one
53
+ * stride against every label and raises it until the whole run fits, so the
54
+ * axis is even by construction AND survives a narrow viewport: a count fixed at
55
+ * the wide case superimposed eight labels into an unreadable smear on a phone,
56
+ * and worse in locales whose date labels run longer.
57
+ */
58
+ const X_AXIS_MIN_TICK_GAP = 48;
59
+ /**
60
+ * How many ticks the y axis draws, ends included. Recharts divides the data's
61
+ * own maximum by this count when it is left to itself, which produces steps of
62
+ * whatever the data happens to reach: one console chart read $0/$5/$10 while the
63
+ * chart beside it read EUR0/9/18/27/36, and the two stopped sharing a numeric
64
+ * register. The domain is rounded up to a step a person counts in instead.
65
+ */
66
+ const Y_AXIS_TICK_COUNT = 5;
67
+ /** The steps a reader counts in, per order of magnitude. */
68
+ const NICE_STEP_MULTIPLES = [1, 2, 2.5, 5, 10];
69
+ function niceStep(rawStep) {
70
+ if (!Number.isFinite(rawStep) || rawStep <= 0)
71
+ return 1;
72
+ const magnitude = 10 ** Math.floor(Math.log10(rawStep));
73
+ const normalized = rawStep / magnitude;
74
+ const multiple = NICE_STEP_MULTIPLES.find((candidate) => normalized <= candidate) ?? 10;
75
+ return multiple * magnitude;
76
+ }
77
+ /**
78
+ * The stacked maximum across the data, rounded up to a whole number of nice
79
+ * steps. Returns `undefined` when there is nothing to bound, so the axis falls
80
+ * back to recharts' own domain rather than pinning itself to zero.
81
+ */
82
+ function niceYDomainMax(data, series) {
83
+ let max = 0;
84
+ for (const datum of data) {
85
+ let total = 0;
86
+ for (const entry of series) {
87
+ const value = datum[entry.id];
88
+ if (typeof value === "number" && Number.isFinite(value))
89
+ total += value;
90
+ }
91
+ if (total > max)
92
+ max = total;
93
+ }
94
+ if (max <= 0)
95
+ return undefined;
96
+ const step = niceStep(max / (Y_AXIS_TICK_COUNT - 1));
97
+ return step * (Y_AXIS_TICK_COUNT - 1);
98
+ }
46
99
  /**
47
100
  * Synthetic id/key for the single aggregated neutral series. Entities past the
48
101
  * sixth resolve to the neutral register, and rendering each as its own
@@ -63,7 +116,7 @@ function defaultFormat(value) {
63
116
  */
64
117
  function tooltipRowFormatter(series, valueFormatter) {
65
118
  const labelById = new Map(series.map((entry) => [entry.id, entry.label]));
66
- return (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "time-series-tooltip-swatch", className: "size-2.5 shrink-0 rounded-[2px]", style: { backgroundColor: item.color ?? item.payload?.fill } }), _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: labelById.get(String(name)) ?? name }), _jsx("span", { className: "font-mono font-medium text-foreground tabular-nums", children: typeof value === "number" ? valueFormatter(value) : String(value) })] })] }));
119
+ return (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "time-series-tooltip-swatch", className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: item.color ?? item.payload?.fill } }), _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: labelById.get(String(name)) ?? name }), _jsx("span", { className: "font-mono font-medium text-foreground tabular-nums", children: typeof value === "number" ? valueFormatter(value) : String(value) })] })] }));
67
120
  }
68
121
  function seriesChartConfig(series, entityColorMap) {
69
122
  return Object.fromEntries(series.map((entry) => [
@@ -114,14 +167,19 @@ function TimeSeriesFrame({ label, className, children, ...props }) {
114
167
  function TimeSeriesBars({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, otherLabel = "Other", className, ...props }) {
115
168
  const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
116
169
  const config = seriesChartConfig(folded.series, entityColorMap);
117
- return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-bars", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", CHART_SURFACE_FOCUS_CLASS), children: _jsxs(BarChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, _jsx(BarStack, { stackId: "stacked", radius: [4, 4, 0, 0], children: folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, isAnimationActive: false, maxBarSize: 24, fill: entityColor(entityColorMap, entry.id), stroke: "var(--background)", strokeWidth: 2 }, entry.id))) })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
170
+ const yDomainMax = niceYDomainMax(folded.data, folded.series);
171
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-bars", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", CHART_SURFACE_FOCUS_CLASS), children: _jsxs(BarChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, _jsx(BarStack, { stackId: "stacked", radius: [4, 4, 0, 0], children: folded.series.map((entry) => (_jsx(Bar, { dataKey: entry.id, isAnimationActive: false, maxBarSize: 24, fill: entityColor(entityColorMap, entry.id), stroke: "var(--background)", strokeWidth: 2 }, entry.id))) })] }) }), _jsx(TimeSeriesTableAlternative, { data: folded.data, xKey: xKey, xLabel: xLabel, series: folded.series, label: label, valueFormatter: valueFormatter })] }));
118
172
  }
119
173
  function TimeSeriesArea({ data, xKey, series, entityColorMap, label, xLabel = "Period", valueFormatter = defaultFormat, yAxisWidth, otherLabel = "Other", className, ...props }) {
120
174
  const folded = foldNeutralSeries(series, entityColorMap, data, otherLabel);
121
175
  const config = seriesChartConfig(folded.series, entityColorMap);
122
- return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-area", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", CHART_SURFACE_FOCUS_CLASS), children: _jsxs(AreaChart, { accessibilityLayer: true, title: label, data: folded.data, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8 }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, folded.series.map((entry) => {
176
+ const yDomainMax = niceYDomainMax(folded.data, folded.series);
177
+ return (_jsxs(TimeSeriesFrame, { "data-slot": "time-series-area", label: label, className: className, ...props, children: [_jsx(ChartContainer, { config: config, className: cn("w-full", CHART_SURFACE_FOCUS_CLASS), children: _jsxs(AreaChart, { accessibilityLayer: true, title: label, data: folded.data, margin: { top: 5, right: 20, bottom: 5, left: 20 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: xKey, tickLine: false, axisLine: false, tickMargin: 8, interval: "equidistantPreserveStart", minTickGap: X_AXIS_MIN_TICK_GAP }), _jsx(YAxis, { tickLine: false, axisLine: false, width: yAxisWidth ?? "auto", tickCount: Y_AXIS_TICK_COUNT, ...(yDomainMax === undefined ? {} : { domain: [0, yDomainMax] }), tickFormatter: (value) => valueFormatter(Number(value)) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, { formatter: tooltipRowFormatter(folded.series, valueFormatter) }) }), folded.series.length > 1 ? _jsx(ChartLegend, { content: _jsx(ChartLegendContent, {}) }) : null, folded.series.map((entry) => {
123
178
  const color = entityColor(entityColorMap, entry.id);
124
- return (_jsx(Area, { type: "monotone", dataKey: entry.id, isAnimationActive: false, stackId: "stacked", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", fill: color, fillOpacity: 0.1,
179
+ return (_jsx(Area, { type: "monotone", dataKey: entry.id, isAnimationActive: false, stackId: "stacked", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", fill: color,
180
+ // DESIGN.md mark specs: an area fill reads as a light wash
181
+ // (roughly 10% opacity) under a full-strength line.
182
+ fillOpacity: 0.1,
125
183
  // Recharts' default activeDot ring is stroke #fff, which the
126
184
  // chart foundation remaps to transparent — an explicit surface
127
185
  // ring keeps the DESIGN.md 2px marker-ring spec on hover/focus.
@@ -89,7 +89,7 @@ function ChartTooltipContent({ active, payload, className, indicator = "dot", hi
89
89
  const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`;
90
90
  const itemConfig = getPayloadConfigFromPayload(config, item, key);
91
91
  const indicatorColor = color ?? item.payload?.fill ?? item.color;
92
- return (_jsx("div", { className: cn("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center"), children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (_jsxs(_Fragment, { children: [itemConfig?.icon ? (_jsx(itemConfig.icon, {})) : (!hideIndicator && (_jsx("div", { className: cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
92
+ return (_jsx("div", { className: cn("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center"), children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (_jsxs(_Fragment, { children: [itemConfig?.icon ? (_jsx(itemConfig.icon, {})) : (!hideIndicator && (_jsx("div", { className: cn("shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)", {
93
93
  "h-2.5 w-2.5": indicator === "dot",
94
94
  "w-1": indicator === "line",
95
95
  "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
@@ -113,7 +113,7 @@ function ChartLegendContent({ className, hideIcon = false, payload, verticalAlig
113
113
  .map((item, index) => {
114
114
  const key = `${nameKey ?? item.dataKey ?? "value"}`;
115
115
  const itemConfig = getPayloadConfigFromPayload(config, item, key);
116
- return (_jsxs("div", { className: cn("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"), children: [itemConfig?.icon && !hideIcon ? (_jsx(itemConfig.icon, {})) : (_jsx("div", { className: "h-2 w-2 shrink-0 rounded-[2px]", style: {
116
+ return (_jsxs("div", { className: cn("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"), children: [itemConfig?.icon && !hideIcon ? (_jsx(itemConfig.icon, {})) : (_jsx("div", { className: "h-2 w-2 shrink-0 rounded-xs", style: {
117
117
  backgroundColor: item.color,
118
118
  } })), itemConfig?.label] }, index));
119
119
  }) }));
@@ -24,7 +24,7 @@ import { cn } from "../lib/utils.js";
24
24
  * dots -> bg-[color-mix(in_srgb,var(--nc-line)_24%,var(--nc-canvas)_22%)]
25
25
  */
26
26
  function CodeEditorMockup({ className, ...props }) {
27
- return (_jsx("div", { "data-slot": "code-editor-mockup", className: cn("grid content-start overflow-hidden rounded-[var(--nc-rounded-md)] bg-primary text-primary-foreground", className), ...props }));
27
+ return (_jsx("div", { "data-slot": "code-editor-mockup", className: cn("grid content-start overflow-hidden rounded-md bg-primary text-primary-foreground", className), ...props }));
28
28
  }
29
29
  function CodeEditorMockupChrome({ className, children, ...props }) {
30
30
  return (_jsxs("div", { "data-slot": "code-editor-mockup-chrome", className: cn("flex items-center gap-2 border-b border-b-[color-mix(in_srgb,var(--nc-line)_24%,var(--nc-canvas)_22%)] px-6 py-3", className), ...props, children: [_jsxs("span", { "data-slot": "code-editor-mockup-dots", "aria-hidden": "true", className: "flex flex-none gap-2", children: [_jsx("span", { className: "size-[11px] rounded-full bg-[color-mix(in_srgb,var(--nc-line)_24%,var(--nc-canvas)_22%)]" }), _jsx("span", { className: "size-[11px] rounded-full bg-[color-mix(in_srgb,var(--nc-line)_24%,var(--nc-canvas)_22%)]" }), _jsx("span", { className: "size-[11px] rounded-full bg-[color-mix(in_srgb,var(--nc-line)_24%,var(--nc-canvas)_22%)]" })] }), children] }));
@@ -28,7 +28,7 @@ function NavBarLogo({ className, ...props }) {
28
28
  function NavBarLinks({ className, ...props }) {
29
29
  return (_jsx("div", { "data-slot": "nav-bar-links", className: cn("hidden items-center justify-center gap-1 text-sm text-muted-foreground lg:inline-flex",
30
30
  // each link reads as a flat pill with hover/focus affordance against the glass.
31
- "[&_a]:rounded-[var(--nc-rounded-sm)] [&_a]:px-3 [&_a]:py-2 [&_a]:text-inherit [&_a]:no-underline [&_a]:transition-colors", "[&_a:hover]:bg-accent [&_a:hover]:text-foreground", "[&_a[data-active]]:text-foreground", "[&_a:focus-visible]:outline [&_a:focus-visible]:outline-1 [&_a:focus-visible]:[outline-color:var(--ring)] [&_a:focus-visible]:outline-offset-2", className), ...props }));
31
+ "[&_a]:rounded-sm [&_a]:px-3 [&_a]:py-2 [&_a]:text-inherit [&_a]:no-underline [&_a]:transition-colors", "[&_a:hover]:bg-accent [&_a:hover]:text-foreground", "[&_a[data-active]]:text-foreground", "[&_a:focus-visible]:outline [&_a:focus-visible]:outline-1 [&_a:focus-visible]:[outline-color:var(--ring)] [&_a:focus-visible]:outline-offset-2", className), ...props }));
32
32
  }
33
33
  function NavBarCta({ className, ...props }) {
34
34
  return (_jsx("div", { "data-slot": "nav-bar-cta", className: cn("inline-flex items-center gap-3 justify-self-end", className), ...props }));
@@ -1,6 +1,6 @@
1
1
  import { Radio as RadioPrimitive } from "@base-ui/react/radio";
2
2
  import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
3
3
  declare function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props): import("react").JSX.Element;
4
- declare function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props): import("react").JSX.Element;
4
+ declare function RadioGroupItem({ children, className, ...props }: RadioPrimitive.Root.Props): import("react").JSX.Element;
5
5
  export { RadioGroup, RadioGroupItem };
6
6
  //# sourceMappingURL=radio-group.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../src/components/radio-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,IAAI,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAI/E,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,CAAC,KAAK,+BAQrE;AAED,iBAAS,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,+BAkBzE;AAED,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../src/components/radio-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,IAAI,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAI/E,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,CAAC,KAAK,+BAQrE;AAED,iBAAS,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,+BAoBnF;AAED,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC"}
@@ -5,7 +5,7 @@ import { cn } from "../lib/utils.js";
5
5
  function RadioGroup({ className, ...props }) {
6
6
  return (_jsx(RadioGroupPrimitive, { "data-slot": "radio-group", className: cn("grid w-full gap-2", className), ...props }));
7
7
  }
8
- function RadioGroupItem({ className, ...props }) {
9
- return (_jsx(RadioPrimitive.Root, { "data-slot": "radio-group-item", className: cn("group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:top-1/2 after:left-1/2 after:size-touch-target after:-translate-x-1/2 after:-translate-y-1/2 focus-visible:border-ring focus-visible:ring-[length:var(--ring-width-focus)] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[length:var(--ring-width-focus)] aria-invalid:ring-destructive aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground [@media(hover:hover)_and_(pointer:fine)]:after:hidden", className), ...props, children: _jsx(RadioPrimitive.Indicator, { "data-slot": "radio-group-indicator", className: "flex size-4 items-center justify-center", children: _jsx("span", { className: "absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" }) }) }));
8
+ function RadioGroupItem({ children, className, ...props }) {
9
+ return (_jsx(RadioPrimitive.Root, { "data-slot": "radio-group-item", className: cn("group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:top-1/2 after:left-1/2 after:size-touch-target after:-translate-x-1/2 after:-translate-y-1/2 focus-visible:border-ring focus-visible:ring-[length:var(--ring-width-focus)] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[length:var(--ring-width-focus)] aria-invalid:ring-destructive aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground [@media(hover:hover)_and_(pointer:fine)]:after:hidden", className), ...props, children: children ?? (_jsx(RadioPrimitive.Indicator, { "data-slot": "radio-group-indicator", className: "flex size-4 items-center justify-center", children: _jsx("span", { className: "absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" }) })) }));
10
10
  }
11
11
  export { RadioGroup, RadioGroupItem };
@@ -1,17 +1,24 @@
1
1
  import * as React from "react";
2
- import { ToggleGroup } from "./toggle-group";
2
+ import { RadioGroup } from "./radio-group";
3
3
  import { isThemeMode, themeModes, type ThemeMode } from "../lib/theme-mode";
4
4
  export { isThemeMode, themeModes };
5
5
  export type { ThemeMode };
6
6
  export type ThemeModeOptionLabels = Record<ThemeMode, string>;
7
- export type ThemeModeSwitcherProps = Omit<React.ComponentProps<typeof ToggleGroup>, "children" | "defaultValue" | "onValueChange" | "type" | "value"> & {
7
+ export type ThemeModeOption = {
8
+ Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
9
+ label: string;
10
+ mode: ThemeMode;
11
+ selected: boolean;
12
+ };
13
+ export type ThemeModeSwitcherProps = Omit<React.ComponentProps<typeof RadioGroup>, "children" | "defaultValue" | "onValueChange" | "value"> & {
8
14
  "aria-label": string;
9
15
  compact?: boolean;
10
16
  itemClassName?: string;
11
17
  labels: ThemeModeOptionLabels;
12
18
  onValueChange: (value: ThemeMode) => void;
19
+ renderItem?: (option: ThemeModeOption) => React.ReactNode;
13
20
  value: ThemeMode;
14
21
  };
15
- declare function ThemeModeSwitcher({ "aria-label": ariaLabel, className, compact, disabled, itemClassName, labels, onValueChange, value, ...props }: ThemeModeSwitcherProps): React.JSX.Element;
22
+ declare function ThemeModeSwitcher({ "aria-label": ariaLabel, className, compact, disabled, itemClassName, labels, onValueChange, renderItem, value, ...props }: ThemeModeSwitcherProps): React.JSX.Element;
16
23
  export { ThemeModeSwitcher };
17
24
  //# sourceMappingURL=theme-mode-switcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme-mode-switcher.d.ts","sourceRoot":"","sources":["../../src/components/theme-mode-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,WAAW,EAAmB,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,KAAK,CAAC,cAAc,CAAC,OAAO,WAAW,CAAC,EACxC,UAAU,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,GAAG,OAAO,CACjE,GAAG;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAQF,iBAAS,iBAAiB,CAAC,EACzB,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,OAAe,EACf,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,KAAK,EACL,GAAG,KAAK,EACT,EAAE,sBAAsB,qBAmCxB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"theme-mode-switcher.d.ts","sourceRoot":"","sources":["../../src/components/theme-mode-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,UAAU,EAAkB,MAAM,0BAA0B,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,EACvC,UAAU,GAAG,cAAc,GAAG,eAAe,GAAG,OAAO,CACxD,GAAG;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAQF,iBAAS,iBAAiB,CAAC,EACzB,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,OAAe,EACf,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,GAAG,KAAK,EACT,EAAE,sBAAsB,qBAkDxB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
4
- import { ToggleGroup, ToggleGroupItem } from "./toggle-group.js";
4
+ import { RadioGroup, RadioGroupItem } from "./radio-group.js";
5
+ import { toggleVariants } from "./toggle.js";
5
6
  import { isThemeMode, themeModes } from "../lib/theme-mode.js";
6
7
  import { cn } from "../lib/utils.js";
7
8
  export { isThemeMode, themeModes };
@@ -10,16 +11,22 @@ const themeModeIcons = {
10
11
  dark: MoonIcon,
11
12
  system: MonitorIcon,
12
13
  };
13
- function ThemeModeSwitcher({ "aria-label": ariaLabel, className, compact = false, disabled, itemClassName, labels, onValueChange, value, ...props }) {
14
- return (_jsx(ToggleGroup, { "aria-label": ariaLabel, className: cn("max-w-full", className), disabled: disabled, onValueChange: (nextValue) => {
15
- const nextMode = nextValue[0];
14
+ function ThemeModeSwitcher({ "aria-label": ariaLabel, className, compact = false, disabled, itemClassName, labels, onValueChange, renderItem, value, ...props }) {
15
+ return (_jsx(RadioGroup, { "aria-label": ariaLabel, className: cn("max-w-full", className), disabled: disabled, onValueChange: (nextMode) => {
16
16
  if (isThemeMode(nextMode)) {
17
17
  onValueChange(nextMode);
18
18
  }
19
- }, value: [value], variant: "outline", ...props, children: themeModes.map((mode) => {
19
+ }, value: value, ...props, children: themeModes.map((mode) => {
20
20
  const Icon = themeModeIcons[mode];
21
21
  const label = labels[mode];
22
- return (_jsxs(ToggleGroupItem, { "aria-label": label, className: cn("min-w-0 gap-1.5 px-2.5", compact ? "h-8 text-xs" : "h-9", itemClassName), disabled: disabled, value: mode, children: [_jsx(Icon, { "aria-hidden": "true", "data-icon": true }), _jsx("span", { className: "min-w-0 truncate", children: label })] }, mode));
22
+ const selected = value === mode;
23
+ return (_jsx(RadioGroupItem, { "aria-label": label, className: cn(toggleVariants({ variant: "outline" }),
24
+ // The selected tile takes the muted interaction surface the
25
+ // toggle uses for its own on-state, so it takes that surface's
26
+ // foreground with it. Left on the radio primitive's on-primary
27
+ // foreground, the selected label reads white on near-white in
28
+ // light and black on black in dark.
29
+ "aspect-auto w-auto min-w-0 gap-1.5 px-2.5 data-checked:bg-muted data-checked:text-foreground", compact ? "h-8 text-xs" : "h-9", itemClassName), disabled: disabled, value: mode, children: renderItem ? (renderItem({ Icon, label, mode, selected })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { "aria-hidden": "true", "data-icon": true }), _jsx("span", { className: "min-w-0 truncate", children: label })] })) }, mode));
23
30
  }) }));
24
31
  }
25
32
  export { ThemeModeSwitcher };
@@ -0,0 +1,91 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ import { noDesignSystemSrcImport } from "./rules/no-design-system-src-import.js";
4
+ import { noDeprecatedZodApi } from "./rules/no-deprecated-zod-api.js";
5
+ import { noRawLogoImport } from "./rules/no-raw-logo-import.js";
6
+ import { noRawColorUtility } from "./rules/no-raw-color-utility.js";
7
+ import { noShadcnAppearanceOverride } from "./rules/no-shadcn-appearance-override.js";
8
+ import { noUnpinnedVercelFunctionsImport } from "./rules/no-unpinned-vercel-functions-import.js";
9
+ import { noUiSrcImport } from "./rules/no-ui-src-import.js";
10
+ import { noNativeVisibleControl } from "./rules/no-native-visible-control.js";
11
+ import { noRawFocusRingWidth } from "./rules/no-raw-focus-ring-width.js";
12
+ import { noInvalidTokenUtility } from "./rules/no-invalid-token-utility.js";
13
+ import { noTokenCategoryBypass } from "./rules/no-token-category-bypass.js";
14
+
15
+ /**
16
+ * The plugin carries no version of its own. Its bytes are published inside
17
+ * `@nadicodeai/ui`, whose version the release gate already forces to move in
18
+ * lockstep, so that manifest is the one home of the number and this reads it.
19
+ * The published copy of this file sits at `<ui>/dist/eslint/index.js` and the
20
+ * source sits at `tools/eslint-plugin-nadicodeai/index.js`, which puts the
21
+ * manifest at one of two places relative to here; the `name` field says which
22
+ * candidate is the right one, so the repository root's own manifest — also two
23
+ * levels up — cannot answer by accident.
24
+ */
25
+ const PUBLISHING_PACKAGE = "@nadicodeai/ui";
26
+ const MANIFEST_CANDIDATES = ["../../package.json", `../../${PUBLISHING_PACKAGE}/package.json`];
27
+
28
+ const version = readPublishedVersion();
29
+
30
+ function readPublishedVersion() {
31
+ for (const candidate of MANIFEST_CANDIDATES) {
32
+ let manifest;
33
+ try {
34
+ manifest = JSON.parse(readFileSync(new URL(candidate, import.meta.url), "utf8"));
35
+ } catch {
36
+ continue;
37
+ }
38
+ if (manifest?.name === PUBLISHING_PACKAGE && typeof manifest.version === "string") {
39
+ return manifest.version;
40
+ }
41
+ }
42
+ throw new Error(
43
+ `@nadicodeai/eslint-plugin: no ${PUBLISHING_PACKAGE} manifest at ${MANIFEST_CANDIDATES.join(" or ")}, so the plugin cannot report the version it ships under.`,
44
+ );
45
+ }
46
+
47
+ export const strictDesignSystemRules = {
48
+ "nadicodeai/no-design-system-src-import": "error",
49
+ "nadicodeai/no-invalid-token-utility": "error",
50
+ "nadicodeai/no-native-visible-control": "error",
51
+ "nadicodeai/no-raw-color-utility": "error",
52
+ "nadicodeai/no-raw-focus-ring-width": "error",
53
+ "nadicodeai/no-raw-logo-import": "error",
54
+ "nadicodeai/no-shadcn-appearance-override": "error",
55
+ "nadicodeai/no-token-category-bypass": "error",
56
+ "nadicodeai/no-ui-src-import": "error",
57
+ };
58
+
59
+ /** @type {import('eslint').ESLint.Plugin} */
60
+ const plugin = {
61
+ meta: {
62
+ name: "@nadicodeai/eslint-plugin",
63
+ version,
64
+ },
65
+ configs: {},
66
+ rules: {
67
+ "no-deprecated-zod-api": noDeprecatedZodApi,
68
+ "no-raw-logo-import": noRawLogoImport,
69
+ "no-raw-color-utility": noRawColorUtility,
70
+ "no-shadcn-appearance-override": noShadcnAppearanceOverride,
71
+ "no-native-visible-control": noNativeVisibleControl,
72
+ "no-raw-focus-ring-width": noRawFocusRingWidth,
73
+ "no-unpinned-vercel-functions-import": noUnpinnedVercelFunctionsImport,
74
+ "no-ui-src-import": noUiSrcImport,
75
+ "no-design-system-src-import": noDesignSystemSrcImport,
76
+ "no-invalid-token-utility": noInvalidTokenUtility,
77
+ "no-token-category-bypass": noTokenCategoryBypass,
78
+ },
79
+ };
80
+
81
+ export const strictDesignSystemConfig = [
82
+ {
83
+ name: "nadicodeai/design-system-strict",
84
+ plugins: { nadicodeai: plugin },
85
+ rules: strictDesignSystemRules,
86
+ },
87
+ ];
88
+
89
+ plugin.configs["flat/strict"] = strictDesignSystemConfig;
90
+
91
+ export default plugin;
@@ -0,0 +1,231 @@
1
+ const PACKAGE_PRIMITIVE_PATH = "@nadicodeai/ui/src/";
2
+
3
+ export function isPackageOwnedPrimitive(filename) {
4
+ return filename.replaceAll("\\", "/").includes(PACKAGE_PRIMITIVE_PATH);
5
+ }
6
+
7
+ function variableInitializer(node, context) {
8
+ let scope = context.sourceCode.getScope(node);
9
+ while (scope) {
10
+ const variable = scope.set.get(node.name);
11
+ const definition = variable?.defs.find(
12
+ (candidate) => candidate.type === "Variable" && candidate.node?.type === "VariableDeclarator",
13
+ );
14
+ if (definition?.node.init) return definition.node.init;
15
+ scope = scope.upper;
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ function collectStaticClassFragments(
21
+ node,
22
+ fragments,
23
+ context,
24
+ seen = new Set(),
25
+ includeObjectKeys = true,
26
+ ) {
27
+ if (!node) return;
28
+
29
+ if (seen.has(node)) return;
30
+ seen.add(node);
31
+
32
+ if (node.type === "Literal" && typeof node.value === "string") {
33
+ fragments.push(node.value);
34
+ return;
35
+ }
36
+ if (node.type === "JSXExpressionContainer" || node.type === "ChainExpression") {
37
+ collectStaticClassFragments(node.expression, fragments, context, seen, includeObjectKeys);
38
+ return;
39
+ }
40
+ if (node.type === "TemplateLiteral") {
41
+ for (const quasi of node.quasis) fragments.push(quasi.value.cooked ?? quasi.value.raw);
42
+ for (const expression of node.expressions) {
43
+ collectStaticClassFragments(expression, fragments, context, seen, includeObjectKeys);
44
+ }
45
+ return;
46
+ }
47
+ if (node.type === "TaggedTemplateExpression") {
48
+ collectStaticClassFragments(node.quasi, fragments, context, seen, includeObjectKeys);
49
+ return;
50
+ }
51
+ if (node.type === "ConditionalExpression") {
52
+ collectStaticClassFragments(node.consequent, fragments, context, seen, includeObjectKeys);
53
+ collectStaticClassFragments(node.alternate, fragments, context, seen, includeObjectKeys);
54
+ return;
55
+ }
56
+ if (node.type === "LogicalExpression" || node.type === "BinaryExpression") {
57
+ collectStaticClassFragments(node.left, fragments, context, seen, includeObjectKeys);
58
+ collectStaticClassFragments(node.right, fragments, context, seen, includeObjectKeys);
59
+ return;
60
+ }
61
+ if (node.type === "CallExpression" || node.type === "NewExpression") {
62
+ for (const argument of node.arguments) {
63
+ collectStaticClassFragments(
64
+ argument.type === "SpreadElement" ? argument.argument : argument,
65
+ fragments,
66
+ context,
67
+ seen,
68
+ includeObjectKeys,
69
+ );
70
+ }
71
+ return;
72
+ }
73
+ if (node.type === "ArrayExpression" || node.type === "SequenceExpression") {
74
+ for (const element of node.elements ?? node.expressions) {
75
+ collectStaticClassFragments(
76
+ element?.type === "SpreadElement" ? element.argument : element,
77
+ fragments,
78
+ context,
79
+ seen,
80
+ includeObjectKeys,
81
+ );
82
+ }
83
+ return;
84
+ }
85
+ if (node.type === "ObjectExpression") {
86
+ for (const property of node.properties) {
87
+ if (property.type === "SpreadElement") {
88
+ collectStaticClassFragments(property.argument, fragments, context, seen, includeObjectKeys);
89
+ continue;
90
+ }
91
+ if (includeObjectKeys && !property.computed) {
92
+ collectStaticClassFragments(property.key, fragments, context, seen, includeObjectKeys);
93
+ }
94
+ collectStaticClassFragments(property.value, fragments, context, seen, includeObjectKeys);
95
+ }
96
+ return;
97
+ }
98
+ if (node.type === "Identifier") {
99
+ collectStaticClassFragments(
100
+ variableInitializer(node, context),
101
+ fragments,
102
+ context,
103
+ seen,
104
+ includeObjectKeys,
105
+ );
106
+ return;
107
+ }
108
+ if (node.type === "MemberExpression") {
109
+ // A dynamic lookup can select any value in a local class table. Resolve the
110
+ // entire object so no palette entry can hide behind `classes[current]`.
111
+ collectStaticClassFragments(node.object, fragments, context, seen, includeObjectKeys);
112
+ return;
113
+ }
114
+ if (
115
+ node.type === "TSAsExpression" ||
116
+ node.type === "TSSatisfiesExpression" ||
117
+ node.type === "TSNonNullExpression" ||
118
+ node.type === "TypeCastExpression"
119
+ ) {
120
+ collectStaticClassFragments(node.expression, fragments, context, seen, includeObjectKeys);
121
+ }
122
+ }
123
+
124
+ function staticPropertyName(property) {
125
+ if (property.key.type === "Identifier" && !property.computed) return property.key.name;
126
+ if (property.key.type === "Literal" && typeof property.key.value === "string") {
127
+ return property.key.value;
128
+ }
129
+ return undefined;
130
+ }
131
+
132
+ function collectStaticSpreadClassFragments(node, fragments, context, seen = new Set()) {
133
+ if (!node || seen.has(node)) return;
134
+ seen.add(node);
135
+
136
+ if (node.type === "Identifier") {
137
+ collectStaticSpreadClassFragments(variableInitializer(node, context), fragments, context, seen);
138
+ return;
139
+ }
140
+ if (node.type === "ObjectExpression") {
141
+ for (const property of node.properties) {
142
+ if (property.type === "SpreadElement") {
143
+ collectStaticSpreadClassFragments(property.argument, fragments, context, seen);
144
+ continue;
145
+ }
146
+ if (staticPropertyName(property) === "className") {
147
+ collectStaticClassFragments(property.value, fragments, context, seen);
148
+ }
149
+ }
150
+ return;
151
+ }
152
+ if (node.type === "ConditionalExpression") {
153
+ collectStaticSpreadClassFragments(node.consequent, fragments, context, seen);
154
+ collectStaticSpreadClassFragments(node.alternate, fragments, context, seen);
155
+ return;
156
+ }
157
+ if (node.type === "LogicalExpression") {
158
+ collectStaticSpreadClassFragments(node.left, fragments, context, seen);
159
+ collectStaticSpreadClassFragments(node.right, fragments, context, seen);
160
+ return;
161
+ }
162
+ if (
163
+ node.type === "TSAsExpression" ||
164
+ node.type === "TSSatisfiesExpression" ||
165
+ node.type === "TSNonNullExpression" ||
166
+ node.type === "TypeCastExpression"
167
+ ) {
168
+ collectStaticSpreadClassFragments(node.expression, fragments, context, seen);
169
+ }
170
+ }
171
+
172
+ export function classUtilities(attribute, context) {
173
+ if (attribute?.type !== "JSXAttribute" || attribute.name.name !== "className") return [];
174
+ return staticClassUtilities(attribute.value, context);
175
+ }
176
+
177
+ export function staticStringFragments(node, context) {
178
+ const fragments = [];
179
+ collectStaticClassFragments(node, fragments, context);
180
+ return fragments;
181
+ }
182
+
183
+ function staticClassUtilities(node, context) {
184
+ return staticStringFragments(node, context).flatMap((fragment) =>
185
+ fragment.split(/\s+/).filter(Boolean),
186
+ );
187
+ }
188
+
189
+ function staticValueClassUtilities(node, context) {
190
+ const fragments = [];
191
+ collectStaticClassFragments(node, fragments, context, new Set(), false);
192
+ return fragments.flatMap((fragment) => fragment.split(/\s+/).filter(Boolean));
193
+ }
194
+
195
+ export function tokenUtilityVisitors(context, onUtility) {
196
+ const cvaNames = new Set();
197
+
198
+ function visitUtilities(node, utilities) {
199
+ for (const utility of utilities) onUtility(node, utility);
200
+ }
201
+
202
+ return {
203
+ ImportDeclaration(node) {
204
+ if (node.source.value !== "class-variance-authority") return;
205
+ for (const specifier of node.specifiers) {
206
+ if (specifier.type === "ImportSpecifier" && specifier.imported.name === "cva") {
207
+ cvaNames.add(specifier.local.name);
208
+ }
209
+ }
210
+ },
211
+ JSXAttribute(node) {
212
+ visitUtilities(node, classUtilities(node, context));
213
+ },
214
+ CallExpression(node) {
215
+ if (node.callee.type !== "Identifier" || !cvaNames.has(node.callee.name)) return;
216
+ visitUtilities(node, staticValueClassUtilities(node, context));
217
+ },
218
+ };
219
+ }
220
+
221
+ export function openingElementClassUtilities(node, context) {
222
+ const fragments = [];
223
+ for (const attribute of node.attributes) {
224
+ if (attribute.type === "JSXAttribute" && attribute.name.name === "className") {
225
+ fragments.push(...staticStringFragments(attribute.value, context));
226
+ } else if (attribute.type === "JSXSpreadAttribute") {
227
+ collectStaticSpreadClassFragments(attribute.argument, fragments, context);
228
+ }
229
+ }
230
+ return fragments.flatMap((fragment) => fragment.split(/\s+/).filter(Boolean));
231
+ }