@siyahi/ui 0.1.9-next.20 → 0.1.9-next.21
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 +30 -0
- package/README.md +3 -3
- package/agents/examples/alert.tsx +4 -6
- package/agents/examples/bar-list-sides.tsx +11 -3
- package/agents/examples/button-states.tsx +23 -0
- package/agents/examples/callout.tsx +33 -0
- package/agents/examples/change-bar.tsx +104 -0
- package/agents/examples/chart-area.tsx +5 -4
- package/agents/examples/chart-bar-flows.tsx +3 -2
- package/agents/examples/chart-bar-sectors.tsx +2 -0
- package/agents/examples/data-table-compact.tsx +95 -0
- package/agents/examples/heatmap-calendar.tsx +17 -7
- package/agents/examples/heatmap-status.tsx +17 -11
- package/agents/examples/heatmap-weekday.tsx +33 -11
- package/agents/examples/input-states.tsx +33 -0
- package/agents/examples/metric-strip.tsx +9 -3
- package/agents/examples/overview-dashboard.tsx +18 -0
- package/agents/examples/page-hero.tsx +38 -0
- package/agents/examples/range-position-table.tsx +101 -0
- package/agents/examples/range-position.tsx +53 -0
- package/agents/examples/section-cards-select.tsx +8 -5
- package/agents/examples/surfaces.tsx +3 -2
- package/agents/examples/target-rows.tsx +70 -0
- package/agents/examples/treemap.tsx +2 -1
- package/agents/manifest.json +153 -3
- package/dist/components/registry/charts/area-chart-card.js +25 -7
- package/dist/components/registry/charts/bar-chart-card.d.ts +0 -1
- package/dist/components/registry/charts/bar-chart-card.js +11 -4
- package/dist/components/registry/charts/line-chart-card.js +2 -2
- package/dist/components/registry/dashboardcn/bar-list.js +11 -5
- package/dist/components/registry/dashboardcn/kpi-row-card.js +2 -2
- package/dist/components/registry/dashboardcn/sparkline.d.ts +5 -1
- package/dist/components/registry/dashboardcn/sparkline.js +40 -18
- package/dist/components/registry/heatmap/calendar-heatmap.d.ts +4 -2
- package/dist/components/registry/heatmap/calendar-heatmap.js +67 -33
- package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
- package/dist/components/registry/heatmap/status-heatmap.js +30 -15
- package/dist/components/registry/heatmap/weekday-heatmap.d.ts +4 -2
- package/dist/components/registry/heatmap/weekday-heatmap.js +28 -12
- package/dist/components/registry/niko-table/core/data-table-context.d.ts +16 -1
- package/dist/components/registry/niko-table/core/data-table-context.js +5 -20
- package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
- package/dist/components/registry/niko-table/core/data-table-structure.js +2 -0
- package/dist/components/registry/niko-table/lib/styles.js +1 -1
- package/dist/components/registry/niko-table/types/index.d.ts +9 -0
- package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -0
- package/dist/components/registry/sidebar-07/nav-main.js +9 -2
- package/dist/components/siyahi/callout.d.ts +24 -0
- package/dist/components/siyahi/callout.js +40 -0
- package/dist/components/siyahi/change-bar.d.ts +21 -0
- package/dist/components/siyahi/change-bar.js +35 -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/page-hero.d.ts +25 -0
- package/dist/components/siyahi/page-hero.js +41 -0
- package/dist/components/siyahi/page-toc.d.ts +20 -0
- package/dist/components/siyahi/page-toc.js +76 -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/siyahi-provider.d.ts +1 -1
- package/dist/components/siyahi/siyahi-provider.js +1 -1
- package/dist/components/siyahi/target-rows.d.ts +34 -0
- package/dist/components/siyahi/target-rows.js +97 -0
- package/dist/components/siyahi/treemap.d.ts +4 -1
- package/dist/components/siyahi/treemap.js +33 -25
- package/dist/components/siyahi/warning-alert.d.ts +10 -0
- package/dist/components/siyahi/warning-alert.js +20 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +12 -1
- package/dist/lib/chart-frame.d.ts +12 -2
- package/dist/lib/chart-frame.js +51 -7
- package/dist/lib/chart-series.d.ts +5 -2
- package/dist/lib/compact-columns.d.ts +19 -0
- package/dist/lib/compact-columns.js +55 -0
- package/dist/lib/format.d.ts +7 -2
- package/dist/lib/format.js +24 -5
- package/dist/lib/treemap.js +6 -1
- package/dist/lib/use-element-width.d.ts +5 -0
- package/dist/lib/use-element-width.js +15 -0
- package/package.json +2 -1
- package/report.css +26 -25
- package/styles/siyahi.css +159 -43
package/DESIGN.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Siyahi design guidelines
|
|
2
|
+
|
|
3
|
+
These rules bind every page built on `@siyahi/ui`: The Book, Dash and any other
|
|
4
|
+
app. Agents must follow them when designing or changing UI; a reviewer treats a
|
|
5
|
+
breach as a defect. The catalogue (https://siyahi.jatinbansal.com) shows each
|
|
6
|
+
component; this file says how to use them.
|
|
7
|
+
|
|
8
|
+
## Colour
|
|
9
|
+
|
|
10
|
+
Colour is placed, not spread. A page is neutral greys plus colour that carries
|
|
11
|
+
a meaning:
|
|
12
|
+
|
|
13
|
+
- **Ink indigo** is the accent: links, focus rings, the selected sidebar item,
|
|
14
|
+
the first chart series and an `accent` `Callout`. Buttons, badges and toggles
|
|
15
|
+
stay monochrome.
|
|
16
|
+
- **Green and red** mean a gain and a loss, always with ▲ or ▼ (`Delta`).
|
|
17
|
+
**Amber** means caution. None of them decorates.
|
|
18
|
+
- **Emphasis tints the whole surface.** A caution is `WarningAlert`; a point set
|
|
19
|
+
apart is `Callout` (`neutral`, `accent`, `warning`, `positive`,
|
|
20
|
+
`negative`); a quotation is `Quote`. Each takes a faint wash of its colour
|
|
21
|
+
inside a 1px border of the same colour all round. Large marks follow the same
|
|
22
|
+
rule: gain and loss bars are a tint inside a full-colour edge, not solid
|
|
23
|
+
slabs.
|
|
24
|
+
- **Never a stripe down one side.** Do not mark a notice, card, row or quote
|
|
25
|
+
with a left (or any single-side) coloured border; tint the box or add an
|
|
26
|
+
icon. 1px neutral dividers between columns are separators, not accents, and
|
|
27
|
+
are fine.
|
|
28
|
+
- Categories (a broker, a sector, an asset class) stay neutral: outline badges,
|
|
29
|
+
never a hue each. Ordered buckets (largest 5, next 5, rest) use steps of one
|
|
30
|
+
colour, not separate hues.
|
package/README.md
CHANGED
|
@@ -100,10 +100,10 @@ and support one keyboard entry with arrow navigation. `WeekdayHeatmap` accepts
|
|
|
100
100
|
|
|
101
101
|
`SiyahiProvider` sets how data blocks sit on the page:
|
|
102
102
|
|
|
103
|
-
- `surface="
|
|
104
|
-
|
|
103
|
+
- `surface="card"` (default, even outside a provider): each block is a card
|
|
104
|
+
lifted off a faintly tinted canvas.
|
|
105
|
+
- `surface="ruled"`: unboxed blocks; `PanelGrid` draws hairlines between them.
|
|
105
106
|
- `surface="open"`: no boxes or rules; card padding is removed.
|
|
106
|
-
- `surface="card"`: shadcn's filled Nova cards with a ring.
|
|
107
107
|
|
|
108
108
|
Nest a provider to change one section. Stack `PageSection` blocks with
|
|
109
109
|
`flex flex-col gap-6`; use `PanelGrid` for blocks side by side. Categories use
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Alert, AlertDescription, AlertTitle } from "@siyahi/ui";
|
|
2
|
-
import { CircleAlertIcon, InfoIcon
|
|
1
|
+
import { Alert, AlertDescription, AlertTitle, WarningAlert } from "@siyahi/ui";
|
|
2
|
+
import { CircleAlertIcon, InfoIcon } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
export default function AlertExample() {
|
|
5
5
|
return (
|
|
@@ -9,12 +9,10 @@ export default function AlertExample() {
|
|
|
9
9
|
<AlertTitle>Prices are delayed</AlertTitle>
|
|
10
10
|
<AlertDescription>Quotes refresh every fifteen minutes during market hours.</AlertDescription>
|
|
11
11
|
</Alert>
|
|
12
|
-
<
|
|
13
|
-
{/* Alert sets its icons to the text colour with a stronger selector; the ! lets the icon's colour win. */}
|
|
14
|
-
<TriangleAlertIcon className="text-warning!" />
|
|
12
|
+
<WarningAlert>
|
|
15
13
|
<AlertTitle>Two trades are unmatched</AlertTitle>
|
|
16
14
|
<AlertDescription>The contract notes list trades the ledger does not have yet.</AlertDescription>
|
|
17
|
-
</
|
|
15
|
+
</WarningAlert>
|
|
18
16
|
<Alert variant="destructive">
|
|
19
17
|
<CircleAlertIcon />
|
|
20
18
|
<AlertTitle>Import failed</AlertTitle>
|
|
@@ -21,9 +21,17 @@ export default function BarListSidesExample() {
|
|
|
21
21
|
<CardTitle>What drives the P&L</CardTitle>
|
|
22
22
|
<CardDescription>The biggest ₹ gains and losses, on one scale</CardDescription>
|
|
23
23
|
</CardHeader>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
{/* Each list is a column of its own, headed and ruled off, with its figures at its own right edge, so a
|
|
25
|
+
gain's figure never reads as the label of the loss beside it. */}
|
|
26
|
+
<CardContent className="grid gap-6 md:grid-cols-2 md:gap-0 md:divide-x">
|
|
27
|
+
<div className="md:pr-6">
|
|
28
|
+
<h3 className="text-muted-foreground mb-2 text-xs font-medium">Gains</h3>
|
|
29
|
+
<BarList data={gains} max={max} color="var(--positive)" valueFormatter={(v) => money(v)} />
|
|
30
|
+
</div>
|
|
31
|
+
<div className="md:pl-6">
|
|
32
|
+
<h3 className="text-muted-foreground mb-2 text-xs font-medium">Losses</h3>
|
|
33
|
+
<BarList data={losses} max={max} color="var(--negative)" valueFormatter={(v) => money(v)} />
|
|
34
|
+
</div>
|
|
27
35
|
</CardContent>
|
|
28
36
|
</Card>
|
|
29
37
|
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Button, Toggle } from "@siyahi/ui";
|
|
2
|
+
import { DownloadIcon, Loader2Icon, PinIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function ButtonStatesExample() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
7
|
+
<Button disabled>Save</Button>
|
|
8
|
+
<Button variant="outline" disabled>
|
|
9
|
+
<DownloadIcon />
|
|
10
|
+
Export
|
|
11
|
+
</Button>
|
|
12
|
+
{/* Busy: the label stays and the icon spins, so the button keeps its width while the work runs. */}
|
|
13
|
+
<Button disabled aria-busy>
|
|
14
|
+
<Loader2Icon className="motion-safe:animate-spin" />
|
|
15
|
+
Saving
|
|
16
|
+
</Button>
|
|
17
|
+
<Toggle variant="outline" defaultPressed aria-label="Pin to top">
|
|
18
|
+
<PinIcon />
|
|
19
|
+
Pinned
|
|
20
|
+
</Toggle>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Callout, Quote, WarningAlert, AlertDescription, AlertTitle } from "@siyahi/ui";
|
|
2
|
+
import { LightbulbIcon, InfoIcon, TriangleAlertIcon, TrendingUpIcon, TrendingDownIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function CalloutExample() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="grid max-w-2xl gap-3">
|
|
7
|
+
<Callout tone="accent" icon={<LightbulbIcon />} title="The takeaway">
|
|
8
|
+
Two of eleven gold rules made a profit in the final test; <strong>none</strong> held up in both metals.
|
|
9
|
+
</Callout>
|
|
10
|
+
<Callout icon={<InfoIcon />} title="How the test was run">
|
|
11
|
+
One mini lot per trade, after charges and a harsher assumed execution cost.
|
|
12
|
+
</Callout>
|
|
13
|
+
<Callout tone="warning" icon={<TriangleAlertIcon />} title="Do not use the combined result">
|
|
14
|
+
Its test used later information, so its profit claims are withdrawn.
|
|
15
|
+
</Callout>
|
|
16
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
17
|
+
<Callout tone="positive" icon={<TrendingUpIcon />} title="Best rule">
|
|
18
|
+
A failed range break reverses: <strong>▲₹10,616</strong>.
|
|
19
|
+
</Callout>
|
|
20
|
+
<Callout tone="negative" icon={<TrendingDownIcon />} title="Worst rule">
|
|
21
|
+
Break the first-hour range: <strong>▼₹70,679</strong>.
|
|
22
|
+
</Callout>
|
|
23
|
+
</div>
|
|
24
|
+
<Quote source="eMudhra's incident notice, 21 Oct 2026">
|
|
25
|
+
The affected intermediate keys were generated with insufficient entropy and have been revoked.
|
|
26
|
+
</Quote>
|
|
27
|
+
<WarningAlert>
|
|
28
|
+
<AlertTitle>Ledger history is incomplete</AlertTitle>
|
|
29
|
+
<AlertDescription>Dhan's cash ledger starts in April 2024; earlier flows are missing.</AlertDescription>
|
|
30
|
+
</WarningAlert>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardDescription,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
ChangeBar,
|
|
8
|
+
DataTable,
|
|
9
|
+
DataTableBody,
|
|
10
|
+
DataTableCompactColumnsToggle,
|
|
11
|
+
DataTableFootnote,
|
|
12
|
+
DataTableHeader,
|
|
13
|
+
DataTableRoot,
|
|
14
|
+
DataTableSortHeader,
|
|
15
|
+
DataTableToolbarSection,
|
|
16
|
+
useFormat,
|
|
17
|
+
type DataTableColumnDef,
|
|
18
|
+
} from "@siyahi/ui";
|
|
19
|
+
|
|
20
|
+
interface Holding {
|
|
21
|
+
symbol: string;
|
|
22
|
+
/** Value at the close of 31 January and of 28 February, in rupees; made-up figures. */
|
|
23
|
+
january: number;
|
|
24
|
+
february: number;
|
|
25
|
+
change: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const holding = (symbol: string, january: number, february: number): Holding => ({
|
|
29
|
+
symbol,
|
|
30
|
+
january,
|
|
31
|
+
february,
|
|
32
|
+
change: february / january - 1,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const holdings: Holding[] = [
|
|
36
|
+
holding("ICICIBANK", 231900, 258000),
|
|
37
|
+
holding("HDFCBANK", 248300, 262500),
|
|
38
|
+
holding("TCS", 161300, 166000),
|
|
39
|
+
holding("NIFTYBEES", 250400, 255600),
|
|
40
|
+
holding("ITC", 283100, 276000),
|
|
41
|
+
holding("INFY", 241800, 224400),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Every bar is a share of the column's largest move, and the column holds gains and losses, so the bars diverge.
|
|
45
|
+
const largestChange = Math.max(...holdings.map((h) => Math.abs(h.change)));
|
|
46
|
+
const diverging = holdings.some((h) => h.change < 0) && holdings.some((h) => h.change > 0);
|
|
47
|
+
|
|
48
|
+
function Money({ amount, muted = false }: { amount: number; muted?: boolean }) {
|
|
49
|
+
const text = useFormat().money(amount);
|
|
50
|
+
return muted ? <span className="text-muted-foreground">{text}</span> : text;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const header = () => <DataTableSortHeader />;
|
|
54
|
+
|
|
55
|
+
const columns: DataTableColumnDef<Holding>[] = [
|
|
56
|
+
{ accessorKey: "symbol", header, size: 130, meta: { label: "Holding", compact: true } },
|
|
57
|
+
{
|
|
58
|
+
accessorKey: "january",
|
|
59
|
+
header,
|
|
60
|
+
size: 130,
|
|
61
|
+
meta: { label: "31 Jan", variant: "number" },
|
|
62
|
+
cell: ({ row }) => <Money amount={row.original.january} muted />,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
accessorKey: "february",
|
|
66
|
+
header,
|
|
67
|
+
size: 130,
|
|
68
|
+
meta: { label: "28 Feb", variant: "number", compact: true },
|
|
69
|
+
cell: ({ row }) => <Money amount={row.original.february} />,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
accessorKey: "change",
|
|
73
|
+
header,
|
|
74
|
+
size: 150,
|
|
75
|
+
meta: { label: "Change", variant: "number", compact: true },
|
|
76
|
+
cell: ({ row }) => <ChangeBar value={row.original.change} max={largestChange} diverging={diverging} />,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export default function ChangeBarExample() {
|
|
81
|
+
return (
|
|
82
|
+
<Card>
|
|
83
|
+
<CardHeader>
|
|
84
|
+
<CardTitle>ICICIBANK rose 11.3% in February, INFY fell 7.2%</CardTitle>
|
|
85
|
+
<CardDescription>Each holding's value at the end of February against the end of January</CardDescription>
|
|
86
|
+
</CardHeader>
|
|
87
|
+
<CardContent>
|
|
88
|
+
<DataTableRoot data={holdings} columns={columns}>
|
|
89
|
+
<DataTableToolbarSection>
|
|
90
|
+
<DataTableCompactColumnsToggle />
|
|
91
|
+
</DataTableToolbarSection>
|
|
92
|
+
<DataTable aria-label="February against January">
|
|
93
|
+
<DataTableHeader />
|
|
94
|
+
<DataTableBody />
|
|
95
|
+
<DataTableFootnote>
|
|
96
|
+
Each percent is the change on January's value, and the bar is its size against the largest move. Gains run
|
|
97
|
+
right of the centre line, losses left.
|
|
98
|
+
</DataTableFootnote>
|
|
99
|
+
</DataTable>
|
|
100
|
+
</DataTableRoot>
|
|
101
|
+
</CardContent>
|
|
102
|
+
</Card>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -9,18 +9,19 @@ const data = [
|
|
|
9
9
|
{ month: "Oct", value: 4512300, invested: 4025000 },
|
|
10
10
|
];
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// The title states the finding, not the measure: what the reader would say after looking.
|
|
13
|
+
// The first series fills; the money put in draws as a line over it.
|
|
13
14
|
const series: ChartSeries<(typeof data)[number]> = [
|
|
14
|
-
{ key: "invested", label: "Invested", color: "var(--chart-2)" },
|
|
15
15
|
{ key: "value", label: "Value", color: "var(--chart-1)" },
|
|
16
|
+
{ key: "invested", label: "Invested", color: "var(--chart-2)" },
|
|
16
17
|
];
|
|
17
18
|
|
|
18
19
|
export default function ChartAreaExample() {
|
|
19
20
|
const { money, moneyCompact } = useFormat();
|
|
20
21
|
return (
|
|
21
22
|
<AreaChartCard
|
|
22
|
-
title="
|
|
23
|
-
description="
|
|
23
|
+
title="₹4.9L ahead of the money put in"
|
|
24
|
+
description="Portfolio value against money invested, at each month end"
|
|
24
25
|
action={
|
|
25
26
|
<ToggleGroup defaultValue={["6M"]} variant="outline" size="sm" aria-label="Range">
|
|
26
27
|
<ToggleGroupItem value="6M">6M</ToggleGroupItem>
|
|
@@ -8,9 +8,10 @@ const data = [
|
|
|
8
8
|
{ month: "Oct", in: 70000, out: -66000 },
|
|
9
9
|
];
|
|
10
10
|
|
|
11
|
+
// Money moving is not a gain or a loss, so in and out take chart colours, not green and red.
|
|
11
12
|
const series: ChartSeries<(typeof data)[number]> = [
|
|
12
|
-
{ key: "in", label: "In", color: "var(--
|
|
13
|
-
{ key: "out", label: "Out", color: "var(--
|
|
13
|
+
{ key: "in", label: "In", color: "var(--chart-1)" },
|
|
14
|
+
{ key: "out", label: "Out", color: "var(--chart-2)" },
|
|
14
15
|
];
|
|
15
16
|
|
|
16
17
|
export default function ChartBarFlowsExample() {
|
|
@@ -60,6 +60,8 @@ export default function ChartBarSectorsExample() {
|
|
|
60
60
|
xTickDetail={(row) => `${pct(row.you)} vs ${pct(row.index)} · ${gap(row)}`}
|
|
61
61
|
series={series}
|
|
62
62
|
valueFormatter={(v) => pct(v)}
|
|
63
|
+
// A tick format puts the value axis along the bottom of the sideways bars.
|
|
64
|
+
yTickFormatter={(v) => pct(v, 0)}
|
|
63
65
|
// Sideways, y is still the value: the axis runs 0 to 40% and the cap is a value level.
|
|
64
66
|
yDomain={[0, 0.4]}
|
|
65
67
|
referenceLines={[{ y: 0.3, label: "cap 30%", labelPosition: "insideBottomRight" }]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardDescription,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
DataTable,
|
|
8
|
+
DataTableBody,
|
|
9
|
+
DataTableCompactColumnsToggle,
|
|
10
|
+
DataTableHeader,
|
|
11
|
+
DataTableRoot,
|
|
12
|
+
DataTableRowCount,
|
|
13
|
+
DataTableSortHeader,
|
|
14
|
+
DataTableToolbarSection,
|
|
15
|
+
Delta,
|
|
16
|
+
useDataTable,
|
|
17
|
+
useFormat,
|
|
18
|
+
type DataTableColumnDef,
|
|
19
|
+
} from "@siyahi/ui";
|
|
20
|
+
|
|
21
|
+
interface Holding {
|
|
22
|
+
symbol: string;
|
|
23
|
+
sector: string;
|
|
24
|
+
quantity: number;
|
|
25
|
+
value: number;
|
|
26
|
+
change: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const holdings: Holding[] = [
|
|
30
|
+
{ symbol: "INFY", sector: "IT", quantity: 120, value: 224400, change: 0.018 },
|
|
31
|
+
{ symbol: "HDFCBANK", sector: "Banks", quantity: 150, value: 262500, change: -0.006 },
|
|
32
|
+
{ symbol: "TCS", sector: "IT", quantity: 40, value: 166000, change: 0.004 },
|
|
33
|
+
{ symbol: "ITC", sector: "Fast Moving Consumer Goods", quantity: 600, value: 276000, change: -0.012 },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const header = () => <DataTableSortHeader />;
|
|
37
|
+
|
|
38
|
+
// While the Day column is hidden to fit, the day's move rides under the value it belongs to.
|
|
39
|
+
function ValueCell({ holding }: { holding: Holding }) {
|
|
40
|
+
const format = useFormat();
|
|
41
|
+
const { compactColumns } = useDataTable<Holding>();
|
|
42
|
+
if (!compactColumns?.hidden.has("change")) return format.money(holding.value);
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex flex-col items-end">
|
|
45
|
+
{format.money(holding.value)}
|
|
46
|
+
<Delta value={holding.change} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const columns: DataTableColumnDef<Holding>[] = [
|
|
52
|
+
{ accessorKey: "symbol", header, size: 140, meta: { label: "Symbol", compact: true } },
|
|
53
|
+
{ accessorKey: "sector", header, size: 120, meta: { label: "Sector" } },
|
|
54
|
+
{ accessorKey: "quantity", header, size: 100, meta: { label: "Qty", variant: "number" } },
|
|
55
|
+
{
|
|
56
|
+
accessorKey: "value",
|
|
57
|
+
header,
|
|
58
|
+
size: 140,
|
|
59
|
+
meta: { label: "Value", variant: "number", compact: true },
|
|
60
|
+
cell: ({ row }) => <ValueCell holding={row.original} />,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
accessorKey: "change",
|
|
64
|
+
header,
|
|
65
|
+
size: 110,
|
|
66
|
+
meta: { label: "Day", variant: "number" },
|
|
67
|
+
cell: ({ row }) => <Delta value={row.original.change} />,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
export default function DataTableCompactExample() {
|
|
72
|
+
return (
|
|
73
|
+
<Card>
|
|
74
|
+
<CardHeader>
|
|
75
|
+
<CardTitle>Holdings on a phone</CardTitle>
|
|
76
|
+
<CardDescription>
|
|
77
|
+
Symbol and value are the key columns. When the columns do not fit, the others hide from the right, and the
|
|
78
|
+
day's move rides under the value once its column is gone; All columns brings back the rest
|
|
79
|
+
</CardDescription>
|
|
80
|
+
</CardHeader>
|
|
81
|
+
<CardContent>
|
|
82
|
+
<DataTableRoot data={holdings} columns={columns}>
|
|
83
|
+
<DataTableToolbarSection>
|
|
84
|
+
<DataTableCompactColumnsToggle />
|
|
85
|
+
<DataTableRowCount unit="holdings" />
|
|
86
|
+
</DataTableToolbarSection>
|
|
87
|
+
<DataTable aria-label="Holdings on a phone">
|
|
88
|
+
<DataTableHeader />
|
|
89
|
+
<DataTableBody />
|
|
90
|
+
</DataTable>
|
|
91
|
+
</DataTableRoot>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
CalendarHeatmap,
|
|
4
4
|
CalendarHeatmapBlock,
|
|
5
5
|
CalendarHeatmapBody,
|
|
6
|
+
CalendarHeatmapFooter,
|
|
7
|
+
CalendarHeatmapLegend,
|
|
6
8
|
useFormat,
|
|
7
9
|
Card,
|
|
8
10
|
CardContent,
|
|
@@ -18,32 +20,40 @@ const data = Array.from({ length: 365 }, (_, i) => {
|
|
|
18
20
|
const value = weekday === 0 || weekday === 6 ? 0 : (i * 7 + weekday * 3) % 6;
|
|
19
21
|
return { date: day.toISOString().slice(0, 10), value };
|
|
20
22
|
});
|
|
23
|
+
const busiest = Math.max(...data.map((day) => day.value));
|
|
21
24
|
|
|
22
25
|
export default function HeatmapCalendarExample() {
|
|
23
|
-
|
|
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);
|
|
24
28
|
const { count } = useFormat();
|
|
25
29
|
return (
|
|
26
30
|
<Card>
|
|
27
31
|
<CardHeader>
|
|
28
32
|
<CardTitle>Trades per day</CardTitle>
|
|
29
|
-
<CardDescription>
|
|
33
|
+
<CardDescription>The last 12 months; counts use a single-hue fade of --chart-1</CardDescription>
|
|
30
34
|
</CardHeader>
|
|
31
35
|
<CardContent>
|
|
32
|
-
<CalendarHeatmap data={data}>
|
|
36
|
+
<CalendarHeatmap data={data} labels={{ heatmapLabel: "Trades per day over the last 12 months" }}>
|
|
33
37
|
<CalendarHeatmapBody>
|
|
34
38
|
{({ activity, dayIndex, weekIndex }) => (
|
|
35
39
|
<CalendarHeatmapBlock
|
|
36
40
|
activity={activity}
|
|
37
41
|
dayIndex={dayIndex}
|
|
38
42
|
weekIndex={weekIndex}
|
|
39
|
-
|
|
43
|
+
selected={activity.date === picked?.date}
|
|
44
|
+
onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
|
|
40
45
|
/>
|
|
41
46
|
)}
|
|
42
47
|
</CalendarHeatmapBody>
|
|
48
|
+
<CalendarHeatmapFooter className="items-center justify-between gap-x-6 gap-y-2 whitespace-normal">
|
|
49
|
+
<p aria-live="polite" className="text-sm">
|
|
50
|
+
{picked
|
|
51
|
+
? `${picked.date}: ${count(picked.value)} trades`
|
|
52
|
+
: "Select a day for its trade count. Use arrow keys to move between days."}
|
|
53
|
+
</p>
|
|
54
|
+
<CalendarHeatmapLegend labels={{ less: "0 trades", more: `${count(busiest)} trades` }} />
|
|
55
|
+
</CalendarHeatmapFooter>
|
|
43
56
|
</CalendarHeatmap>
|
|
44
|
-
<p aria-live="polite" className="mt-3 text-sm">
|
|
45
|
-
{selected ?? "Select a day for its trade count. Use arrow keys to move between days."}
|
|
46
|
-
</p>
|
|
47
57
|
</CardContent>
|
|
48
58
|
</Card>
|
|
49
59
|
);
|
|
@@ -8,17 +8,20 @@ import {
|
|
|
8
8
|
StatusHeatmap,
|
|
9
9
|
StatusHeatmapBlock,
|
|
10
10
|
StatusHeatmapBody,
|
|
11
|
+
StatusHeatmapFooter,
|
|
12
|
+
StatusHeatmapLegend,
|
|
11
13
|
} from "@siyahi/ui";
|
|
12
14
|
|
|
13
15
|
// One cell per day for the last 60 days, as a status the heatmap colours:
|
|
14
|
-
// 3 healthy (
|
|
16
|
+
// 3 healthy (quiet grey), 2 degraded (--warning), 1 critical (--destructive).
|
|
15
17
|
const data = Array.from({ length: 60 }, (_, i) => {
|
|
16
18
|
const day = new Date(Date.UTC(2026, 7, 22 + i));
|
|
17
19
|
return { date: day.toISOString().slice(0, 10), value: i % 17 === 5 ? 1 : i % 11 === 3 ? 2 : 3 };
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
export default function HeatmapStatusExample() {
|
|
21
|
-
|
|
23
|
+
// The page owns the pick: a second tap on the same day clears it.
|
|
24
|
+
const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
|
|
22
25
|
|
|
23
26
|
return (
|
|
24
27
|
<Card>
|
|
@@ -27,24 +30,27 @@ export default function HeatmapStatusExample() {
|
|
|
27
30
|
<CardDescription>Daily health: healthy, degraded or critical</CardDescription>
|
|
28
31
|
</CardHeader>
|
|
29
32
|
<CardContent>
|
|
30
|
-
<StatusHeatmap data={data}>
|
|
33
|
+
<StatusHeatmap data={data} statusValues={[3, 2, 1]}>
|
|
31
34
|
<StatusHeatmapBody>
|
|
32
35
|
{({ activity, dayIndex }) => (
|
|
33
36
|
<StatusHeatmapBlock
|
|
34
37
|
activity={activity}
|
|
35
38
|
dayIndex={dayIndex}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`${cell.date}: ${["unknown", "critical", "degraded", "healthy"][cell.value] ?? "unknown"}`,
|
|
39
|
-
)
|
|
40
|
-
}
|
|
39
|
+
selected={activity.date === picked?.date}
|
|
40
|
+
onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
|
|
41
41
|
/>
|
|
42
42
|
)}
|
|
43
43
|
</StatusHeatmapBody>
|
|
44
|
+
<StatusHeatmapFooter className="items-center justify-between gap-x-6 gap-y-2 whitespace-normal">
|
|
45
|
+
<p aria-live="polite" className="text-sm">
|
|
46
|
+
{picked
|
|
47
|
+
? `${picked.date}: ${["unknown", "critical", "degraded", "healthy"][picked.value] ?? "unknown"}`
|
|
48
|
+
: "Select a day for its health. Use arrow keys to move between days."}
|
|
49
|
+
</p>
|
|
50
|
+
{/* No day lacks data, so the legend leaves out "No Data". */}
|
|
51
|
+
<StatusHeatmapLegend className="gap-3" />
|
|
52
|
+
</StatusHeatmapFooter>
|
|
44
53
|
</StatusHeatmap>
|
|
45
|
-
<p aria-live="polite" className="mt-3 text-sm">
|
|
46
|
-
{selected ?? "Select a day for its health. Use arrow keys to move between days."}
|
|
47
|
-
</p>
|
|
48
54
|
</CardContent>
|
|
49
55
|
</Card>
|
|
50
56
|
);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
WeekdayHeatmap,
|
|
10
10
|
WeekdayHeatmapBlock,
|
|
11
11
|
WeekdayHeatmapBody,
|
|
12
|
+
WeekdayHeatmapLegend,
|
|
12
13
|
} from "@siyahi/ui";
|
|
13
14
|
|
|
14
15
|
// Orders placed by weekday and hour of the trading session.
|
|
@@ -19,9 +20,15 @@ const data = [1, 2, 3, 4, 5].flatMap((weekday) =>
|
|
|
19
20
|
value: ((weekday * 5 + hour * 3) % 9) + (hour === 9 ? 6 : 0),
|
|
20
21
|
})),
|
|
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`;
|
|
22
26
|
|
|
23
27
|
export default function HeatmapWeekdayExample() {
|
|
24
|
-
|
|
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;
|
|
25
32
|
const { count } = useFormat();
|
|
26
33
|
return (
|
|
27
34
|
<Card>
|
|
@@ -29,24 +36,39 @@ export default function HeatmapWeekdayExample() {
|
|
|
29
36
|
<CardTitle>When orders are placed</CardTitle>
|
|
30
37
|
<CardDescription>Weekday by hour in India Standard Time, market hours only</CardDescription>
|
|
31
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. */}
|
|
32
41
|
<CardContent>
|
|
33
|
-
<WeekdayHeatmap
|
|
34
|
-
|
|
42
|
+
<WeekdayHeatmap
|
|
43
|
+
data={data}
|
|
44
|
+
hours={[9, 10, 11, 12, 13, 14, 15]}
|
|
45
|
+
weekdays={[1, 2, 3, 4, 5]}
|
|
46
|
+
className="gap-4 lg:flex-row lg:items-center lg:gap-8"
|
|
47
|
+
>
|
|
48
|
+
<WeekdayHeatmapBody className="lg:w-md lg:shrink-0">
|
|
35
49
|
{({ activity }) => (
|
|
36
50
|
<WeekdayHeatmapBlock
|
|
37
51
|
activity={activity}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][cell.weekday]}, ${String(cell.hour).padStart(2, "0")}:00 IST: ${count(cell.value)} orders`,
|
|
41
|
-
)
|
|
42
|
-
}
|
|
52
|
+
selected={isPicked(activity)}
|
|
53
|
+
onCellClick={(cell) => setPicked(isPicked(cell) ? null : cell)}
|
|
43
54
|
/>
|
|
44
55
|
)}
|
|
45
56
|
</WeekdayHeatmapBody>
|
|
57
|
+
<div className="flex flex-col items-start gap-3 text-sm">
|
|
58
|
+
<WeekdayHeatmapLegend
|
|
59
|
+
className="ml-0"
|
|
60
|
+
labels={{ less: "0 orders", more: `${count(busiest.value)} orders an hour` }}
|
|
61
|
+
/>
|
|
62
|
+
<p>
|
|
63
|
+
Busiest: {DAYS[busiest.weekday]} at {hourLabel(busiest.hour)}, {count(busiest.value)} orders
|
|
64
|
+
</p>
|
|
65
|
+
<p aria-live="polite" className="text-muted-foreground">
|
|
66
|
+
{picked
|
|
67
|
+
? `${DAYS[picked.weekday]}, ${hourLabel(picked.hour)} IST: ${count(picked.value)} orders`
|
|
68
|
+
: "Select a cell for its order count. Use arrow keys to move between cells."}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
46
71
|
</WeekdayHeatmap>
|
|
47
|
-
<p aria-live="polite" className="mt-3 text-sm">
|
|
48
|
-
{selected ?? "Select a cell for its order count. Use arrow keys to move between cells."}
|
|
49
|
-
</p>
|
|
50
72
|
</CardContent>
|
|
51
73
|
</Card>
|
|
52
74
|
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Input } from "@siyahi/ui";
|
|
2
|
+
|
|
3
|
+
// A field is its label, the input and one line under it: a hint, or the error that replaces it.
|
|
4
|
+
export default function InputStatesExample() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="grid max-w-sm gap-5">
|
|
7
|
+
<div className="grid gap-1.5">
|
|
8
|
+
<label htmlFor="budget" className="text-sm font-medium">
|
|
9
|
+
Monthly budget
|
|
10
|
+
</label>
|
|
11
|
+
<Input id="budget" inputMode="numeric" defaultValue="25,000" aria-describedby="budget-hint" />
|
|
12
|
+
<p id="budget-hint" className="text-sm text-muted-foreground">
|
|
13
|
+
In rupees, before brokerage
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="grid gap-1.5">
|
|
17
|
+
<label htmlFor="folio" className="text-sm font-medium">
|
|
18
|
+
Folio number
|
|
19
|
+
</label>
|
|
20
|
+
<Input id="folio" defaultValue="12/34" aria-invalid aria-describedby="folio-error" />
|
|
21
|
+
<p id="folio-error" className="text-sm text-destructive">
|
|
22
|
+
A folio is digits only, such as 1234567
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="grid gap-1.5">
|
|
26
|
+
<label htmlFor="broker" className="text-sm font-medium text-muted-foreground">
|
|
27
|
+
Broker
|
|
28
|
+
</label>
|
|
29
|
+
<Input id="broker" defaultValue="Kite" disabled />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -19,13 +19,19 @@ export default function MetricStripExample() {
|
|
|
19
19
|
trend: [3600000, 3685000, 3770000, 3855000, 3940000, 4025000],
|
|
20
20
|
},
|
|
21
21
|
{ label: "XIRR", value: pct(0.142), change: -0.004, trend: [0.151, 0.149, 0.152, 0.144, 0.146, 0.142] },
|
|
22
|
-
{
|
|
22
|
+
{
|
|
23
|
+
label: "Realised",
|
|
24
|
+
value: moneyCompact(-61200),
|
|
25
|
+
change: -61200,
|
|
26
|
+
changeFormat: "money",
|
|
27
|
+
trend: [14200, -3800, 9100, 2600, -18400, -61200],
|
|
28
|
+
},
|
|
23
29
|
{ label: "Dividends", value: moneyCompact(38400), change: 0.18, trend: [4100, 0, 12800, 3300, 9900, 8300] },
|
|
24
|
-
{ label: "Holdings", value: count(42), note: "3 bought in Oct" },
|
|
30
|
+
{ label: "Holdings", value: count(42), note: "3 bought in Oct", trend: [38, 39, 39, 40, 39, 42] },
|
|
25
31
|
];
|
|
26
32
|
return (
|
|
27
33
|
<MetricStrip
|
|
28
|
-
title="October"
|
|
34
|
+
title="Portfolio up 3.1% in October"
|
|
29
35
|
description="Month-end figures with the six-month run behind each"
|
|
30
36
|
action={
|
|
31
37
|
<ToggleGroup defaultValue={["1M"]} variant="outline" size="sm" aria-label="Period">
|