@siyahi/ui 0.1.9-next.21 → 0.1.9-next.23

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 (93) hide show
  1. package/DESIGN.md +16 -3
  2. package/agents/examples/accordion.tsx +7 -7
  3. package/agents/examples/alert.tsx +16 -15
  4. package/agents/examples/app-sidebar.tsx +3 -1
  5. package/agents/examples/badge.tsx +20 -12
  6. package/agents/examples/bar-list-labels.tsx +6 -6
  7. package/agents/examples/bar-list-sides.tsx +18 -16
  8. package/agents/examples/bar-list.tsx +8 -8
  9. package/agents/examples/breadcrumb.tsx +3 -3
  10. package/agents/examples/calendar.tsx +3 -7
  11. package/agents/examples/callout.tsx +15 -15
  12. package/agents/examples/card.tsx +5 -3
  13. package/agents/examples/change-bar.tsx +21 -21
  14. package/agents/examples/chart-area.tsx +11 -11
  15. package/agents/examples/chart-bar-flows.tsx +1 -1
  16. package/agents/examples/chart-bar-hourly.tsx +1 -1
  17. package/agents/examples/chart-bar-losses.tsx +3 -3
  18. package/agents/examples/chart-bar-teams.tsx +72 -0
  19. package/agents/examples/chart-bar-years.tsx +5 -5
  20. package/agents/examples/chart-bar.tsx +10 -10
  21. package/agents/examples/chart-category-ticks.tsx +6 -6
  22. package/agents/examples/chart-line.tsx +17 -17
  23. package/agents/examples/collapsible.tsx +5 -3
  24. package/agents/examples/command.tsx +23 -21
  25. package/agents/examples/context-menu.tsx +6 -4
  26. package/agents/examples/data-bar.tsx +32 -45
  27. package/agents/examples/data-table-compact.tsx +31 -30
  28. package/agents/examples/data-table-filters.tsx +20 -20
  29. package/agents/examples/data-table.tsx +31 -30
  30. package/agents/examples/dialog.tsx +3 -3
  31. package/agents/examples/distribution-bar-tug.tsx +36 -34
  32. package/agents/examples/dropdown-menu.tsx +2 -2
  33. package/agents/examples/heatmap-calendar.tsx +12 -10
  34. package/agents/examples/heatmap-status.tsx +11 -9
  35. package/agents/examples/heatmap-weekday.tsx +27 -30
  36. package/agents/examples/input-states.tsx +9 -9
  37. package/agents/examples/input.tsx +3 -3
  38. package/agents/examples/local-time.tsx +1 -1
  39. package/agents/examples/metric-strip.tsx +8 -8
  40. package/agents/examples/page-hero.tsx +2 -2
  41. package/agents/examples/popover.tsx +3 -3
  42. package/agents/examples/price-chart.tsx +5 -5
  43. package/agents/examples/progress-tone.tsx +10 -9
  44. package/agents/examples/progress.tsx +1 -1
  45. package/agents/examples/range-position-table.tsx +31 -31
  46. package/agents/examples/range-position.tsx +31 -29
  47. package/agents/examples/scroll-area.tsx +12 -10
  48. package/agents/examples/section-cards-select.tsx +11 -10
  49. package/agents/examples/section-cards-trend.tsx +4 -4
  50. package/agents/examples/section-cards.tsx +13 -13
  51. package/agents/examples/select.tsx +9 -9
  52. package/agents/examples/separator.tsx +3 -3
  53. package/agents/examples/sheet.tsx +1 -1
  54. package/agents/examples/slider.tsx +1 -1
  55. package/agents/examples/surfaces.tsx +16 -16
  56. package/agents/examples/tabs.tsx +7 -7
  57. package/agents/examples/target-rows.tsx +31 -27
  58. package/agents/examples/tooltip.tsx +2 -2
  59. package/agents/examples/treemap.tsx +38 -33
  60. package/agents/manifest.json +15 -15
  61. package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
  62. package/dist/components/registry/charts/bar-chart-card.js +6 -2
  63. package/dist/components/registry/charts/line-chart-card.d.ts +1 -1
  64. package/dist/components/registry/charts/line-chart-card.js +1 -1
  65. package/dist/components/registry/dashboard-01/section-cards.js +1 -1
  66. package/dist/components/registry/dashboardcn/distribution-bar.js +5 -3
  67. package/dist/components/registry/heatmap/status-heatmap.js +1 -1
  68. package/dist/components/siyahi/callout.d.ts +5 -12
  69. package/dist/components/siyahi/callout.js +5 -11
  70. package/dist/components/siyahi/delta.d.ts +5 -1
  71. package/dist/components/siyahi/delta.js +14 -9
  72. package/dist/components/siyahi/status-alert.d.ts +15 -0
  73. package/dist/components/siyahi/status-alert.js +29 -0
  74. package/dist/components/siyahi/status-badge.d.ts +15 -0
  75. package/dist/components/siyahi/status-badge.js +24 -0
  76. package/dist/components/siyahi/tone-progress.d.ts +1 -1
  77. package/dist/components/siyahi/tone-progress.js +1 -1
  78. package/dist/components/siyahi/treemap.d.ts +4 -4
  79. package/dist/components/siyahi/treemap.js +5 -5
  80. package/dist/index.d.ts +5 -4
  81. package/dist/index.js +3 -2
  82. package/dist/lib/chart-frame.js +1 -1
  83. package/dist/lib/chart-series.d.ts +1 -1
  84. package/dist/lib/format.d.ts +1 -1
  85. package/dist/lib/format.js +1 -1
  86. package/dist/lib/tone.d.ts +36 -0
  87. package/dist/lib/tone.js +35 -0
  88. package/package.json +1 -1
  89. package/report.css +14 -14
  90. package/styles/siyahi.css +92 -20
  91. package/agents/examples/chart-bar-sectors.tsx +0 -72
  92. package/dist/components/siyahi/warning-alert.d.ts +0 -10
  93. package/dist/components/siyahi/warning-alert.js +0 -20
@@ -14,42 +14,45 @@ import {
14
14
  } from "@siyahi/ui";
15
15
  import { useState } from "react";
16
16
 
17
- interface Holding extends TreemapItem {
18
- broker: string;
17
+ interface City extends TreemapItem {
18
+ channel: string;
19
19
  }
20
20
 
21
- // change is the return since bought; LT came in as a transfer, with no cost basis.
22
- const holdings: Holding[] = [
23
- { name: "INFY", key: "INFY-kite", broker: "Kite", group: "IT", weight: 224400, change: 0.38 },
24
- { name: "INFY", key: "INFY-dhan", broker: "Dhan", group: "IT", weight: 152000, change: 0.12 },
25
- { name: "TCS", broker: "Kite", group: "IT", weight: 166000, change: 0.09 },
26
- { name: "WIPRO", broker: "Dhan", group: "IT", weight: 98000, change: -0.21 },
27
- { name: "HDFCBANK", broker: "Kite", group: "Banks", weight: 262500, change: -0.06 },
28
- { name: "ICICIBANK", broker: "Kite", group: "Banks", weight: 258000, change: 0.44 },
29
- { name: "SBIN", broker: "Dhan", group: "Banks", weight: 87000, change: 0.58 },
30
- { name: "ITC", broker: "Kite", group: "FMCG", weight: 276000, change: 0.27 },
31
- { name: "ASIANPAINT", broker: "Dhan", group: "FMCG", weight: 64000, change: -0.33 },
32
- { name: "NIFTYBEES", broker: "Kite", group: "ETF", weight: 255600, change: 0.16 },
33
- { name: "LT", broker: "Dhan", group: "Construction", weight: 52000, change: null },
21
+ // weight is the month's sales in ₹ and change their growth on the same month last year; Guwahati opened this year,
22
+ // so it has no last year to grow from. Mumbai sells online and in stores, so it draws two tiles under one name.
23
+ const cities: City[] = [
24
+ { name: "Mumbai", key: "mumbai-online", channel: "Online", group: "West", weight: 224400, change: 0.38 },
25
+ { name: "Mumbai", key: "mumbai-stores", channel: "Stores", group: "West", weight: 152000, change: 0.12 },
26
+ { name: "Pune", channel: "Stores", group: "West", weight: 166000, change: 0.09 },
27
+ { name: "Surat", channel: "Online", group: "West", weight: 98000, change: -0.21 },
28
+ { name: "Bengaluru", channel: "Stores", group: "South", weight: 262500, change: -0.06 },
29
+ { name: "Chennai", channel: "Stores", group: "South", weight: 258000, change: 0.44 },
30
+ { name: "Kochi", channel: "Online", group: "South", weight: 87000, change: 0.58 },
31
+ { name: "Delhi", channel: "Stores", group: "North", weight: 276000, change: 0.27 },
32
+ { name: "Jaipur", channel: "Online", group: "North", weight: 64000, change: -0.33 },
33
+ { name: "Kolkata", channel: "Stores", group: "East", weight: 255600, change: 0.16 },
34
+ { name: "Guwahati", channel: "Online", group: "Northeast", weight: 52000, change: null },
34
35
  ];
35
36
 
36
37
  export default function TreemapExample() {
37
- const [hovered, setHovered] = useState<Holding | null>(null);
38
- const [selected, setSelected] = useState<Holding | null>(null);
38
+ const [hovered, setHovered] = useState<City | null>(null);
39
+ const [selected, setSelected] = useState<City | null>(null);
39
40
  const { money, pct } = useFormat();
40
- const figures = (h: Holding) =>
41
- `${money(h.weight)} · ${h.change === null ? "no cost basis" : `${h.change < 0 ? "▼" : "▲"} ${pct(Math.abs(h.change))}`}`;
41
+ const figures = (c: City) =>
42
+ `${money(c.weight)} · ${c.change === null ? "new this year" : `${c.change < 0 ? "\u2212" : "+"}${pct(Math.abs(c.change))}`}`;
42
43
  return (
43
44
  <Card>
44
45
  <CardHeader>
45
- <CardTitle>Holdings by sector</CardTitle>
46
- <CardDescription>Area is the holding's value; colour is its return, deepest at ±60%</CardDescription>
46
+ <CardTitle>Sales by region</CardTitle>
47
+ <CardDescription>
48
+ Area is the city's sales this month; colour is its growth on last year, deepest at ±60%
49
+ </CardDescription>
47
50
  </CardHeader>
48
51
  <CardContent>
49
52
  <Tooltip open={hovered !== null} trackCursorAxis="both">
50
53
  <TooltipTrigger render={<div />}>
51
54
  <Treemap
52
- items={holdings}
55
+ items={cities}
53
56
  height={320}
54
57
  range={0.6}
55
58
  onHover={setHovered}
@@ -57,16 +60,18 @@ export default function TreemapExample() {
57
60
  onSelect={setSelected}
58
61
  />
59
62
  </TooltipTrigger>
60
- <TooltipContent side="bottom" className="flex-col items-start gap-0 tabular-nums">
61
- {hovered && (
62
- <>
63
- <span className="font-medium">
64
- {hovered.name} · {hovered.broker}
65
- </span>
66
- <span>{hovered.group}</span>
67
- <span>{figures(hovered)}</span>
68
- </>
69
- )}
63
+ <TooltipContent side="bottom">
64
+ <div className="flex flex-col items-start">
65
+ {hovered && (
66
+ <>
67
+ <span className="font-medium">
68
+ {hovered.name} · {hovered.channel}
69
+ </span>
70
+ <span>{hovered.group}</span>
71
+ <span>{figures(hovered)}</span>
72
+ </>
73
+ )}
74
+ </div>
70
75
  </TooltipContent>
71
76
  </Tooltip>
72
77
  <TreemapLegend range={0.6} className="mt-3 ml-auto" />
@@ -74,7 +79,7 @@ export default function TreemapExample() {
74
79
  {selected ? (
75
80
  <>
76
81
  <span className="font-medium">
77
- {selected.name} · {selected.broker}
82
+ {selected.name} · {selected.channel}
78
83
  </span>{" "}
79
84
  · {selected.group} · {figures(selected)}
80
85
  </>
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "needs": [
18
18
  {
19
- "need": "Values over time or another ordered axis (months, hours, price buckets)",
19
+ "need": "Values over time or another ordered axis (months, hours, size buckets)",
20
20
  "guidance": "`BarChartCard`, `AreaChartCard` or `LineChartCard`",
21
21
  "components": [
22
22
  "BarChartCard",
@@ -32,14 +32,14 @@
32
32
  ]
33
33
  },
34
34
  {
35
- "need": "A running level, such as a portfolio's value against money put in",
35
+ "need": "A running level, such as revenue against its target",
36
36
  "guidance": "`AreaChartCard`",
37
37
  "components": [
38
38
  "AreaChartCard"
39
39
  ]
40
40
  },
41
41
  {
42
- "need": "Series compared point by point, such as a return against an index",
42
+ "need": "Series compared point by point, such as growth against a benchmark",
43
43
  "guidance": "`LineChartCard`",
44
44
  "components": [
45
45
  "LineChartCard"
@@ -53,7 +53,7 @@
53
53
  ]
54
54
  },
55
55
  {
56
- "need": "Long category names, such as sectors",
56
+ "need": "Long category names, such as teams or product categories",
57
57
  "guidance": "`BarChartCard` with `horizontal`",
58
58
  "components": [
59
59
  "BarChartCard"
@@ -118,28 +118,28 @@
118
118
  ]
119
119
  },
120
120
  {
121
- "need": "Figures against their targets, such as allocation against plan",
121
+ "need": "Figures against their targets, such as spend against budget",
122
122
  "guidance": "`TargetRows`, with neutral `bands` and a `status` per row",
123
123
  "components": [
124
124
  "TargetRows"
125
125
  ]
126
126
  },
127
127
  {
128
- "need": "Where a value sits in its range, such as a price in its 52-week range",
128
+ "need": "Where a value sits in its range, such as today's response time in its 30-day range",
129
129
  "guidance": "`RangePosition`; `variant=\"compact\"` in a table cell",
130
130
  "components": [
131
131
  "RangePosition"
132
132
  ]
133
133
  },
134
134
  {
135
- "need": "One instrument's price history",
135
+ "need": "A long daily series, such as a price or an order value",
136
136
  "guidance": "`PriceChart`",
137
137
  "components": [
138
138
  "PriceChart"
139
139
  ]
140
140
  },
141
141
  {
142
- "need": "Holdings sized by weight and coloured by change",
142
+ "need": "Items sized by weight and coloured by change, such as sales by city",
143
143
  "guidance": "`Treemap`",
144
144
  "components": [
145
145
  "Treemap"
@@ -304,10 +304,9 @@
304
304
  {
305
305
  "file": "agents/examples/alert.tsx",
306
306
  "components": [
307
- "Alert",
308
307
  "AlertDescription",
309
308
  "AlertTitle",
310
- "WarningAlert"
309
+ "StatusAlert"
311
310
  ]
312
311
  },
313
312
  {
@@ -328,7 +327,8 @@
328
327
  "file": "agents/examples/badge.tsx",
329
328
  "components": [
330
329
  "Badge",
331
- "Delta"
330
+ "Delta",
331
+ "StatusBadge"
332
332
  ]
333
333
  },
334
334
  {
@@ -401,11 +401,11 @@
401
401
  {
402
402
  "file": "agents/examples/callout.tsx",
403
403
  "components": [
404
+ "AlertDescription",
405
+ "AlertTitle",
404
406
  "Callout",
405
407
  "Quote",
406
- "WarningAlert",
407
- "AlertDescription",
408
- "AlertTitle"
408
+ "StatusAlert"
409
409
  ]
410
410
  },
411
411
  {
@@ -472,7 +472,7 @@
472
472
  ]
473
473
  },
474
474
  {
475
- "file": "agents/examples/chart-bar-sectors.tsx",
475
+ "file": "agents/examples/chart-bar-teams.tsx",
476
476
  "components": [
477
477
  "BarChartCard",
478
478
  "ToggleGroup",
@@ -3,7 +3,7 @@ import { SeriesChartProps } from "../../../lib/chart-series.js";
3
3
  export interface BarChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K> {
4
4
  /** Stack the series in one bar per point instead of side by side. */
5
5
  stacked?: boolean;
6
- /** Bars run sideways, categories down the left: for long names, such as sectors. */
6
+ /** Bars run sideways, categories down the left: for long names, such as product categories. */
7
7
  horizontal?: boolean;
8
8
  /** The picked category's x value: its bars stay solid and the rest fade. */
9
9
  selected?: T[K] | null;
@@ -42,9 +42,13 @@ function BarChartCard({ stacked = false, selected = null, onSelect, ...props })
42
42
  const tone = negativeColor && Number(row[key]) < 0 ? negativeColor : color;
43
43
  return negativeColor ? /* @__PURE__ */ jsx(Cell, {
44
44
  fill: signedFill(tone, isSelected),
45
- stroke: dimUnselected(tone, isSelected),
45
+ stroke: tone,
46
46
  strokeWidth: 1.5
47
- }, i) : /* @__PURE__ */ jsx(Cell, { fill: dimUnselected(tone, isSelected) }, i);
47
+ }, i) : /* @__PURE__ */ jsx(Cell, {
48
+ fill: dimUnselected(tone, isSelected),
49
+ stroke: tone,
50
+ strokeWidth: 1.5
51
+ }, i);
48
52
  })
49
53
  }, key);
50
54
  })
@@ -3,6 +3,6 @@ import { ChartCurve, NumericXProps, SeriesChartProps } from "../../../lib/chart-
3
3
  export interface LineChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps {
4
4
  curve?: ChartCurve;
5
5
  }
6
- /** Values over an ordered axis as lines: one series, or several to compare, such as a portfolio against its index. */
6
+ /** Values over an ordered axis as lines: one series, or several to compare, such as sales against a target. */
7
7
  export declare function LineChartCard<T extends object, K extends keyof T>({ curve, ...props }: LineChartCardProps<T, K>): import("react").JSX.Element;
8
8
  //#endregion
@@ -3,7 +3,7 @@ import { ChartFrame, endDot } from "../../../lib/chart-frame.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { Line, LineChart } from "recharts";
5
5
  //#region src/components/registry/charts/line-chart-card.tsx
6
- /** Values over an ordered axis as lines: one series, or several to compare, such as a portfolio against its index. */
6
+ /** Values over an ordered axis as lines: one series, or several to compare, such as sales against a target. */
7
7
  function LineChartCard({ curve = "monotone", ...props }) {
8
8
  return /* @__PURE__ */ jsx(ChartFrame, {
9
9
  chart: LineChart,
@@ -31,7 +31,7 @@ function SectionCards({ items, selected, onSelect }) {
31
31
  "aria-pressed": item.label === selected,
32
32
  disabled: item.disabled,
33
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"
34
+ className: "absolute inset-0 z-10 rounded-[inherit] outline-none focus-visible:ring-3 focus-visible:ring-ring focus-visible:ring-inset"
35
35
  }),
36
36
  /* @__PURE__ */ jsxs(CardHeader, { children: [
37
37
  /* @__PURE__ */ jsx(CardDescription, { children: item.label }),
@@ -66,12 +66,14 @@ function DistributionBar({ segments, valueFormatter, showLegend = true, showValu
66
66
  onMouseLeave: onHover ? () => onHover(null) : void 0,
67
67
  children: resolved.map((segment) => segment.width > 0 ? /* @__PURE__ */ jsx("div", {
68
68
  "data-slot": "distribution-segment",
69
- className: "border-card absolute inset-y-0 border-x transition-[width,left,opacity] duration-300",
69
+ className: "border-card absolute inset-y-0 border-x transition-[width,left,background-color] duration-300",
70
70
  style: {
71
71
  left: `${segment.left}%`,
72
72
  width: `${segment.width}%`,
73
- backgroundColor: segment.color,
74
- opacity: highlighted === null || highlighted === segment.key ? 1 : .35
73
+ ...highlighted === null || highlighted === segment.key ? { backgroundColor: segment.color } : {
74
+ backgroundColor: `color-mix(in oklab, ${segment.color} 35%, var(--card))`,
75
+ boxShadow: `inset 0 0 0 1px ${segment.color}`
76
+ }
75
77
  },
76
78
  onMouseEnter: onHover ? () => onHover(segment) : void 0
77
79
  }, segment.key) : null)
@@ -322,7 +322,7 @@ const StatusHeatmapLegend = ({ labels: labelsProp, className, children, ...props
322
322
  return /* @__PURE__ */ jsx("fieldset", {
323
323
  "data-slot": "status-heatmap-legend",
324
324
  "aria-label": labels.legendLabel ?? "Status legend",
325
- className: cn("ml-auto flex items-center gap-1", className),
325
+ className: cn("ml-auto flex items-center gap-3", className),
326
326
  ...props,
327
327
  children: statuses.map((status) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({
328
328
  value: status.value,
@@ -1,24 +1,17 @@
1
+ import { Tone } from "../../lib/tone.js";
1
2
  import { ComponentProps, ReactNode } from "react";
2
3
  //#region src/components/siyahi/callout.d.ts
3
- declare const TONES: {
4
- readonly neutral: "border-border bg-muted/50 [&_[data-slot=callout-icon]]:text-muted-foreground";
5
- readonly accent: "border-chart-1/35 bg-chart-1/8 [&_[data-slot=callout-icon]]:text-link";
6
- readonly warning: "border-warning/45 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning";
7
- readonly positive: "border-positive/40 bg-positive/8 [&_[data-slot=callout-icon]]:text-positive";
8
- readonly negative: "border-negative/40 bg-negative/8 [&_[data-slot=callout-icon]]:text-negative";
9
- };
10
4
  export interface CalloutProps extends Omit<ComponentProps<"aside">, "title"> {
11
5
  /** neutral for an aside, accent for a key point, warning for a caution, positive or negative for a gain or a loss. */
12
- tone?: keyof typeof TONES;
6
+ tone?: Tone;
13
7
  /** A lucide icon, drawn in the tone's colour. */
14
8
  icon?: ReactNode;
15
9
  title?: ReactNode;
16
10
  }
17
11
  /**
18
12
  * A point set apart from the prose: a takeaway, a caveat, a definition. The whole box takes the tone, a faint wash
19
- * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use Alert or
20
- * WarningAlert instead.
13
+ * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use StatusAlert
14
+ * instead.
21
15
  */
22
16
  export declare function Callout({ tone, icon, title, className, children, ...props }: CalloutProps): import("react").JSX.Element;
23
- //#endregion
24
- export {};
17
+ //#endregion
@@ -1,28 +1,22 @@
1
+ import { TONES } from "../../lib/tone.js";
1
2
  import { cn } from "cn";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region src/components/siyahi/callout.tsx
4
- const TONES = {
5
- neutral: "border-border bg-muted/50 [&_[data-slot=callout-icon]]:text-muted-foreground",
6
- accent: "border-chart-1/35 bg-chart-1/8 [&_[data-slot=callout-icon]]:text-link",
7
- warning: "border-warning/45 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning",
8
- positive: "border-positive/40 bg-positive/8 [&_[data-slot=callout-icon]]:text-positive",
9
- negative: "border-negative/40 bg-negative/8 [&_[data-slot=callout-icon]]:text-negative"
10
- };
11
5
  /**
12
6
  * A point set apart from the prose: a takeaway, a caveat, a definition. The whole box takes the tone, a faint wash
13
- * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use Alert or
14
- * WarningAlert instead.
7
+ * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use StatusAlert
8
+ * instead.
15
9
  */
16
10
  function Callout({ tone = "neutral", icon, title, className, children, ...props }) {
17
11
  return /* @__PURE__ */ jsxs("aside", {
18
12
  "data-slot": "callout",
19
13
  "data-tone": tone,
20
- className: cn("flex gap-3 rounded-lg border px-4 py-3 text-sm", TONES[tone], className),
14
+ className: cn("flex gap-3 rounded-lg border px-4 py-3 text-sm", TONES[tone].box, className),
21
15
  ...props,
22
16
  children: [icon && /* @__PURE__ */ jsx("span", {
23
17
  "data-slot": "callout-icon",
24
18
  "aria-hidden": true,
25
- className: "mt-0.5 shrink-0 [&>svg]:size-4",
19
+ className: cn("mt-0.5 shrink-0 [&>svg]:size-4", TONES[tone].ink),
26
20
  children: icon
27
21
  }), /* @__PURE__ */ jsxs("div", {
28
22
  className: "flex min-w-0 flex-col gap-1",
@@ -5,6 +5,10 @@ export interface DeltaProps {
5
5
  format?: "pct" | "money";
6
6
  className?: string;
7
7
  }
8
- /** A signed figure: ▲ in --positive, ▼ in --negative, so colour is never the only cue. */
8
+ /**
9
+ * A signed change: the figure in the surrounding text colour with its sign, and a trend icon in --positive or
10
+ * --negative. The sign and the icon's shape carry the direction, so colour is never the only cue, and a column of
11
+ * changes reads as figures rather than a wall of red and green.
12
+ */
9
13
  export declare function Delta({ value, format, className }: DeltaProps): import("react").JSX.Element;
10
14
  //#endregion
@@ -1,24 +1,29 @@
1
1
  import { useFormat } from "../../lib/format.js";
2
2
  import { cn } from "cn";
3
+ import { MinusIcon, TrendingDownIcon, TrendingUpIcon } from "lucide-react";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/components/siyahi/delta.tsx
5
- /** A signed figure: ▲ in --positive, ▼ in --negative, so colour is never the only cue. */
6
+ const MINUS = "−";
7
+ /**
8
+ * A signed change: the figure in the surrounding text colour with its sign, and a trend icon in --positive or
9
+ * --negative. The sign and the icon's shape carry the direction, so colour is never the only cue, and a column of
10
+ * changes reads as figures rather than a wall of red and green.
11
+ */
6
12
  function Delta({ value, format = "pct", className }) {
7
13
  const f = useFormat();
8
14
  const size = format === "money" ? f.money(Math.abs(value)) : f.pct(Math.abs(value));
9
- const direction = value > 0 ? "up" : value < 0 ? "down" : null;
15
+ const direction = value > 0 ? "up" : value < 0 ? "down" : "none";
16
+ const Icon = direction === "up" ? TrendingUpIcon : direction === "down" ? TrendingDownIcon : MinusIcon;
10
17
  return /* @__PURE__ */ jsxs("span", {
11
18
  "data-slot": "delta",
12
- className: cn("inline-flex items-center gap-1 tabular-nums", direction === "up" && "text-positive", direction === "down" && "text-negative", direction === null && "text-muted-foreground", className),
19
+ "data-direction": direction,
20
+ className: cn("inline-flex items-center gap-1 whitespace-nowrap", className),
13
21
  children: [
14
- direction && /* @__PURE__ */ jsx("span", {
22
+ /* @__PURE__ */ jsx(Icon, {
15
23
  "aria-hidden": "true",
16
- children: direction === "up" ? "▲" : "▼"
17
- }),
18
- direction && /* @__PURE__ */ jsx("span", {
19
- className: "sr-only",
20
- children: direction
24
+ className: cn("size-3.5 shrink-0", direction === "up" && "text-positive", direction === "down" && "text-negative", direction === "none" && "text-muted-foreground")
21
25
  }),
26
+ direction === "up" ? "+" : direction === "down" ? MINUS : "",
22
27
  size
23
28
  ]
24
29
  });
@@ -0,0 +1,15 @@
1
+ import { Tone } from "../../lib/tone.js";
2
+ import { Alert } from "../ui/alert.js";
3
+ import { ComponentProps } from "react";
4
+ //#region src/components/siyahi/status-alert.d.ts
5
+ export interface StatusAlertProps extends Omit<ComponentProps<typeof Alert>, "variant"> {
6
+ /** neutral for information, warning for a caution, negative for an error, positive for a success. */
7
+ tone?: Tone;
8
+ }
9
+ /**
10
+ * A notice the reader must not miss: the whole card takes its tone, a faint wash inside a border of the same colour,
11
+ * with the tone's icon. Title and description stay in the text colours so they read on the wash. Never mark a notice
12
+ * with a single coloured edge. Put an AlertTitle and an AlertDescription inside.
13
+ */
14
+ export declare function StatusAlert({ tone, className, children, ...props }: StatusAlertProps): import("react").JSX.Element;
15
+ //#endregion
@@ -0,0 +1,29 @@
1
+ import { Alert } from "../ui/alert.js";
2
+ import { TONES } from "../../lib/tone.js";
3
+ import { cn } from "cn";
4
+ import { CircleAlertIcon, CircleCheckIcon, InfoIcon, LightbulbIcon, TriangleAlertIcon } from "lucide-react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/components/siyahi/status-alert.tsx
7
+ const ICONS = {
8
+ neutral: InfoIcon,
9
+ accent: LightbulbIcon,
10
+ warning: TriangleAlertIcon,
11
+ positive: CircleCheckIcon,
12
+ negative: CircleAlertIcon
13
+ };
14
+ /**
15
+ * A notice the reader must not miss: the whole card takes its tone, a faint wash inside a border of the same colour,
16
+ * with the tone's icon. Title and description stay in the text colours so they read on the wash. Never mark a notice
17
+ * with a single coloured edge. Put an AlertTitle and an AlertDescription inside.
18
+ */
19
+ function StatusAlert({ tone = "neutral", className, children, ...props }) {
20
+ const Icon = ICONS[tone];
21
+ return /* @__PURE__ */ jsxs(Alert, {
22
+ "data-tone": tone,
23
+ className: cn("gap-x-3 px-4 py-3", TONES[tone].box, className),
24
+ ...props,
25
+ children: [/* @__PURE__ */ jsx(Icon, { className: TONES[tone].ink }), children]
26
+ });
27
+ }
28
+ //#endregion
29
+ export { StatusAlert };
@@ -0,0 +1,15 @@
1
+ import { Tone } from "../../lib/tone.js";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { ComponentProps } from "react";
4
+ //#region src/components/siyahi/status-badge.d.ts
5
+ export interface StatusBadgeProps extends Omit<ComponentProps<typeof Badge>, "variant"> {
6
+ /** positive for done or paid, warning for pending, negative for failed, accent for new, neutral for a draft. */
7
+ tone?: Tone;
8
+ }
9
+ /**
10
+ * A state, such as Paid, Pending or Failed: the badge takes its tone's wash and border, and a dot in the tone's
11
+ * colour marks it. The label stays in the text colour, since small coloured text on its own wash falls under 4.5:1.
12
+ * Categories (a team, a region) are not states: they stay outline badges.
13
+ */
14
+ export declare function StatusBadge({ tone, className, children, ...props }: StatusBadgeProps): import("react").JSX.Element;
15
+ //#endregion
@@ -0,0 +1,24 @@
1
+ import { Badge } from "../ui/badge.js";
2
+ import { TONES } from "../../lib/tone.js";
3
+ import { cn } from "cn";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/siyahi/status-badge.tsx
6
+ /**
7
+ * A state, such as Paid, Pending or Failed: the badge takes its tone's wash and border, and a dot in the tone's
8
+ * colour marks it. The label stays in the text colour, since small coloured text on its own wash falls under 4.5:1.
9
+ * Categories (a team, a region) are not states: they stay outline badges.
10
+ */
11
+ function StatusBadge({ tone = "neutral", className, children, ...props }) {
12
+ return /* @__PURE__ */ jsxs(Badge, {
13
+ variant: "outline",
14
+ "data-tone": tone,
15
+ className: cn(TONES[tone].box, className),
16
+ ...props,
17
+ children: [/* @__PURE__ */ jsx("span", {
18
+ "aria-hidden": "true",
19
+ className: cn("size-1.5 rounded-full", TONES[tone].dot)
20
+ }), children]
21
+ });
22
+ }
23
+ //#endregion
24
+ export { StatusBadge };
@@ -9,7 +9,7 @@ export interface ToneProgressProps extends ComponentProps<typeof Progress> {
9
9
  /** The meaning colour of the bar: a gain's share in --positive, a loss's in --negative. */
10
10
  tone: keyof typeof TONE;
11
11
  }
12
- /** shadcn's Progress with its bar in a meaning colour, such as each position's share of the booked P&L. */
12
+ /** shadcn's Progress with its bar in a meaning colour, such as each channel's share of profit or loss. */
13
13
  export declare function ToneProgress({ tone, className, ...props }: ToneProgressProps): import("react").JSX.Element;
14
14
  //#endregion
15
15
  export {};
@@ -6,7 +6,7 @@ const TONE = {
6
6
  positive: "**:data-[slot=progress-indicator]:bg-positive",
7
7
  negative: "**:data-[slot=progress-indicator]:bg-negative"
8
8
  };
9
- /** shadcn's Progress with its bar in a meaning colour, such as each position's share of the booked P&L. */
9
+ /** shadcn's Progress with its bar in a meaning colour, such as each channel's share of profit or loss. */
10
10
  function ToneProgress({ tone, className, ...props }) {
11
11
  return /* @__PURE__ */ jsx(Progress, {
12
12
  "data-tone": tone,
@@ -1,13 +1,13 @@
1
1
  //#region src/components/siyahi/treemap.d.ts
2
2
  export interface TreemapItem {
3
3
  name: string;
4
- /** Tile area, such as the holding's value. Zero, negative and non-finite weights draw no tile. */
4
+ /** Tile area, such as a city's sales. Zero, negative and non-finite weights draw no tile. */
5
5
  weight: number;
6
6
  /** Signed change as a fraction; sets the heat. `null` when unknown: a muted tile marked "—". */
7
7
  change: number | null;
8
- /** Unique key when names repeat, such as one symbol held at two brokers. Defaults to `name`. */
8
+ /** Unique key when names repeat, such as one city sold to online and in stores. Defaults to `name`. */
9
9
  key?: string;
10
- /** Tiles of one group, such as a sector, sit together under the group's name, even a group of one. */
10
+ /** Tiles of one group, such as a region, sit together under the group's name, even a group of one. */
11
11
  group?: string;
12
12
  }
13
13
  export interface TreemapProps<T extends TreemapItem> {
@@ -29,7 +29,7 @@ export interface TreemapProps<T extends TreemapItem> {
29
29
  */
30
30
  onSelect?: (item: T | null) => void;
31
31
  }
32
- /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
32
+ /** Heat map of parts of a whole: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
33
33
  export declare function Treemap<T extends TreemapItem>({ items, height, range, onHover, selected, onSelect }: TreemapProps<T>): import("react").JSX.Element;
34
34
  export interface TreemapLegendProps {
35
35
  /** The Treemap's own range, so each swatch matches its tiles. */
@@ -5,7 +5,7 @@ import { cn } from "cn";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { useEffect, useEffectEvent, useMemo, useState } from "react";
7
7
  //#region src/components/siyahi/treemap.tsx
8
- /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
8
+ /** Heat map of parts of a whole: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
9
9
  function Treemap({ items, height, range, onHover, selected, onSelect }) {
10
10
  const [ref, width] = useElementWidth();
11
11
  const [focusedKey, setFocusedKey] = useState(null);
@@ -27,7 +27,7 @@ function Treemap({ items, height, range, onHover, selected, onSelect }) {
27
27
  height
28
28
  ]);
29
29
  const focusKey = tiles.some(({ item }) => (item.key ?? item.name) === focusedKey) ? focusedKey : tiles[0]?.item.key ?? tiles[0]?.item.name;
30
- const change = (value) => value === null ? "—" : `${value > 0 ? "▲ " : value < 0 ? "▼ " : ""}${pct(Math.abs(value))}`;
30
+ const change = (value) => value === null ? "—" : `${value > 0 ? "+" : value < 0 ? "−" : ""}${pct(Math.abs(value))}`;
31
31
  return /* @__PURE__ */ jsx("div", {
32
32
  ref,
33
33
  "data-slot": "treemap",
@@ -36,7 +36,7 @@ function Treemap({ items, height, range, onHover, selected, onSelect }) {
36
36
  width,
37
37
  height,
38
38
  role: onSelect ? "group" : "img",
39
- "aria-label": "Treemap of holdings by weight and change",
39
+ "aria-label": "Treemap by weight and change",
40
40
  className: "text-xs",
41
41
  onPointerLeave: (event) => event.pointerType === "mouse" && onHover?.(null),
42
42
  children: [groups.map(({ group, ...box }) => /* @__PURE__ */ jsxs("svg", {
@@ -193,9 +193,9 @@ function TreemapLegend({ range, className }) {
193
193
  /* @__PURE__ */ jsxs("div", {
194
194
  className: "flex justify-between gap-2 tabular-nums",
195
195
  children: [
196
- /* @__PURE__ */ jsxs("span", { children: ["▼ over ", over] }),
196
+ /* @__PURE__ */ jsxs("span", { children: ["down over ", over] }),
197
197
  /* @__PURE__ */ jsxs("span", { children: [pct(step, digits), " a step"] }),
198
- /* @__PURE__ */ jsxs("span", { children: ["▲ over ", over] })
198
+ /* @__PURE__ */ jsxs("span", { children: ["up over ", over] })
199
199
  ]
200
200
  }),
201
201
  /* @__PURE__ */ jsxs("p", {