@siyahi/ui 0.1.9-next.2 → 0.1.9-next.20

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 (149) hide show
  1. package/README.md +82 -176
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/agents/examples/accordion.tsx +39 -0
  4. package/agents/examples/alert.tsx +25 -0
  5. package/agents/examples/app-sidebar.tsx +38 -0
  6. package/agents/examples/avatar.tsx +14 -0
  7. package/agents/examples/badge.tsx +18 -0
  8. package/agents/examples/bar-list-labels.tsx +22 -0
  9. package/agents/examples/bar-list-sides.tsx +30 -0
  10. package/agents/examples/bar-list.tsx +24 -0
  11. package/agents/examples/breadcrumb.tsx +28 -0
  12. package/agents/examples/button.tsx +21 -0
  13. package/agents/examples/calendar.tsx +15 -0
  14. package/agents/examples/card.tsx +34 -0
  15. package/agents/examples/chart-area.tsx +38 -0
  16. package/agents/examples/chart-bar-flows.tsx +31 -0
  17. package/agents/examples/chart-bar-hourly.tsx +28 -0
  18. package/agents/examples/chart-bar-losses.tsx +29 -0
  19. package/agents/examples/chart-bar-sectors.tsx +70 -0
  20. package/agents/examples/chart-bar-years.tsx +40 -0
  21. package/agents/examples/chart-bar.tsx +56 -0
  22. package/agents/examples/chart-category-ticks.tsx +50 -0
  23. package/agents/examples/chart-line-events.tsx +47 -0
  24. package/agents/examples/chart-line-timeline.tsx +41 -0
  25. package/agents/examples/chart-line.tsx +57 -0
  26. package/agents/examples/code-block.tsx +38 -0
  27. package/agents/examples/collapsible.tsx +16 -0
  28. package/agents/examples/command.tsx +27 -0
  29. package/agents/examples/context-menu.tsx +15 -0
  30. package/agents/examples/copy-button.tsx +8 -0
  31. package/agents/examples/data-bar.tsx +121 -0
  32. package/agents/examples/data-table-filters.tsx +103 -0
  33. package/agents/examples/data-table.tsx +98 -0
  34. package/agents/examples/delta.tsx +13 -0
  35. package/agents/examples/dialog.tsx +28 -0
  36. package/agents/examples/distribution-bar-budget.tsx +30 -0
  37. package/agents/examples/distribution-bar-tug.tsx +72 -0
  38. package/agents/examples/dropdown-menu.tsx +31 -0
  39. package/agents/examples/formatters.tsx +42 -0
  40. package/agents/examples/heatmap-calendar.tsx +50 -0
  41. package/agents/examples/heatmap-status.tsx +51 -0
  42. package/agents/examples/heatmap-weekday.tsx +53 -0
  43. package/agents/examples/input.tsx +17 -0
  44. package/agents/examples/local-time.tsx +14 -0
  45. package/agents/examples/metric-strip.tsx +40 -0
  46. package/agents/examples/page-section.tsx +52 -0
  47. package/agents/examples/popover.tsx +23 -0
  48. package/agents/examples/price-chart.tsx +32 -0
  49. package/agents/examples/progress-tone.tsx +28 -0
  50. package/agents/examples/progress.tsx +10 -0
  51. package/agents/examples/scroll-area.tsx +20 -0
  52. package/agents/examples/section-cards-select.tsx +27 -0
  53. package/agents/examples/section-cards-trend.tsx +30 -0
  54. package/agents/examples/section-cards.tsx +48 -0
  55. package/agents/examples/select.tsx +27 -0
  56. package/agents/examples/separator.tsx +13 -0
  57. package/agents/examples/sheet.tsx +15 -0
  58. package/agents/examples/skeleton.tsx +11 -0
  59. package/agents/examples/slider.tsx +5 -0
  60. package/agents/examples/surfaces.tsx +115 -0
  61. package/agents/examples/table.tsx +50 -0
  62. package/agents/examples/tabs.tsx +16 -0
  63. package/agents/examples/toggle-group.tsx +12 -0
  64. package/agents/examples/toggle.tsx +11 -0
  65. package/agents/examples/tooltip.tsx +13 -0
  66. package/agents/examples/treemap.tsx +87 -0
  67. package/agents/manifest.json +931 -0
  68. package/agents/report-rules.json +7 -0
  69. package/dist/components/registry/charts/bar-chart-card.js +33 -26
  70. package/dist/components/registry/dashboard-01/section-cards.d.ts +2 -0
  71. package/dist/components/registry/dashboard-01/section-cards.js +42 -35
  72. package/dist/components/registry/dashboardcn/bar-list.js +9 -8
  73. package/dist/components/registry/dashboardcn/distribution-bar.js +1 -1
  74. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
  75. package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
  76. package/dist/components/registry/dashboardcn/sparkline.d.ts +18 -0
  77. package/dist/components/registry/dashboardcn/sparkline.js +95 -0
  78. package/dist/components/registry/heatmap/calendar-heatmap.js +8 -1
  79. package/dist/components/registry/heatmap/status-heatmap.js +8 -1
  80. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +6 -2
  81. package/dist/components/registry/heatmap/weekday-heatmap.js +56 -37
  82. package/dist/components/registry/niko-table/core/data-table-context.d.ts +2 -0
  83. package/dist/components/registry/niko-table/core/data-table-context.js +21 -0
  84. package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
  85. package/dist/components/registry/niko-table/core/data-table-structure.js +15 -12
  86. package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
  87. package/dist/components/registry/niko-table/core/data-table.js +5 -1
  88. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +4 -4
  89. package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
  90. package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
  91. package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
  92. package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
  93. package/dist/components/registry/niko-table/lib/styles.js +2 -2
  94. package/dist/components/registry/niko-table/ui/table.js +2 -2
  95. package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
  96. package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
  97. package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -1
  98. package/dist/components/registry/sidebar-07/nav-main.js +7 -1
  99. package/dist/components/siyahi/data-bar.d.ts +19 -0
  100. package/dist/components/siyahi/data-bar.js +29 -0
  101. package/dist/components/siyahi/page-section.d.ts +4 -1
  102. package/dist/components/siyahi/page-section.js +6 -3
  103. package/dist/components/siyahi/panel-grid.d.ts +22 -0
  104. package/dist/components/siyahi/panel-grid.js +34 -0
  105. package/dist/components/siyahi/siyahi-provider.d.ts +13 -2
  106. package/dist/components/siyahi/siyahi-provider.js +17 -4
  107. package/dist/components/siyahi/treemap.js +46 -4
  108. package/dist/components/ui/accordion.js +5 -5
  109. package/dist/components/ui/alert.js +2 -2
  110. package/dist/components/ui/badge.js +1 -1
  111. package/dist/components/ui/breadcrumb.js +2 -2
  112. package/dist/components/ui/button.js +10 -10
  113. package/dist/components/ui/calendar.js +1 -1
  114. package/dist/components/ui/card.js +4 -4
  115. package/dist/components/ui/chart.js +1 -1
  116. package/dist/components/ui/code-block.js +13 -12
  117. package/dist/components/ui/command.js +7 -6
  118. package/dist/components/ui/context-menu.js +7 -7
  119. package/dist/components/ui/dialog.js +5 -5
  120. package/dist/components/ui/dropdown-menu.js +8 -8
  121. package/dist/components/ui/input-group.js +11 -11
  122. package/dist/components/ui/input.js +1 -1
  123. package/dist/components/ui/popover.js +3 -3
  124. package/dist/components/ui/progress.js +1 -1
  125. package/dist/components/ui/select.js +6 -6
  126. package/dist/components/ui/sheet.js +5 -5
  127. package/dist/components/ui/sidebar.js +17 -18
  128. package/dist/components/ui/skeleton.js +1 -1
  129. package/dist/components/ui/slider.js +2 -2
  130. package/dist/components/ui/table.js +2 -2
  131. package/dist/components/ui/tabs.js +2 -2
  132. package/dist/components/ui/textarea.js +1 -1
  133. package/dist/components/ui/toggle-group.js +2 -2
  134. package/dist/components/ui/toggle.js +4 -4
  135. package/dist/components/ui/tooltip.js +2 -2
  136. package/dist/index.d.ts +7 -4
  137. package/dist/index.js +7 -4
  138. package/dist/lib/chart-frame.d.ts +13 -5
  139. package/dist/lib/chart-frame.js +207 -127
  140. package/dist/lib/chart-series.d.ts +7 -4
  141. package/dist/lib/format.js +32 -13
  142. package/dist/lib/heatmap-navigation.d.ts +8 -0
  143. package/dist/lib/heatmap-navigation.js +49 -0
  144. package/package.json +3 -1
  145. package/registry.lock.json +16 -0
  146. package/report.css +11 -11
  147. package/styles/siyahi.css +77 -11
  148. package/dist/lib/recharts.d.ts +0 -2
  149. package/dist/lib/recharts.js +0 -1
@@ -0,0 +1,7 @@
1
+ [
2
+ "Never paint html or body beyond the base styles report.css sets.",
3
+ "Fluid width. No outer card and no banner title.",
4
+ "A panel with its own background gets 16px padding and var(--radius).",
5
+ "Charts get fixed pixel heights.",
6
+ "Never use 100vh."
7
+ ]
@@ -1,39 +1,46 @@
1
1
  "use client";
2
- import { ChartFrame } from "../../../lib/chart-frame.js";
2
+ import { ChartFrame, seriesFill } from "../../../lib/chart-frame.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { Bar, BarChart, Cell } from "recharts";
5
5
  //#region src/components/registry/charts/bar-chart-card.tsx
6
+ const DOWN_FROM_ZERO = ["auto", 0];
6
7
  /** Values over an ordered axis as bars: one series, or several grouped or stacked. */
7
8
  function BarChartCard({ stacked = false, selected = null, onSelect, ...props }) {
8
9
  return /* @__PURE__ */ jsx(ChartFrame, {
9
10
  chart: BarChart,
10
11
  ...props,
12
+ yDomain: props.yDomain ?? (props.data.some((row) => props.series.some(({ key }) => Number(row[key]) > 0)) ? void 0 : DOWN_FROM_ZERO),
11
13
  stackOffset: stacked ? "sign" : void 0,
12
- children: props.series.map(({ key, color, negativeColor }, index) => /* @__PURE__ */ jsx(Bar, {
13
- dataKey: key,
14
- fill: color,
15
- stackId: stacked ? "stack" : void 0,
16
- radius: !stacked ? 2 : index === props.series.length - 1 ? props.horizontal ? [
17
- 0,
18
- 2,
19
- 2,
20
- 0
21
- ] : [
22
- 2,
23
- 2,
24
- 0,
25
- 0
26
- ] : 0,
27
- className: onSelect ? "cursor-pointer" : void 0,
28
- onClick: onSelect && ((bar) => {
29
- const value = bar.payload?.[props.xKey];
30
- if (value !== void 0) onSelect(value === selected ? null : value);
31
- }),
32
- children: (negativeColor || selected !== null) && props.data.map((row, i) => /* @__PURE__ */ jsx(Cell, {
33
- fill: negativeColor && Number(row[key]) < 0 ? negativeColor : color,
34
- fillOpacity: selected === null || row[props.xKey] === selected ? 1 : .35
35
- }, i))
36
- }, key))
14
+ onClick: onSelect ? ({ activeTooltipIndex, isTooltipActive }) => {
15
+ if (!isTooltipActive || activeTooltipIndex == null) return;
16
+ const row = props.data[Number(activeTooltipIndex)];
17
+ if (row) onSelect(row[props.xKey] === selected ? null : row[props.xKey]);
18
+ } : void 0,
19
+ children: props.series.map((item, index) => {
20
+ const { key, label, color, negativeColor } = item;
21
+ return /* @__PURE__ */ jsx(Bar, {
22
+ dataKey: key,
23
+ name: label,
24
+ fill: seriesFill(item, props.data),
25
+ maxBarSize: props.horizontal ? 24 : void 0,
26
+ stackId: stacked ? "stack" : void 0,
27
+ radius: !stacked ? 2 : index === props.series.length - 1 ? props.horizontal ? [
28
+ 0,
29
+ 2,
30
+ 2,
31
+ 0
32
+ ] : [
33
+ 2,
34
+ 2,
35
+ 0,
36
+ 0
37
+ ] : 0,
38
+ children: (negativeColor || selected !== null) && props.data.map((row, i) => /* @__PURE__ */ jsx(Cell, {
39
+ fill: negativeColor && Number(row[key]) < 0 ? negativeColor : color,
40
+ fillOpacity: selected === null || row[props.xKey] === selected ? 1 : .35
41
+ }, i))
42
+ }, key);
43
+ })
37
44
  });
38
45
  }
39
46
  //#endregion
@@ -6,6 +6,8 @@ export interface SectionCard {
6
6
  value: React$1.ReactNode;
7
7
  /** Outline badge, top right; usually a <Delta />. */
8
8
  badge?: React$1.ReactNode;
9
+ /** A small chart under the value, usually a <Sparkline /> of the recent run. */
10
+ trend?: React$1.ReactNode;
9
11
  /** Bold first footer line. */
10
12
  headline?: React$1.ReactNode;
11
13
  /** Muted second footer line. */
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { Badge } from "../../ui/badge.js";
3
- import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
3
+ import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
4
4
  import { cn } from "../../../lib/utils.js";
5
+ import { useSurface } from "../../siyahi/siyahi-provider.js";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  import "react";
7
8
  //#region src/components/registry/dashboard-01/section-cards.tsx
@@ -13,43 +14,49 @@ const COLUMNS = {
13
14
  6: "@3xl/main:grid-cols-3"
14
15
  };
15
16
  function SectionCards({ items, selected, onSelect }) {
17
+ const ruled = useSurface() === "ruled";
16
18
  return /* @__PURE__ */ jsx("div", {
17
- className: "@container/main",
19
+ className: cn("@container/main", ruled && "overflow-hidden border-y"),
18
20
  children: /* @__PURE__ */ jsx("div", {
19
- className: cn("grid grid-cols-1 gap-4", COLUMNS[items.length] ?? COLUMNS[4]),
20
- children: items.map((item) => /* @__PURE__ */ jsxs(Card, {
21
- className: cn("@container/card", onSelect && "relative", item.label === selected && "ring-2 ring-primary!", onSelect && item.disabled && "opacity-60"),
22
- children: [
23
- onSelect && /* @__PURE__ */ jsx("button", {
24
- type: "button",
25
- "aria-label": item.label,
26
- "aria-pressed": item.label === selected,
27
- disabled: item.disabled,
28
- onClick: () => onSelect(item.label),
29
- className: "absolute inset-0 z-10 rounded-4xl outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset"
30
- }),
31
- /* @__PURE__ */ jsxs(CardHeader, { children: [
32
- /* @__PURE__ */ jsx(CardDescription, { children: item.label }),
33
- /* @__PURE__ */ jsx(CardTitle, {
34
- className: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl",
35
- children: item.value
21
+ className: cn("grid grid-cols-1", ruled ? "-mt-px -ml-px" : "gap-4", COLUMNS[items.length] ?? COLUMNS[4]),
22
+ children: items.map((item) => /* @__PURE__ */ jsx("div", {
23
+ "data-slot": "section-card",
24
+ className: cn("min-w-0", ruled && "border-t border-l"),
25
+ children: /* @__PURE__ */ jsxs(Card, {
26
+ className: cn("@container/card h-full [--card-spacing:--spacing(3)]", onSelect && "relative", item.label === selected && "ring-2 ring-primary!", item.label === selected && ruled && "ring-inset", onSelect && item.disabled && "opacity-60"),
27
+ children: [
28
+ onSelect && /* @__PURE__ */ jsx("button", {
29
+ type: "button",
30
+ "aria-label": item.label,
31
+ "aria-pressed": item.label === selected,
32
+ disabled: item.disabled,
33
+ onClick: () => onSelect(item.label),
34
+ className: "absolute inset-0 z-10 rounded-[inherit] outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset"
36
35
  }),
37
- item.badge && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, {
38
- variant: "outline",
39
- children: item.badge
40
- }) })
41
- ] }),
42
- (item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
43
- className: "flex-col items-start gap-1.5 text-sm",
44
- children: [item.headline && /* @__PURE__ */ jsx("div", {
45
- className: "line-clamp-1 flex gap-2 font-medium",
46
- children: item.headline
47
- }), item.detail && /* @__PURE__ */ jsx("div", {
48
- className: "text-muted-foreground",
49
- children: item.detail
50
- })]
51
- })
52
- ]
36
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
37
+ /* @__PURE__ */ jsx(CardDescription, { children: item.label }),
38
+ /* @__PURE__ */ jsx(CardTitle, {
39
+ className: "text-2xl font-semibold tabular-nums",
40
+ children: item.value
41
+ }),
42
+ item.badge && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, {
43
+ variant: "outline",
44
+ children: item.badge
45
+ }) })
46
+ ] }),
47
+ item.trend && /* @__PURE__ */ jsx(CardContent, { children: item.trend }),
48
+ (item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
49
+ className: "flex-col items-start gap-1 text-sm",
50
+ children: [item.headline && /* @__PURE__ */ jsx("div", {
51
+ className: "flex flex-wrap gap-2 font-medium",
52
+ children: item.headline
53
+ }), item.detail && /* @__PURE__ */ jsx("div", {
54
+ className: "text-muted-foreground",
55
+ children: item.detail
56
+ })]
57
+ })
58
+ ]
59
+ })
53
60
  }, item.label))
54
61
  })
55
62
  });
@@ -1,15 +1,16 @@
1
- import { useLocale } from "../../siyahi/siyahi-provider.js";
2
1
  import { cn } from "../../../lib/utils.js";
2
+ import { useLocale } from "../../siyahi/siyahi-provider.js";
3
3
  import { pct } from "../../../lib/format.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import * as React$1 from "react";
6
6
  //#region src/components/registry/dashboardcn/bar-list.tsx
7
7
  function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(--chart-1)", showPercentage = false, max: scale, onItemClick, className, ...props }) {
8
8
  const locale = useLocale();
9
- const format = valueFormatter ?? ((value) => new Intl.NumberFormat(locale.numberLocale, {
9
+ const defaultFormat = React$1.useMemo(() => new Intl.NumberFormat(locale.numberLocale, {
10
10
  notation: "compact",
11
11
  maximumFractionDigits: 1
12
- }).format(value));
12
+ }).format, [locale.numberLocale]);
13
+ const format = valueFormatter ?? defaultFormat;
13
14
  const items = React$1.useMemo(() => {
14
15
  if (sortOrder === "none") return data;
15
16
  const sorted = [...data].sort((a, b) => a.value - b.value);
@@ -19,7 +20,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
19
20
  const total = items.reduce((sum, item) => sum + item.value, 0);
20
21
  return /* @__PURE__ */ jsx("div", {
21
22
  "data-slot": "bar-list",
22
- className: cn("flex flex-col gap-1.5", className),
23
+ className: cn("@container/bar-list flex flex-col gap-1.5", className),
23
24
  ...props,
24
25
  children: items.map((item) => {
25
26
  const width = max > 0 ? item.value / max * 100 : 0;
@@ -28,13 +29,13 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
28
29
  const Label = item.href ? "a" : onItemClick ? "button" : "div";
29
30
  return /* @__PURE__ */ jsxs("div", {
30
31
  "data-slot": "bar-list-row",
31
- className: "flex items-center gap-3 text-sm",
32
+ className: "flex items-center gap-3 text-sm @max-sm/bar-list:items-start",
32
33
  children: [/* @__PURE__ */ jsxs("div", {
33
- className: "relative flex h-8 min-w-0 flex-1 items-center",
34
+ className: "relative flex h-8 min-w-0 flex-1 items-center @max-sm/bar-list:h-auto @max-sm/bar-list:min-h-8 @max-sm/bar-list:items-start @max-sm/bar-list:pb-3",
34
35
  children: [
35
36
  /* @__PURE__ */ jsx("div", {
36
37
  "aria-hidden": "true",
37
- className: "absolute inset-y-0 left-0 rounded-md transition-[width] duration-300",
38
+ className: "absolute inset-y-0 left-0 rounded-md transition-[width] duration-300 @max-sm/bar-list:inset-y-auto @max-sm/bar-list:bottom-0 @max-sm/bar-list:h-1.5",
38
39
  style: {
39
40
  width: `${width}%`,
40
41
  backgroundColor: `color-mix(in oklab, ${color} 20%, transparent)`
@@ -51,7 +52,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
51
52
  className: "text-muted-foreground shrink-0 [&>svg]:size-4",
52
53
  children: item.icon
53
54
  }) : null, /* @__PURE__ */ jsx("span", {
54
- className: "truncate",
55
+ className: "truncate @max-sm/bar-list:whitespace-normal",
55
56
  children: item.name
56
57
  })]
57
58
  }),
@@ -1,5 +1,5 @@
1
- import { useLocale } from "../../siyahi/siyahi-provider.js";
2
1
  import { cn } from "../../../lib/utils.js";
2
+ import { useLocale } from "../../siyahi/siyahi-provider.js";
3
3
  import { pct } from "../../../lib/format.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import "react";
@@ -0,0 +1,29 @@
1
+ import { Card } from "../../ui/card.js";
2
+ import * as React$1 from "react";
3
+ //#region src/components/registry/dashboardcn/kpi-row-card.d.ts
4
+ export interface MetricStripItem {
5
+ /** Muted label above the figure. */
6
+ label: string;
7
+ /** The figure, formatted by the page: money(4512300). */
8
+ value: React$1.ReactNode;
9
+ /** The signed change, drawn as a Delta under the figure; its sign colours the trend. For "pct", a fraction. */
10
+ change?: number;
11
+ changeFormat?: "pct" | "money";
12
+ /** Muted words after the change, such as "vs last month". */
13
+ note?: React$1.ReactNode;
14
+ /** Recent values, oldest first, drawn as a sparkline beside the figure. */
15
+ trend?: number[];
16
+ }
17
+ export interface MetricStripProps extends Omit<React$1.ComponentProps<typeof Card>, "title"> {
18
+ title?: React$1.ReactNode;
19
+ description?: React$1.ReactNode;
20
+ /** A range toggle, top right, that changes every figure. */
21
+ action?: React$1.ReactNode;
22
+ items: MetricStripItem[];
23
+ /** Lines under the strip, such as what the figures cover. */
24
+ footer?: React$1.ReactNode;
25
+ }
26
+ /** A card holding a row of small figures, each with its change and a sparkline: more numbers in less room than stat tiles. */
27
+ declare function MetricStrip({ title, description, action, items, footer, className, ...props }: MetricStripProps): React$1.JSX.Element;
28
+ //#endregion
29
+ export { MetricStrip };
@@ -0,0 +1,81 @@
1
+ import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
2
+ import { cn } from "../../../lib/utils.js";
3
+ import { Delta } from "../../siyahi/delta.js";
4
+ import { Sparkline } from "./sparkline.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import "react";
7
+ //#region src/components/registry/dashboardcn/kpi-row-card.tsx
8
+ const COLUMNS = {
9
+ 1: "basis-full",
10
+ 2: "basis-full @2xs/strip:basis-1/2",
11
+ 3: "basis-full @lg/strip:basis-1/3",
12
+ 4: "basis-full @2xs/strip:basis-1/2 @3xl/strip:basis-1/4",
13
+ 5: "basis-full @2xs/strip:basis-1/2 @3xl/strip:basis-1/5",
14
+ 6: "basis-full @2xs/strip:basis-1/2 @xl/strip:basis-1/3 @5xl/strip:basis-1/6",
15
+ 8: "basis-full @2xs/strip:basis-1/2 @3xl/strip:basis-1/4 @7xl/strip:basis-1/8"
16
+ };
17
+ function trendColor(change) {
18
+ if (change === void 0) return "var(--chart-1)";
19
+ if (change > 0) return "var(--positive)";
20
+ if (change < 0) return "var(--negative)";
21
+ return "var(--muted-foreground)";
22
+ }
23
+ /** A card holding a row of small figures, each with its change and a sparkline: more numbers in less room than stat tiles. */
24
+ function MetricStrip({ title, description, action, items, footer, className, ...props }) {
25
+ const columns = COLUMNS[Math.min(items.length, 8)] ?? COLUMNS[8];
26
+ return /* @__PURE__ */ jsxs(Card, {
27
+ className: cn("gap-4", className),
28
+ ...props,
29
+ children: [
30
+ (title || description || action) && /* @__PURE__ */ jsxs(CardHeader, { children: [
31
+ title && /* @__PURE__ */ jsx(CardTitle, { children: title }),
32
+ description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
33
+ action && /* @__PURE__ */ jsx(CardAction, { children: action })
34
+ ] }),
35
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", {
36
+ "data-slot": "metric-strip",
37
+ className: "@container/strip -mx-4 -my-3 overflow-hidden",
38
+ children: /* @__PURE__ */ jsx("div", {
39
+ className: "-mt-px -ml-px flex flex-wrap",
40
+ children: items.map((item) => /* @__PURE__ */ jsxs("div", {
41
+ "data-slot": "metric-strip-item",
42
+ className: cn("flex min-w-0 flex-col gap-1 border-t border-l px-4 py-3", columns),
43
+ children: [/* @__PURE__ */ jsx("div", {
44
+ className: "truncate text-xs text-muted-foreground",
45
+ children: item.label
46
+ }), /* @__PURE__ */ jsxs("div", {
47
+ className: "flex flex-wrap items-end justify-between gap-x-3 gap-y-1",
48
+ children: [/* @__PURE__ */ jsxs("div", {
49
+ className: "flex min-w-0 flex-col gap-0.5",
50
+ children: [/* @__PURE__ */ jsx("div", {
51
+ className: "text-xl font-semibold tracking-tight tabular-nums",
52
+ children: item.value
53
+ }), (item.change !== void 0 || item.note) && /* @__PURE__ */ jsxs("div", {
54
+ className: "flex flex-wrap items-center gap-x-1.5 text-xs",
55
+ children: [item.change !== void 0 && /* @__PURE__ */ jsx(Delta, {
56
+ value: item.change,
57
+ format: item.changeFormat
58
+ }), item.note && /* @__PURE__ */ jsx("span", {
59
+ className: "text-muted-foreground",
60
+ children: item.note
61
+ })]
62
+ })]
63
+ }), item.trend && item.trend.length > 1 && /* @__PURE__ */ jsx(Sparkline, {
64
+ data: item.trend,
65
+ variant: "line",
66
+ color: trendColor(item.change),
67
+ className: "h-8 w-16 shrink-0"
68
+ })]
69
+ })]
70
+ }, item.label))
71
+ })
72
+ }) }),
73
+ footer && /* @__PURE__ */ jsx(CardFooter, {
74
+ className: "flex-col items-start gap-2 text-sm",
75
+ children: footer
76
+ })
77
+ ]
78
+ });
79
+ }
80
+ //#endregion
81
+ export { MetricStrip };
@@ -0,0 +1,18 @@
1
+ import { ChartColor } from "../../../lib/chart-series.js";
2
+ import * as React$1 from "react";
3
+ //#region src/components/registry/dashboardcn/sparkline.d.ts
4
+ export interface SparklineProps extends React$1.ComponentProps<"div"> {
5
+ /** Series to plot, oldest first. */
6
+ data: number[];
7
+ variant?: "area" | "line";
8
+ /** A chart slot, or var(--positive) or var(--negative) for a gain or a loss. Defaults to var(--chart-1). */
9
+ color?: ChartColor;
10
+ curve?: "monotone" | "linear" | "step";
11
+ strokeWidth?: number;
12
+ /** Animate after hydration and when data changes. Respects reduced motion. */
13
+ animate?: boolean;
14
+ animationDuration?: number;
15
+ }
16
+ declare function Sparkline({ data, variant, color, curve, strokeWidth, animate, animationDuration, className, ...props }: SparklineProps): React$1.JSX.Element;
17
+ //#endregion
18
+ export { Sparkline };
@@ -0,0 +1,95 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React$1 from "react";
4
+ import { Area, AreaChart, Line, LineChart, ResponsiveContainer, YAxis } from "recharts";
5
+ //#region src/components/registry/dashboardcn/sparkline.tsx
6
+ const reducedMotionQuery = "(prefers-reduced-motion: reduce)";
7
+ function subscribeToReducedMotion(onChange) {
8
+ const query = window.matchMedia(reducedMotionQuery);
9
+ query.addEventListener("change", onChange);
10
+ return () => query.removeEventListener("change", onChange);
11
+ }
12
+ function getReducedMotionSnapshot() {
13
+ return window.matchMedia(reducedMotionQuery).matches;
14
+ }
15
+ function getReducedMotionServerSnapshot() {
16
+ return true;
17
+ }
18
+ function Sparkline({ data, variant = "area", color = "var(--chart-1)", curve = "monotone", strokeWidth = 1.5, animate = true, animationDuration = 500, className, ...props }) {
19
+ const id = React$1.useId();
20
+ const reducedMotion = React$1.useSyncExternalStore(subscribeToReducedMotion, getReducedMotionSnapshot, getReducedMotionServerSnapshot);
21
+ const animationActive = animate && !reducedMotion;
22
+ const points = React$1.useMemo(() => data.map((y, i) => ({
23
+ i,
24
+ y
25
+ })), [data]);
26
+ const margin = {
27
+ top: 2,
28
+ right: 0,
29
+ bottom: 2,
30
+ left: 0
31
+ };
32
+ const axis = /* @__PURE__ */ jsx(YAxis, {
33
+ hide: true,
34
+ domain: ["dataMin", "dataMax"]
35
+ });
36
+ return /* @__PURE__ */ jsx("div", {
37
+ "data-slot": "sparkline",
38
+ "aria-hidden": "true",
39
+ className: cn("h-10 w-full", className),
40
+ ...props,
41
+ children: /* @__PURE__ */ jsx(ResponsiveContainer, {
42
+ width: "100%",
43
+ height: "100%",
44
+ children: variant === "line" ? /* @__PURE__ */ jsxs(LineChart, {
45
+ data: points,
46
+ margin,
47
+ children: [axis, /* @__PURE__ */ jsx(Line, {
48
+ type: curve,
49
+ dataKey: "y",
50
+ stroke: color,
51
+ strokeWidth,
52
+ dot: false,
53
+ isAnimationActive: animationActive,
54
+ animationDuration,
55
+ animationEasing: "ease-out"
56
+ })]
57
+ }) : /* @__PURE__ */ jsxs(AreaChart, {
58
+ data: points,
59
+ margin,
60
+ children: [
61
+ axis,
62
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", {
63
+ id,
64
+ x1: "0",
65
+ y1: "0",
66
+ x2: "0",
67
+ y2: "1",
68
+ children: [/* @__PURE__ */ jsx("stop", {
69
+ offset: "0%",
70
+ stopColor: color,
71
+ stopOpacity: .3
72
+ }), /* @__PURE__ */ jsx("stop", {
73
+ offset: "100%",
74
+ stopColor: color,
75
+ stopOpacity: 0
76
+ })]
77
+ }) }),
78
+ /* @__PURE__ */ jsx(Area, {
79
+ type: curve,
80
+ dataKey: "y",
81
+ stroke: color,
82
+ strokeWidth,
83
+ fill: `url(#${id})`,
84
+ dot: false,
85
+ isAnimationActive: animationActive,
86
+ animationDuration,
87
+ animationEasing: "ease-out"
88
+ })
89
+ ]
90
+ })
91
+ })
92
+ });
93
+ }
94
+ //#endregion
95
+ export { Sparkline };
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
+ import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
4
5
  import { differenceInCalendarDays, eachDayOfInterval, format, formatISO, getDay, getMonth, getYear, nextDay, parseISO, subWeeks } from "date-fns";
@@ -290,7 +291,7 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
290
291
  value: contextValue,
291
292
  children: /* @__PURE__ */ jsx("div", {
292
293
  "data-slot": "calendar-heatmap",
293
- className: cn("flex w-max max-w-full flex-col gap-2 p-4", className),
294
+ className: cn("flex w-max max-w-full flex-col gap-2", className),
294
295
  style: {
295
296
  fontSize,
296
297
  ...style
@@ -349,6 +350,7 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
349
350
  };
350
351
  CalendarHeatmapBlock.displayName = "CalendarHeatmapBlock";
351
352
  const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = false, hideYearLabels = false, className, labelClassName, yearClassName, children, renderYearFooter, ...props }) => {
353
+ const navigation = useHeatmapNavigation();
352
354
  const { yearRows, blockSize, blockWidth, blockMargin, labels, labelHeight, fontSize, weekStart, data } = useCalendarHeatmap();
353
355
  const weekdayLabelWidth = hideWeekdayLabels ? 0 : 40;
354
356
  const strokePadding = 3;
@@ -374,8 +376,13 @@ const CalendarHeatmapBody = ({ hideMonthLabels = false, hideWeekdayLabels = fals
374
376
  const totalWidth = weekdayLabelWidth + Math.max(...rowData.map((r) => r.width)) + 6;
375
377
  return /* @__PURE__ */ jsx("div", {
376
378
  "data-slot": "calendar-heatmap-body",
379
+ ref: navigation.ref,
377
380
  className: cn("flex max-w-full flex-col gap-6 overflow-x-auto overflow-y-hidden py-4", className),
378
381
  ...props,
382
+ onKeyDown: (event) => {
383
+ props.onKeyDown?.(event);
384
+ if (!event.defaultPrevented) navigation.onKeyDown(event);
385
+ },
379
386
  children: rowData.map(({ yearRow, height, monthLabels, yearTotalCount }) => /* @__PURE__ */ jsxs("div", { children: [
380
387
  !hideYearLabels && /* @__PURE__ */ jsx("div", {
381
388
  className: cn("mb-2 text-muted-foreground", yearClassName),
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
+ import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
4
5
  import { format } from "date-fns";
@@ -155,7 +156,7 @@ const StatusHeatmap = ({ data, dateFormat = "MMM d", blockSize = 40, blockMargin
155
156
  value: contextValue,
156
157
  children: /* @__PURE__ */ jsx("div", {
157
158
  "data-slot": "status-heatmap",
158
- className: cn("flex w-max max-w-full flex-col gap-2 p-4", className),
159
+ className: cn("flex w-max max-w-full flex-col gap-2", className),
159
160
  style: {
160
161
  fontSize,
161
162
  ...style
@@ -214,6 +215,7 @@ const StatusHeatmapBlock = ({ ref, activity, dayIndex, highlighted = false, onCe
214
215
  };
215
216
  StatusHeatmapBlock.displayName = "StatusHeatmapBlock";
216
217
  const StatusHeatmapBody = ({ hideDateLabels = false, labelInterval = 30, className, labelClassName, children, ...props }) => {
218
+ const navigation = useHeatmapNavigation();
217
219
  const { data, dates, width, height, blockWidth, blockMargin, fontSize, dateFormat, locale, labels } = useStatusHeatmap();
218
220
  const activityMap = useMemo(() => {
219
221
  const map = /* @__PURE__ */ new Map();
@@ -234,8 +236,13 @@ const StatusHeatmapBody = ({ hideDateLabels = false, labelInterval = 30, classNa
234
236
  const labelHeight = hideDateLabels ? 0 : labelFontSize * 2;
235
237
  return /* @__PURE__ */ jsx("div", {
236
238
  "data-slot": "status-heatmap-body",
239
+ ref: navigation.ref,
237
240
  className: cn("max-w-full overflow-x-auto overflow-y-hidden", className),
238
241
  ...props,
242
+ onKeyDown: (event) => {
243
+ props.onKeyDown?.(event);
244
+ if (!event.defaultPrevented) navigation.onKeyDown(event);
245
+ },
239
246
  children: /* @__PURE__ */ jsxs("svg", {
240
247
  role: "img",
241
248
  "aria-label": labels.heatmapLabel ?? "Status heatmap",
@@ -36,6 +36,10 @@ export type ColorConfig = {
36
36
  export type WeekdayHeatmapProps = HTMLAttributes<HTMLDivElement> & {
37
37
  data: WeekdayHourlyActivity[];
38
38
  weekStart?: Day;
39
+ /** Displayed hours, in order; defaults to the full day. */
40
+ hours?: readonly WeekdayHour[];
41
+ /** Displayed days, in order; defaults to the full week from weekStart. */
42
+ weekdays?: readonly WeekdayIndex[];
39
43
  use12Hour?: boolean;
40
44
  blockSize?: number;
41
45
  blockMargin?: number;
@@ -86,7 +90,7 @@ export type WeekdayHeatmapProps = HTMLAttributes<HTMLDivElement> & {
86
90
  * @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.
87
91
  * @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.
88
92
  */
89
- export declare const WeekdayHeatmap: ({ data, weekStart, use12Hour, blockSize, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
93
+ export declare const WeekdayHeatmap: ({ data, weekStart, hours, weekdays: displayedWeekdays, use12Hour, blockSize, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
90
94
  export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
91
95
  activity: WeekdayHourlyActivityWithLevel;
92
96
  extra?: "row" | "column";
@@ -97,7 +101,7 @@ export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
97
101
  };
98
102
  export declare function WeekdayHeatmapBlock({ ref, activity, extra, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: WeekdayHeatmapBlockProps & {
99
103
  ref?: React.RefObject<SVGRectElement | null>;
100
- }): import("react").JSX.Element;
104
+ }): import("react").JSX.Element | null;
101
105
  export declare namespace WeekdayHeatmapBlock {
102
106
  var displayName: string;
103
107
  }