@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.
- package/DESIGN.md +16 -3
- package/agents/examples/accordion.tsx +7 -7
- package/agents/examples/alert.tsx +16 -15
- package/agents/examples/app-sidebar.tsx +3 -1
- package/agents/examples/badge.tsx +20 -12
- package/agents/examples/bar-list-labels.tsx +6 -6
- package/agents/examples/bar-list-sides.tsx +18 -16
- package/agents/examples/bar-list.tsx +8 -8
- package/agents/examples/breadcrumb.tsx +3 -3
- package/agents/examples/calendar.tsx +3 -7
- package/agents/examples/callout.tsx +15 -15
- package/agents/examples/card.tsx +5 -3
- package/agents/examples/change-bar.tsx +21 -21
- package/agents/examples/chart-area.tsx +11 -11
- package/agents/examples/chart-bar-flows.tsx +1 -1
- package/agents/examples/chart-bar-hourly.tsx +1 -1
- package/agents/examples/chart-bar-losses.tsx +3 -3
- package/agents/examples/chart-bar-teams.tsx +72 -0
- package/agents/examples/chart-bar-years.tsx +5 -5
- package/agents/examples/chart-bar.tsx +10 -10
- package/agents/examples/chart-category-ticks.tsx +6 -6
- package/agents/examples/chart-line.tsx +17 -17
- package/agents/examples/collapsible.tsx +5 -3
- package/agents/examples/command.tsx +23 -21
- package/agents/examples/context-menu.tsx +6 -4
- package/agents/examples/data-bar.tsx +32 -45
- package/agents/examples/data-table-compact.tsx +31 -30
- package/agents/examples/data-table-filters.tsx +20 -20
- package/agents/examples/data-table.tsx +31 -30
- package/agents/examples/dialog.tsx +3 -3
- package/agents/examples/distribution-bar-tug.tsx +36 -34
- package/agents/examples/dropdown-menu.tsx +2 -2
- package/agents/examples/heatmap-calendar.tsx +12 -10
- package/agents/examples/heatmap-status.tsx +11 -9
- package/agents/examples/heatmap-weekday.tsx +27 -30
- package/agents/examples/input-states.tsx +9 -9
- package/agents/examples/input.tsx +3 -3
- package/agents/examples/local-time.tsx +1 -1
- package/agents/examples/metric-strip.tsx +8 -8
- package/agents/examples/page-hero.tsx +2 -2
- package/agents/examples/popover.tsx +3 -3
- package/agents/examples/price-chart.tsx +5 -5
- package/agents/examples/progress-tone.tsx +10 -9
- package/agents/examples/progress.tsx +1 -1
- package/agents/examples/range-position-table.tsx +31 -31
- package/agents/examples/range-position.tsx +31 -29
- package/agents/examples/scroll-area.tsx +12 -10
- package/agents/examples/section-cards-select.tsx +11 -10
- package/agents/examples/section-cards-trend.tsx +4 -4
- package/agents/examples/section-cards.tsx +13 -13
- package/agents/examples/select.tsx +9 -9
- package/agents/examples/separator.tsx +3 -3
- package/agents/examples/sheet.tsx +1 -1
- package/agents/examples/slider.tsx +1 -1
- package/agents/examples/surfaces.tsx +16 -16
- package/agents/examples/tabs.tsx +7 -7
- package/agents/examples/target-rows.tsx +31 -27
- package/agents/examples/tooltip.tsx +2 -2
- package/agents/examples/treemap.tsx +38 -33
- package/agents/manifest.json +15 -15
- package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
- package/dist/components/registry/charts/bar-chart-card.js +6 -2
- package/dist/components/registry/charts/line-chart-card.d.ts +1 -1
- package/dist/components/registry/charts/line-chart-card.js +1 -1
- package/dist/components/registry/dashboard-01/section-cards.js +1 -1
- package/dist/components/registry/dashboardcn/distribution-bar.js +5 -3
- package/dist/components/registry/heatmap/status-heatmap.js +1 -1
- package/dist/components/siyahi/callout.d.ts +5 -12
- package/dist/components/siyahi/callout.js +5 -11
- package/dist/components/siyahi/delta.d.ts +5 -1
- package/dist/components/siyahi/delta.js +14 -9
- package/dist/components/siyahi/status-alert.d.ts +15 -0
- package/dist/components/siyahi/status-alert.js +29 -0
- package/dist/components/siyahi/status-badge.d.ts +15 -0
- package/dist/components/siyahi/status-badge.js +24 -0
- package/dist/components/siyahi/tone-progress.d.ts +1 -1
- package/dist/components/siyahi/tone-progress.js +1 -1
- package/dist/components/siyahi/treemap.d.ts +4 -4
- package/dist/components/siyahi/treemap.js +5 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/lib/chart-frame.js +1 -1
- package/dist/lib/chart-series.d.ts +1 -1
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/tone.d.ts +36 -0
- package/dist/lib/tone.js +35 -0
- package/package.json +1 -1
- package/report.css +14 -14
- package/styles/siyahi.css +92 -20
- package/agents/examples/chart-bar-sectors.tsx +0 -72
- package/dist/components/siyahi/warning-alert.d.ts +0 -10
- 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
|
|
18
|
-
|
|
17
|
+
interface City extends TreemapItem {
|
|
18
|
+
channel: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ name: "
|
|
25
|
-
{ name: "
|
|
26
|
-
{ name: "
|
|
27
|
-
{ name: "
|
|
28
|
-
{ name: "
|
|
29
|
-
{ name: "
|
|
30
|
-
{ name: "
|
|
31
|
-
{ name: "
|
|
32
|
-
{ name: "
|
|
33
|
-
{ name: "
|
|
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<
|
|
38
|
-
const [selected, setSelected] = useState<
|
|
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 = (
|
|
41
|
-
`${money(
|
|
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>
|
|
46
|
-
<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={
|
|
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"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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.
|
|
82
|
+
{selected.name} · {selected.channel}
|
|
78
83
|
</span>{" "}
|
|
79
84
|
· {selected.group} · {figures(selected)}
|
|
80
85
|
</>
|
package/agents/manifest.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"needs": [
|
|
18
18
|
{
|
|
19
|
-
"need": "Values over time or another ordered axis (months, hours,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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-
|
|
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
|
|
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:
|
|
45
|
+
stroke: tone,
|
|
46
46
|
strokeWidth: 1.5
|
|
47
|
-
}, i) : /* @__PURE__ */ jsx(Cell, {
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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-
|
|
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?:
|
|
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
|
|
20
|
-
*
|
|
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
|
|
14
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
|
|
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" :
|
|
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
|
-
|
|
19
|
+
"data-direction": direction,
|
|
20
|
+
className: cn("inline-flex items-center gap-1 whitespace-nowrap", className),
|
|
13
21
|
children: [
|
|
14
|
-
|
|
22
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
15
23
|
"aria-hidden": "true",
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 ? "
|
|
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
|
|
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: ["
|
|
196
|
+
/* @__PURE__ */ jsxs("span", { children: ["down over ", over] }),
|
|
197
197
|
/* @__PURE__ */ jsxs("span", { children: [pct(step, digits), " a step"] }),
|
|
198
|
-
/* @__PURE__ */ jsxs("span", { children: ["
|
|
198
|
+
/* @__PURE__ */ jsxs("span", { children: ["up over ", over] })
|
|
199
199
|
]
|
|
200
200
|
}),
|
|
201
201
|
/* @__PURE__ */ jsxs("p", {
|