@siyahi/ui 0.1.9-next.3 → 0.1.9-next.31
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 +251 -0
- package/README.md +94 -177
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/agents/examples/accordion.tsx +39 -0
- package/agents/examples/alert.tsx +24 -0
- package/agents/examples/app-sidebar.tsx +40 -0
- package/agents/examples/avatar.tsx +24 -0
- package/agents/examples/badge.tsx +26 -0
- package/agents/examples/bar-list-labels.tsx +22 -0
- package/agents/examples/bar-list-sides.tsx +40 -0
- package/agents/examples/bar-list.tsx +24 -0
- package/agents/examples/breadcrumb.tsx +28 -0
- package/agents/examples/button-states.tsx +23 -0
- package/agents/examples/button.tsx +21 -0
- package/agents/examples/calendar.tsx +11 -0
- package/agents/examples/callout.tsx +33 -0
- package/agents/examples/card.tsx +36 -0
- package/agents/examples/change-bar.tsx +104 -0
- package/agents/examples/change-grid-money.tsx +36 -0
- package/agents/examples/change-grid.tsx +43 -0
- package/agents/examples/chart-area-log.tsx +45 -0
- package/agents/examples/chart-area.tsx +39 -0
- package/agents/examples/chart-bar-flows.tsx +32 -0
- package/agents/examples/chart-bar-hourly.tsx +28 -0
- package/agents/examples/chart-bar-losses.tsx +29 -0
- package/agents/examples/chart-bar-teams.tsx +72 -0
- package/agents/examples/chart-bar-years.tsx +42 -0
- package/agents/examples/chart-bar.tsx +56 -0
- package/agents/examples/chart-category-ticks.tsx +50 -0
- package/agents/examples/chart-line-events.tsx +47 -0
- package/agents/examples/chart-line-forecast.tsx +36 -0
- package/agents/examples/chart-line-log.tsx +39 -0
- package/agents/examples/chart-line-quiet.tsx +114 -0
- package/agents/examples/chart-line-timeline.tsx +41 -0
- package/agents/examples/chart-line.tsx +57 -0
- package/agents/examples/chart-panels.tsx +94 -0
- package/agents/examples/code-block.tsx +40 -0
- package/agents/examples/collapsible.tsx +18 -0
- package/agents/examples/command.tsx +29 -0
- package/agents/examples/context-menu.tsx +17 -0
- package/agents/examples/copy-button.tsx +8 -0
- package/agents/examples/data-bar.tsx +114 -0
- package/agents/examples/data-table-compact.tsx +93 -0
- package/agents/examples/data-table-filters.tsx +103 -0
- package/agents/examples/data-table.tsx +96 -0
- package/agents/examples/delta.tsx +18 -0
- package/agents/examples/dialog.tsx +28 -0
- package/agents/examples/distribution-bar-budget.tsx +30 -0
- package/agents/examples/distribution-bar-tug.tsx +74 -0
- package/agents/examples/drawdown-chart.tsx +35 -0
- package/agents/examples/dropdown-menu.tsx +31 -0
- package/agents/examples/field.tsx +108 -0
- package/agents/examples/formatters.tsx +42 -0
- package/agents/examples/heatmap-calendar-signed.tsx +67 -0
- package/agents/examples/heatmap-calendar.tsx +62 -0
- package/agents/examples/heatmap-status.tsx +61 -0
- package/agents/examples/heatmap-weekday.tsx +72 -0
- package/agents/examples/histogram-chart.tsx +28 -0
- package/agents/examples/input-states.tsx +23 -0
- package/agents/examples/input.tsx +17 -0
- package/agents/examples/local-time.tsx +14 -0
- package/agents/examples/metric-strip.tsx +46 -0
- package/agents/examples/overview-dashboard.tsx +180 -0
- package/agents/examples/page-hero.tsx +38 -0
- package/agents/examples/page-section.tsx +52 -0
- package/agents/examples/popover.tsx +23 -0
- package/agents/examples/price-chart.tsx +32 -0
- package/agents/examples/progress-tone.tsx +29 -0
- package/agents/examples/progress.tsx +10 -0
- package/agents/examples/range-position-table.tsx +113 -0
- package/agents/examples/range-position.tsx +55 -0
- package/agents/examples/scatter-chart-quadrants.tsx +63 -0
- package/agents/examples/scatter-chart-scales.tsx +46 -0
- package/agents/examples/scatter-chart.tsx +78 -0
- package/agents/examples/scroll-area.tsx +22 -0
- package/agents/examples/section-cards-select.tsx +28 -0
- package/agents/examples/section-cards-trend.tsx +32 -0
- package/agents/examples/section-cards.tsx +48 -0
- package/agents/examples/select.tsx +27 -0
- package/agents/examples/separator.tsx +13 -0
- package/agents/examples/sheet.tsx +15 -0
- package/agents/examples/skeleton.tsx +11 -0
- package/agents/examples/slider.tsx +5 -0
- package/agents/examples/slope-chart-market.tsx +33 -0
- package/agents/examples/slope-chart.tsx +26 -0
- package/agents/examples/stale-blocks.tsx +56 -0
- package/agents/examples/surfaces.tsx +116 -0
- package/agents/examples/table.tsx +60 -0
- package/agents/examples/tabs.tsx +16 -0
- package/agents/examples/target-rows.tsx +74 -0
- package/agents/examples/toggle-group.tsx +12 -0
- package/agents/examples/toggle.tsx +11 -0
- package/agents/examples/tooltip.tsx +13 -0
- package/agents/examples/treemap.tsx +93 -0
- package/agents/examples/waterfall-chart-stores.tsx +27 -0
- package/agents/examples/waterfall-chart.tsx +37 -0
- package/agents/manifest.json +1391 -0
- package/agents/report-rules.json +7 -0
- package/dist/components/registry/charts/area-chart-card.d.ts +7 -3
- package/dist/components/registry/charts/area-chart-card.js +56 -10
- package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
- package/dist/components/registry/charts/bar-chart-card.js +20 -22
- package/dist/components/registry/charts/line-chart-card.d.ts +3 -3
- package/dist/components/registry/charts/line-chart-card.js +44 -9
- package/dist/components/registry/dashboard-01/section-cards.d.ts +6 -0
- package/dist/components/registry/dashboard-01/section-cards.js +51 -35
- package/dist/components/registry/dashboardcn/bar-list.js +17 -10
- package/dist/components/registry/dashboardcn/distribution-bar.js +6 -4
- package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
- package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
- package/dist/components/registry/dashboardcn/sparkline.d.ts +22 -0
- package/dist/components/registry/dashboardcn/sparkline.js +117 -0
- package/dist/components/registry/heatmap/calendar-heatmap.d.ts +11 -3
- package/dist/components/registry/heatmap/calendar-heatmap.js +164 -45
- package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
- package/dist/components/registry/heatmap/status-heatmap.js +115 -54
- package/dist/components/registry/heatmap/weekday-heatmap.d.ts +9 -3
- package/dist/components/registry/heatmap/weekday-heatmap.js +83 -53
- package/dist/components/registry/niko-table/core/data-table-context.d.ts +18 -1
- package/dist/components/registry/niko-table/core/data-table-context.js +7 -1
- package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
- package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
- package/dist/components/registry/niko-table/core/data-table-structure.js +17 -12
- package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
- package/dist/components/registry/niko-table/core/data-table.js +5 -1
- package/dist/components/registry/niko-table/filters/table-faceted-filter.js +5 -5
- package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
- package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
- package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
- package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
- package/dist/components/registry/niko-table/lib/styles.js +3 -3
- package/dist/components/registry/niko-table/types/index.d.ts +9 -0
- package/dist/components/registry/niko-table/ui/table.js +2 -2
- package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
- package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
- package/dist/components/registry/sidebar-07/nav-main.d.ts +2 -1
- package/dist/components/registry/sidebar-07/nav-main.js +16 -3
- package/dist/components/siyahi/callout.d.ts +17 -0
- package/dist/components/siyahi/callout.js +34 -0
- package/dist/components/siyahi/change-bar.d.ts +21 -0
- package/dist/components/siyahi/change-bar.js +35 -0
- package/dist/components/siyahi/change-grid.d.ts +31 -0
- package/dist/components/siyahi/change-grid.js +74 -0
- package/dist/components/siyahi/data-bar.d.ts +19 -0
- package/dist/components/siyahi/data-bar.js +29 -0
- package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
- package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
- package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
- package/dist/components/siyahi/data-table-footnote.js +35 -0
- package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
- package/dist/components/siyahi/default-sidebar-open.js +13 -0
- package/dist/components/siyahi/delta.d.ts +5 -1
- package/dist/components/siyahi/delta.js +14 -9
- package/dist/components/siyahi/drawdown-chart.d.ts +34 -0
- package/dist/components/siyahi/drawdown-chart.js +102 -0
- package/dist/components/siyahi/histogram-chart.d.ts +57 -0
- package/dist/components/siyahi/histogram-chart.js +134 -0
- package/dist/components/siyahi/page-hero.d.ts +23 -0
- package/dist/components/siyahi/page-hero.js +43 -0
- package/dist/components/siyahi/page-section.d.ts +4 -1
- package/dist/components/siyahi/page-section.js +6 -3
- package/dist/components/siyahi/page-toc.d.ts +20 -0
- package/dist/components/siyahi/page-toc.js +76 -0
- package/dist/components/siyahi/panel-grid.d.ts +24 -0
- package/dist/components/siyahi/panel-grid.js +37 -0
- package/dist/components/siyahi/quote.d.ts +12 -0
- package/dist/components/siyahi/quote.js +23 -0
- package/dist/components/siyahi/range-position.d.ts +24 -0
- package/dist/components/siyahi/range-position.js +105 -0
- package/dist/components/siyahi/scale-toggle.d.ts +32 -0
- package/dist/components/siyahi/scale-toggle.js +61 -0
- package/dist/components/siyahi/scatter-chart.d.ts +69 -0
- package/dist/components/siyahi/scatter-chart.js +395 -0
- package/dist/components/siyahi/siyahi-provider.d.ts +15 -2
- package/dist/components/siyahi/siyahi-provider.js +17 -4
- package/dist/components/siyahi/slope-chart.d.ts +35 -0
- package/dist/components/siyahi/slope-chart.js +159 -0
- package/dist/components/siyahi/stale-note.d.ts +9 -0
- package/dist/components/siyahi/stale-note.js +12 -0
- 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/target-rows.d.ts +34 -0
- package/dist/components/siyahi/target-rows.js +97 -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 +8 -5
- package/dist/components/siyahi/treemap.js +80 -34
- package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
- package/dist/components/siyahi/waterfall-chart.js +147 -0
- package/dist/components/ui/accordion.js +5 -5
- package/dist/components/ui/alert.js +2 -2
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +2 -2
- package/dist/components/ui/button.js +10 -10
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/card.js +4 -4
- package/dist/components/ui/chart.js +1 -1
- package/dist/components/ui/code-block.js +13 -12
- package/dist/components/ui/command.js +7 -6
- package/dist/components/ui/context-menu.js +7 -7
- package/dist/components/ui/dialog.js +5 -5
- package/dist/components/ui/dropdown-menu.js +8 -8
- package/dist/components/ui/field.d.ts +26 -0
- package/dist/components/ui/field.js +109 -0
- package/dist/components/ui/input-group.js +11 -11
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.js +13 -0
- package/dist/components/ui/popover.js +3 -3
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/select.js +6 -6
- package/dist/components/ui/sheet.js +5 -5
- package/dist/components/ui/sidebar.js +17 -18
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +2 -2
- package/dist/components/ui/table.js +2 -2
- package/dist/components/ui/tabs.js +2 -2
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/toggle-group.js +2 -2
- package/dist/components/ui/toggle.js +4 -4
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/index.d.ts +35 -8
- package/dist/index.js +30 -4
- package/dist/lib/chart-frame.d.ts +51 -7
- package/dist/lib/chart-frame.js +390 -133
- package/dist/lib/chart-marks.d.ts +83 -0
- package/dist/lib/chart-marks.js +216 -0
- package/dist/lib/chart-series.d.ts +42 -10
- package/dist/lib/compact-columns.d.ts +19 -0
- package/dist/lib/compact-columns.js +55 -0
- package/dist/lib/fit-text.d.ts +6 -0
- package/dist/lib/fit-text.js +11 -0
- package/dist/lib/format.d.ts +8 -3
- package/dist/lib/format.js +55 -17
- package/dist/lib/heat.d.ts +7 -0
- package/dist/lib/heat.js +14 -0
- package/dist/lib/heatmap-navigation.d.ts +8 -0
- package/dist/lib/heatmap-navigation.js +49 -0
- package/dist/lib/scatter.d.ts +88 -0
- package/dist/lib/scatter.js +179 -0
- package/dist/lib/ticks.d.ts +27 -0
- package/dist/lib/ticks.js +67 -0
- package/dist/lib/tone.d.ts +36 -0
- package/dist/lib/tone.js +35 -0
- package/dist/lib/treemap.d.ts +0 -5
- package/dist/lib/treemap.js +1 -11
- package/dist/lib/use-element-width.d.ts +5 -0
- package/dist/lib/use-element-width.js +15 -0
- package/package.json +5 -1
- package/registry.lock.json +16 -0
- package/report.css +63 -47
- package/styles/siyahi.css +708 -55
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Delta } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
export default function DeltaExample() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex flex-col gap-4">
|
|
6
|
+
<div className="flex flex-wrap items-center gap-6">
|
|
7
|
+
<Delta value={0.0234} />
|
|
8
|
+
<Delta value={-0.0108} />
|
|
9
|
+
<Delta value={0} />
|
|
10
|
+
<Delta value={18250} format="money" />
|
|
11
|
+
<Delta value={-4300} format="money" />
|
|
12
|
+
</div>
|
|
13
|
+
<p>
|
|
14
|
+
Revenue was <Delta value={0.031} /> on last month, and refunds <Delta value={-1200} format="money" />.
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogClose,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogDescription,
|
|
7
|
+
DialogFooter,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DialogTrigger,
|
|
11
|
+
} from "@siyahi/ui";
|
|
12
|
+
|
|
13
|
+
export default function DialogExample() {
|
|
14
|
+
return (
|
|
15
|
+
<Dialog>
|
|
16
|
+
<DialogTrigger render={<Button variant="outline" className="w-fit" />}>Show parcels</DialogTrigger>
|
|
17
|
+
<DialogContent>
|
|
18
|
+
<DialogHeader>
|
|
19
|
+
<DialogTitle>Parcels for this order</DialogTitle>
|
|
20
|
+
<DialogDescription>Each parcel this order was split into, earliest first.</DialogDescription>
|
|
21
|
+
</DialogHeader>
|
|
22
|
+
<DialogFooter>
|
|
23
|
+
<DialogClose render={<Button variant="outline" />}>Close</DialogClose>
|
|
24
|
+
</DialogFooter>
|
|
25
|
+
</DialogContent>
|
|
26
|
+
</Dialog>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, DistributionBar } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
// Where a memory budget sits, on a fixed 0 to 12 GiB scale; made-up figures.
|
|
4
|
+
const gib = (value: number) => `${value.toFixed(1)} GiB`;
|
|
5
|
+
|
|
6
|
+
export default function DistributionBarBudgetExample() {
|
|
7
|
+
return (
|
|
8
|
+
<Card>
|
|
9
|
+
<CardHeader>
|
|
10
|
+
<CardTitle>The agents group's budget</CardTitle>
|
|
11
|
+
<CardDescription>0 to 12 GiB; the lines mark its limits</CardDescription>
|
|
12
|
+
</CardHeader>
|
|
13
|
+
<CardContent>
|
|
14
|
+
<DistributionBar
|
|
15
|
+
max={12}
|
|
16
|
+
valueFormatter={gib}
|
|
17
|
+
markers={[
|
|
18
|
+
{ at: 6, label: "High" },
|
|
19
|
+
{ at: 8.7, label: "Max" },
|
|
20
|
+
]}
|
|
21
|
+
segments={[
|
|
22
|
+
{ name: "In RAM, under High", value: 6, color: "var(--chart-1)" },
|
|
23
|
+
{ name: "In RAM, over High", value: 1.4, color: "var(--chart-2)" },
|
|
24
|
+
{ name: "Pushed to zram", value: 2.6, color: "var(--chart-3)" },
|
|
25
|
+
]}
|
|
26
|
+
/>
|
|
27
|
+
</CardContent>
|
|
28
|
+
</Card>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardFooter,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
Delta,
|
|
10
|
+
DistributionBar,
|
|
11
|
+
type DistributionSegment,
|
|
12
|
+
} from "@siyahi/ui";
|
|
13
|
+
|
|
14
|
+
// Each finished marketing campaign's net return, ₹: sales it brought in less what it cost; made-up figures.
|
|
15
|
+
const campaigns = [
|
|
16
|
+
{ name: "Festive sale", value: 61000 },
|
|
17
|
+
{ name: "Referrals", value: 38000 },
|
|
18
|
+
{ name: "Email win-back", value: 17000 },
|
|
19
|
+
{ name: "Billboards", value: -26000 },
|
|
20
|
+
{ name: "Podcast ads", value: -14000 },
|
|
21
|
+
{ name: "Trade fair", value: -6000 },
|
|
22
|
+
];
|
|
23
|
+
const won = campaigns.filter((c) => c.value > 0).reduce((sum, c) => sum + c.value, 0);
|
|
24
|
+
const lost = campaigns.filter((c) => c.value < 0).reduce((sum, c) => sum + c.value, 0);
|
|
25
|
+
// One scale both ways, so whichever side reaches farther is the net.
|
|
26
|
+
const reach = Math.max(won, -lost);
|
|
27
|
+
|
|
28
|
+
export default function DistributionBarTugExample() {
|
|
29
|
+
const [focus, setFocus] = useState<DistributionSegment | null>(null);
|
|
30
|
+
return (
|
|
31
|
+
<Card>
|
|
32
|
+
<CardHeader>
|
|
33
|
+
<CardTitle>Campaigns earned ₹70,000 more than they lost</CardTitle>
|
|
34
|
+
<CardDescription>Each finished campaign, losses left of break-even and gains right</CardDescription>
|
|
35
|
+
</CardHeader>
|
|
36
|
+
<CardContent>
|
|
37
|
+
<div className="flex flex-col gap-2">
|
|
38
|
+
<DistributionBar
|
|
39
|
+
min={-reach}
|
|
40
|
+
max={reach}
|
|
41
|
+
showLegend={false}
|
|
42
|
+
markers={[{ at: 0 }]}
|
|
43
|
+
highlighted={focus?.name ?? null}
|
|
44
|
+
onHover={setFocus}
|
|
45
|
+
segments={campaigns.map(({ name, value }) => ({
|
|
46
|
+
name,
|
|
47
|
+
value,
|
|
48
|
+
color: value > 0 ? "var(--positive)" : "var(--negative)",
|
|
49
|
+
}))}
|
|
50
|
+
/>
|
|
51
|
+
<div className="grid grid-cols-3 text-xs text-muted-foreground tabular-nums">
|
|
52
|
+
<span>
|
|
53
|
+
lost <Delta value={lost} format="money" />
|
|
54
|
+
</span>
|
|
55
|
+
<span className="text-center">break-even</span>
|
|
56
|
+
<span className="text-right">
|
|
57
|
+
earned <Delta value={won} format="money" />
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</CardContent>
|
|
62
|
+
<CardFooter className="min-h-12" aria-live="polite">
|
|
63
|
+
{focus ? (
|
|
64
|
+
<p className="flex items-center gap-2">
|
|
65
|
+
<span className="font-medium">{focus.name}</span>
|
|
66
|
+
<Delta value={focus.value} format="money" />
|
|
67
|
+
</p>
|
|
68
|
+
) : (
|
|
69
|
+
<p className="text-muted-foreground">Hover a band to see its campaign.</p>
|
|
70
|
+
)}
|
|
71
|
+
</CardFooter>
|
|
72
|
+
</Card>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DrawdownChartCard, useFormat } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
// A bakery's cash balance at the end of each week of 2026: it climbs, falls through the summer when an oven is
|
|
4
|
+
// replaced, and falls again in the quiet weeks of the new year; made-up figures.
|
|
5
|
+
const weeks = Array.from({ length: 52 }, (_, i) => {
|
|
6
|
+
const date = new Date(Date.UTC(2026, 0, 4 + i * 7));
|
|
7
|
+
const trend = 600_000 + i * 9_000;
|
|
8
|
+
const oven = i >= 22 && i < 34 ? -180_000 + (i - 22) * 12_000 : 0;
|
|
9
|
+
const quiet = i >= 46 ? -(i - 45) * 22_000 : 0;
|
|
10
|
+
const wobble = ((i * 53) % 17) * 3_000 - 24_000;
|
|
11
|
+
return { week: date.toISOString().slice(0, 10), balance: trend + oven + quiet + wobble };
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const month = (iso: string) => new Date(iso).toLocaleString("en-IN", { month: "short", timeZone: "UTC" });
|
|
15
|
+
const day = (iso: string) =>
|
|
16
|
+
new Date(iso).toLocaleString("en-IN", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });
|
|
17
|
+
|
|
18
|
+
export default function DrawdownChartExample() {
|
|
19
|
+
const { money } = useFormat();
|
|
20
|
+
return (
|
|
21
|
+
<DrawdownChartCard
|
|
22
|
+
title="Cash below its best"
|
|
23
|
+
description="Each week's balance against the highest it had been so far. At zero the bakery was at a new high."
|
|
24
|
+
data={weeks}
|
|
25
|
+
xKey="week"
|
|
26
|
+
levelKey="balance"
|
|
27
|
+
levelFormatter={money}
|
|
28
|
+
label="Below its best"
|
|
29
|
+
xFormatter={month}
|
|
30
|
+
xTooltipFormatter={(iso) => `Week ending ${day(iso)}`}
|
|
31
|
+
xTicks={weeks.filter((_, i) => i % 9 === 0).map(({ week }) => week)}
|
|
32
|
+
height={260}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
DropdownMenu,
|
|
4
|
+
DropdownMenuContent,
|
|
5
|
+
DropdownMenuGroup,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "@siyahi/ui";
|
|
11
|
+
import { EllipsisIcon } from "lucide-react";
|
|
12
|
+
|
|
13
|
+
export default function DropdownMenuExample() {
|
|
14
|
+
return (
|
|
15
|
+
<DropdownMenu>
|
|
16
|
+
<DropdownMenuTrigger render={<Button variant="outline" className="w-fit" />}>
|
|
17
|
+
<EllipsisIcon />
|
|
18
|
+
Actions
|
|
19
|
+
</DropdownMenuTrigger>
|
|
20
|
+
<DropdownMenuContent>
|
|
21
|
+
<DropdownMenuGroup>
|
|
22
|
+
<DropdownMenuLabel>Order</DropdownMenuLabel>
|
|
23
|
+
<DropdownMenuItem>View parcels</DropdownMenuItem>
|
|
24
|
+
<DropdownMenuItem>Export CSV</DropdownMenuItem>
|
|
25
|
+
</DropdownMenuGroup>
|
|
26
|
+
<DropdownMenuSeparator />
|
|
27
|
+
<DropdownMenuItem variant="destructive">Hide</DropdownMenuItem>
|
|
28
|
+
</DropdownMenuContent>
|
|
29
|
+
</DropdownMenu>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
Field,
|
|
9
|
+
FieldDescription,
|
|
10
|
+
FieldError,
|
|
11
|
+
FieldLabel,
|
|
12
|
+
Input,
|
|
13
|
+
InputGroup,
|
|
14
|
+
InputGroupAddon,
|
|
15
|
+
InputGroupInput,
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectGroup,
|
|
19
|
+
SelectItem,
|
|
20
|
+
SelectTrigger,
|
|
21
|
+
SelectValue,
|
|
22
|
+
Textarea,
|
|
23
|
+
ToggleGroup,
|
|
24
|
+
ToggleGroupItem,
|
|
25
|
+
} from "@siyahi/ui";
|
|
26
|
+
import { IndianRupeeIcon } from "lucide-react";
|
|
27
|
+
|
|
28
|
+
const kinds = [
|
|
29
|
+
{ value: "equity", label: "Equity" },
|
|
30
|
+
{ value: "debt", label: "Debt" },
|
|
31
|
+
{ value: "gold", label: "Gold" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Each field is a Field: its label above (small mono capitals, from the theme), the control, and a hint or an error.
|
|
35
|
+
export default function FieldExample() {
|
|
36
|
+
return (
|
|
37
|
+
<Card>
|
|
38
|
+
<CardHeader>
|
|
39
|
+
<CardTitle>Add a holding</CardTitle>
|
|
40
|
+
<CardDescription>What you bought, how much, and how often to review it</CardDescription>
|
|
41
|
+
</CardHeader>
|
|
42
|
+
<CardContent>
|
|
43
|
+
<form className="grid max-w-3xl gap-x-8 gap-y-5 sm:grid-cols-2" onSubmit={(event) => event.preventDefault()}>
|
|
44
|
+
<Field>
|
|
45
|
+
<FieldLabel htmlFor="holding-name">Name</FieldLabel>
|
|
46
|
+
<Input id="holding-name" defaultValue="Nifty Next 50 index fund" />
|
|
47
|
+
<FieldDescription>As it appears on the statement</FieldDescription>
|
|
48
|
+
</Field>
|
|
49
|
+
<Field>
|
|
50
|
+
<FieldLabel htmlFor="holding-kind">Kind</FieldLabel>
|
|
51
|
+
<Select items={kinds} defaultValue="equity">
|
|
52
|
+
<SelectTrigger id="holding-kind" className="w-full">
|
|
53
|
+
<SelectValue />
|
|
54
|
+
</SelectTrigger>
|
|
55
|
+
<SelectContent>
|
|
56
|
+
<SelectGroup>
|
|
57
|
+
{kinds.map((kind) => (
|
|
58
|
+
<SelectItem key={kind.value} value={kind.value}>
|
|
59
|
+
{kind.label}
|
|
60
|
+
</SelectItem>
|
|
61
|
+
))}
|
|
62
|
+
</SelectGroup>
|
|
63
|
+
</SelectContent>
|
|
64
|
+
</Select>
|
|
65
|
+
</Field>
|
|
66
|
+
<Field>
|
|
67
|
+
<FieldLabel htmlFor="holding-amount">Amount invested</FieldLabel>
|
|
68
|
+
<InputGroup>
|
|
69
|
+
<InputGroupAddon>
|
|
70
|
+
<IndianRupeeIcon />
|
|
71
|
+
</InputGroupAddon>
|
|
72
|
+
<InputGroupInput id="holding-amount" defaultValue="2,50,000" inputMode="numeric" />
|
|
73
|
+
</InputGroup>
|
|
74
|
+
<FieldDescription>In rupees, before charges</FieldDescription>
|
|
75
|
+
</Field>
|
|
76
|
+
<Field data-invalid>
|
|
77
|
+
<FieldLabel htmlFor="holding-folio">Folio number</FieldLabel>
|
|
78
|
+
<Input id="holding-folio" defaultValue="9103-44" aria-invalid aria-describedby="holding-folio-error" />
|
|
79
|
+
<FieldError id="holding-folio-error">A folio number is 8 to 12 digits, such as 91034455</FieldError>
|
|
80
|
+
</Field>
|
|
81
|
+
<Field>
|
|
82
|
+
<FieldLabel id="holding-review">Review</FieldLabel>
|
|
83
|
+
<ToggleGroup defaultValue={["quarterly"]} variant="outline" aria-labelledby="holding-review">
|
|
84
|
+
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
|
85
|
+
<ToggleGroupItem value="quarterly">Quarterly</ToggleGroupItem>
|
|
86
|
+
<ToggleGroupItem value="yearly">Yearly</ToggleGroupItem>
|
|
87
|
+
</ToggleGroup>
|
|
88
|
+
</Field>
|
|
89
|
+
<Field data-disabled>
|
|
90
|
+
<FieldLabel htmlFor="holding-account">Account</FieldLabel>
|
|
91
|
+
<Input id="holding-account" defaultValue="Demat, joint" disabled />
|
|
92
|
+
<FieldDescription>Set by the statement you imported</FieldDescription>
|
|
93
|
+
</Field>
|
|
94
|
+
<Field className="sm:col-span-2">
|
|
95
|
+
<FieldLabel htmlFor="holding-note">Note</FieldLabel>
|
|
96
|
+
<Textarea id="holding-note" placeholder="Why you bought it, and what would make you sell" />
|
|
97
|
+
</Field>
|
|
98
|
+
<div className="flex gap-2 sm:col-span-2">
|
|
99
|
+
<Button type="submit">Save holding</Button>
|
|
100
|
+
<Button type="button" variant="outline">
|
|
101
|
+
Cancel
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
</form>
|
|
105
|
+
</CardContent>
|
|
106
|
+
</Card>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SiyahiProvider, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, useFormat } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
function Row({ label }: { label: string }) {
|
|
4
|
+
const { count, money, moneyCompact, pct, time } = useFormat();
|
|
5
|
+
return (
|
|
6
|
+
<TableRow>
|
|
7
|
+
<TableCell>{label}</TableCell>
|
|
8
|
+
<TableCell data-numeric>{count(153110)}</TableCell>
|
|
9
|
+
<TableCell data-numeric>{money(153110)}</TableCell>
|
|
10
|
+
<TableCell data-numeric>{moneyCompact(99999)}</TableCell>
|
|
11
|
+
<TableCell data-numeric>{moneyCompact(240000)}</TableCell>
|
|
12
|
+
<TableCell data-numeric>{moneyCompact(400000000)}</TableCell>
|
|
13
|
+
<TableCell data-numeric>{pct(0.1234)}</TableCell>
|
|
14
|
+
<TableCell data-numeric>{time("2026-10-20T09:00:00Z")}</TableCell>
|
|
15
|
+
</TableRow>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function FormattersExample() {
|
|
20
|
+
return (
|
|
21
|
+
<Table>
|
|
22
|
+
<TableHeader>
|
|
23
|
+
<TableRow>
|
|
24
|
+
<TableHead>Locale</TableHead>
|
|
25
|
+
<TableHead data-numeric>count</TableHead>
|
|
26
|
+
<TableHead data-numeric>money</TableHead>
|
|
27
|
+
<TableHead data-numeric>moneyCompact</TableHead>
|
|
28
|
+
<TableHead data-numeric>moneyCompact</TableHead>
|
|
29
|
+
<TableHead data-numeric>moneyCompact</TableHead>
|
|
30
|
+
<TableHead data-numeric>pct</TableHead>
|
|
31
|
+
<TableHead data-numeric>time</TableHead>
|
|
32
|
+
</TableRow>
|
|
33
|
+
</TableHeader>
|
|
34
|
+
<TableBody>
|
|
35
|
+
<Row label="Default (en-IN, INR, Kolkata)" />
|
|
36
|
+
<SiyahiProvider locale={{ numberLocale: "en-US", currency: "USD", timeZone: "America/New_York" }}>
|
|
37
|
+
<Row label="en-US, USD, New York" />
|
|
38
|
+
</SiyahiProvider>
|
|
39
|
+
</TableBody>
|
|
40
|
+
</Table>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
CalendarHeatmap,
|
|
4
|
+
CalendarHeatmapBlock,
|
|
5
|
+
CalendarHeatmapBody,
|
|
6
|
+
CalendarHeatmapFooter,
|
|
7
|
+
CalendarHeatmapLegend,
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
useFormat,
|
|
14
|
+
} from "@siyahi/ui";
|
|
15
|
+
|
|
16
|
+
// A café's profit or loss each day for six months: weekends earn most, rainy Mondays lose; made-up figures.
|
|
17
|
+
const data = Array.from({ length: 182 }, (_, i) => {
|
|
18
|
+
const day = new Date(Date.UTC(2026, 4, 1 + i));
|
|
19
|
+
const weekday = day.getUTCDay();
|
|
20
|
+
const base = weekday === 0 || weekday === 6 ? 9000 : weekday === 1 ? -2500 : 3000;
|
|
21
|
+
const swing = ((i * 37) % 23) * 400 - 4400;
|
|
22
|
+
return { date: day.toISOString().slice(0, 10), value: base + swing };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default function HeatmapCalendarSignedExample() {
|
|
26
|
+
const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
|
|
27
|
+
const { money } = useFormat();
|
|
28
|
+
const signed = (value: number) => `${value > 0 ? "+" : value < 0 ? "−" : ""}${money(Math.abs(value))}`;
|
|
29
|
+
return (
|
|
30
|
+
<Card>
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle>Profit by day</CardTitle>
|
|
33
|
+
<CardDescription>
|
|
34
|
+
May to October. Square green days made money and round red days lost it; deeper is more.
|
|
35
|
+
</CardDescription>
|
|
36
|
+
</CardHeader>
|
|
37
|
+
<CardContent>
|
|
38
|
+
<CalendarHeatmap
|
|
39
|
+
data={data}
|
|
40
|
+
signed
|
|
41
|
+
weekStart={1}
|
|
42
|
+
labels={{ heatmapLabel: "Profit by day, May to October", cellLabel: "{{date}}: {{value}} rupees" }}
|
|
43
|
+
>
|
|
44
|
+
<CalendarHeatmapBody>
|
|
45
|
+
{({ activity, dayIndex, weekIndex }) => (
|
|
46
|
+
<CalendarHeatmapBlock
|
|
47
|
+
activity={activity}
|
|
48
|
+
dayIndex={dayIndex}
|
|
49
|
+
weekIndex={weekIndex}
|
|
50
|
+
selected={activity.date === picked?.date}
|
|
51
|
+
onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
</CalendarHeatmapBody>
|
|
55
|
+
<CalendarHeatmapFooter className="whitespace-normal">
|
|
56
|
+
<div className="flex w-full flex-wrap items-center justify-between gap-x-6 gap-y-2">
|
|
57
|
+
<p aria-live="polite" className="text-sm tabular-nums">
|
|
58
|
+
{picked ? `${picked.date}: ${signed(picked.value)}` : "Select a day for its profit or loss."}
|
|
59
|
+
</p>
|
|
60
|
+
<CalendarHeatmapLegend labels={{ less: "Loss", more: "Profit" }} valueFormatter={money} />
|
|
61
|
+
</div>
|
|
62
|
+
</CalendarHeatmapFooter>
|
|
63
|
+
</CalendarHeatmap>
|
|
64
|
+
</CardContent>
|
|
65
|
+
</Card>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
CalendarHeatmap,
|
|
4
|
+
CalendarHeatmapBlock,
|
|
5
|
+
CalendarHeatmapBody,
|
|
6
|
+
CalendarHeatmapFooter,
|
|
7
|
+
CalendarHeatmapLegend,
|
|
8
|
+
useFormat,
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardDescription,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "@siyahi/ui";
|
|
15
|
+
|
|
16
|
+
// A year of working days with a count of production deploys on each.
|
|
17
|
+
const data = Array.from({ length: 365 }, (_, i) => {
|
|
18
|
+
const day = new Date(Date.UTC(2025, 9, 20 + i));
|
|
19
|
+
const weekday = day.getUTCDay();
|
|
20
|
+
const value = weekday === 0 || weekday === 6 ? 0 : (i * 7 + weekday * 3) % 6;
|
|
21
|
+
return { date: day.toISOString().slice(0, 10), value };
|
|
22
|
+
});
|
|
23
|
+
const busiest = Math.max(...data.map((day) => day.value));
|
|
24
|
+
|
|
25
|
+
export default function HeatmapCalendarExample() {
|
|
26
|
+
// The page owns the pick: a second tap on the same day clears it.
|
|
27
|
+
const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
|
|
28
|
+
const { count } = useFormat();
|
|
29
|
+
return (
|
|
30
|
+
<Card>
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle>Deploys per day</CardTitle>
|
|
33
|
+
<CardDescription>The last 12 months; the stronger the colour, the more deploys</CardDescription>
|
|
34
|
+
</CardHeader>
|
|
35
|
+
<CardContent>
|
|
36
|
+
<CalendarHeatmap data={data} labels={{ heatmapLabel: "Deploys per day over the last 12 months" }}>
|
|
37
|
+
<CalendarHeatmapBody>
|
|
38
|
+
{({ activity, dayIndex, weekIndex }) => (
|
|
39
|
+
<CalendarHeatmapBlock
|
|
40
|
+
activity={activity}
|
|
41
|
+
dayIndex={dayIndex}
|
|
42
|
+
weekIndex={weekIndex}
|
|
43
|
+
selected={activity.date === picked?.date}
|
|
44
|
+
onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
</CalendarHeatmapBody>
|
|
48
|
+
<CalendarHeatmapFooter className="whitespace-normal">
|
|
49
|
+
<div className="flex w-full flex-wrap items-center justify-between gap-x-6 gap-y-2">
|
|
50
|
+
<p aria-live="polite" className="text-sm">
|
|
51
|
+
{picked
|
|
52
|
+
? `${picked.date}: ${count(picked.value)} deploys`
|
|
53
|
+
: "Select a day for its deploy count. Use arrow keys to move between days."}
|
|
54
|
+
</p>
|
|
55
|
+
<CalendarHeatmapLegend labels={{ less: "0 deploys", more: `${count(busiest)} deploys` }} />
|
|
56
|
+
</div>
|
|
57
|
+
</CalendarHeatmapFooter>
|
|
58
|
+
</CalendarHeatmap>
|
|
59
|
+
</CardContent>
|
|
60
|
+
</Card>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
StatusHeatmap,
|
|
9
|
+
StatusHeatmapBlock,
|
|
10
|
+
StatusHeatmapBody,
|
|
11
|
+
StatusHeatmapFooter,
|
|
12
|
+
StatusHeatmapLegend,
|
|
13
|
+
} from "@siyahi/ui";
|
|
14
|
+
|
|
15
|
+
// One cell per day for the last 60 days, as a status the heatmap colours:
|
|
16
|
+
// 3 healthy (quiet grey), 2 degraded (a half bar in --warning), 1 critical (a full bar in --destructive),
|
|
17
|
+
// 0 no data (a hollow outline).
|
|
18
|
+
const data = Array.from({ length: 60 }, (_, i) => {
|
|
19
|
+
const day = new Date(Date.UTC(2026, 7, 22 + i));
|
|
20
|
+
// Two days in September the monitor itself was down, so there is no reading: 0.
|
|
21
|
+
const value = i === 30 || i === 31 ? 0 : i % 17 === 5 ? 1 : i % 11 === 3 ? 2 : 3;
|
|
22
|
+
return { date: day.toISOString().slice(0, 10), value };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default function HeatmapStatusExample() {
|
|
26
|
+
// The page owns the pick: a second tap on the same day clears it.
|
|
27
|
+
const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Card>
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle>Checkout API</CardTitle>
|
|
33
|
+
<CardDescription>Daily health: healthy, degraded or critical</CardDescription>
|
|
34
|
+
</CardHeader>
|
|
35
|
+
<CardContent>
|
|
36
|
+
<StatusHeatmap data={data} statusValues={[3, 2, 1, 0]}>
|
|
37
|
+
<StatusHeatmapBody>
|
|
38
|
+
{({ activity, dayIndex }) => (
|
|
39
|
+
<StatusHeatmapBlock
|
|
40
|
+
activity={activity}
|
|
41
|
+
dayIndex={dayIndex}
|
|
42
|
+
selected={activity.date === picked?.date}
|
|
43
|
+
onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
</StatusHeatmapBody>
|
|
47
|
+
<StatusHeatmapFooter className="whitespace-normal">
|
|
48
|
+
<div className="flex w-full flex-wrap items-center justify-between gap-x-6 gap-y-2">
|
|
49
|
+
<p aria-live="polite" className="text-sm">
|
|
50
|
+
{picked
|
|
51
|
+
? `${picked.date}: ${["unknown", "critical", "degraded", "healthy"][picked.value] ?? "unknown"}`
|
|
52
|
+
: "Select a day for its health. Use arrow keys to move between days."}
|
|
53
|
+
</p>
|
|
54
|
+
<StatusHeatmapLegend />
|
|
55
|
+
</div>
|
|
56
|
+
</StatusHeatmapFooter>
|
|
57
|
+
</StatusHeatmap>
|
|
58
|
+
</CardContent>
|
|
59
|
+
</Card>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useFormat,
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
WeekdayHeatmap,
|
|
10
|
+
WeekdayHeatmapBlock,
|
|
11
|
+
WeekdayHeatmapBody,
|
|
12
|
+
WeekdayHeatmapLegend,
|
|
13
|
+
} from "@siyahi/ui";
|
|
14
|
+
|
|
15
|
+
// Support tickets opened by weekday and office hour; the overnight queue lands at 9.
|
|
16
|
+
const data = [1, 2, 3, 4, 5].flatMap((weekday) =>
|
|
17
|
+
[9, 10, 11, 12, 13, 14, 15].map((hour) => ({
|
|
18
|
+
weekday,
|
|
19
|
+
hour,
|
|
20
|
+
value: ((weekday * 5 + hour * 3) % 9) + (hour === 9 ? 6 : 0),
|
|
21
|
+
})),
|
|
22
|
+
);
|
|
23
|
+
const busiest = data.reduce((top, cell) => (cell.value > top.value ? cell : top));
|
|
24
|
+
const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
25
|
+
const hourLabel = (hour: number) => `${String(hour).padStart(2, "0")}:00`;
|
|
26
|
+
|
|
27
|
+
export default function HeatmapWeekdayExample() {
|
|
28
|
+
// The page owns the pick: a second tap on the same cell clears it.
|
|
29
|
+
const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
|
|
30
|
+
const isPicked = (cell: { weekday: number; hour: number }) =>
|
|
31
|
+
cell.weekday === picked?.weekday && cell.hour === picked.hour;
|
|
32
|
+
const { count } = useFormat();
|
|
33
|
+
return (
|
|
34
|
+
<Card>
|
|
35
|
+
<CardHeader>
|
|
36
|
+
<CardTitle>When support tickets arrive</CardTitle>
|
|
37
|
+
<CardDescription>Weekday by hour in India Standard Time, office hours only</CardDescription>
|
|
38
|
+
</CardHeader>
|
|
39
|
+
{/* Seven hours by five days is a small grid: capped, it keeps square cells, and the legend and the answer
|
|
40
|
+
sit beside it instead of a wide empty card. */}
|
|
41
|
+
<CardContent>
|
|
42
|
+
<WeekdayHeatmap data={data} hours={[9, 10, 11, 12, 13, 14, 15]} weekdays={[1, 2, 3, 4, 5]}>
|
|
43
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:gap-8">
|
|
44
|
+
<WeekdayHeatmapBody className="lg:w-md lg:shrink-0">
|
|
45
|
+
{({ activity }) => (
|
|
46
|
+
<WeekdayHeatmapBlock
|
|
47
|
+
activity={activity}
|
|
48
|
+
selected={isPicked(activity)}
|
|
49
|
+
onCellClick={(cell) => setPicked(isPicked(cell) ? null : cell)}
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
</WeekdayHeatmapBody>
|
|
53
|
+
<div className="flex flex-col items-start gap-3 text-sm">
|
|
54
|
+
<WeekdayHeatmapLegend
|
|
55
|
+
className="ml-0"
|
|
56
|
+
labels={{ less: "0 tickets", more: `${count(busiest.value)} tickets an hour` }}
|
|
57
|
+
/>
|
|
58
|
+
<p>
|
|
59
|
+
Busiest: {DAYS[busiest.weekday]} at {hourLabel(busiest.hour)}, {count(busiest.value)} tickets
|
|
60
|
+
</p>
|
|
61
|
+
<p aria-live="polite" className="text-muted-foreground">
|
|
62
|
+
{picked
|
|
63
|
+
? `${DAYS[picked.weekday]}, ${hourLabel(picked.hour)} IST: ${count(picked.value)} tickets`
|
|
64
|
+
: "Select a cell for its ticket count. Use arrow keys to move between cells."}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</WeekdayHeatmap>
|
|
69
|
+
</CardContent>
|
|
70
|
+
</Card>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HistogramChartCard, useFormat } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
// 2,000 checkout requests' response times in milliseconds: most are quick, a long tail is slow. Made-up figures from a
|
|
4
|
+
// fixed sequence, so the page draws the same every time.
|
|
5
|
+
let seed = 7;
|
|
6
|
+
const next = () => {
|
|
7
|
+
seed = (seed * 16807) % 2147483647;
|
|
8
|
+
return seed / 2147483647;
|
|
9
|
+
};
|
|
10
|
+
const times = Array.from({ length: 2000 }, () => Math.round(80 + 900 * next() ** 3 + 120 * next()));
|
|
11
|
+
|
|
12
|
+
export default function HistogramChartExample() {
|
|
13
|
+
const { count } = useFormat();
|
|
14
|
+
return (
|
|
15
|
+
<HistogramChartCard
|
|
16
|
+
title="Checkout response times"
|
|
17
|
+
description="The last 2,000 requests, counted in 100 ms buckets. Lines mark the median and the 95th percentile (95 in 100 requests were faster)."
|
|
18
|
+
values={times}
|
|
19
|
+
valueFormatter={(ms) => `${count(ms)} ms`}
|
|
20
|
+
tickFormatter={count}
|
|
21
|
+
countLabel="Requests"
|
|
22
|
+
marks={["median", "p95"]}
|
|
23
|
+
xLabel="Response time (ms)"
|
|
24
|
+
yLabel="Requests"
|
|
25
|
+
height={280}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|