@siyahi/ui 0.1.9-next.29 → 0.1.9-next.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/DESIGN.md +79 -10
  2. package/agents/examples/avatar.tsx +15 -5
  3. package/agents/examples/chart-line-forecast.tsx +36 -0
  4. package/agents/examples/chart-line-quiet.tsx +114 -0
  5. package/agents/examples/chart-panels.tsx +94 -0
  6. package/agents/examples/code-block.tsx +5 -3
  7. package/agents/examples/field.tsx +108 -0
  8. package/agents/examples/formatters.tsx +14 -14
  9. package/agents/examples/input-states.tsx +12 -22
  10. package/agents/examples/overview-dashboard.tsx +6 -6
  11. package/agents/examples/stale-blocks.tsx +56 -0
  12. package/agents/examples/table.tsx +6 -6
  13. package/agents/manifest.json +104 -3
  14. package/dist/components/registry/charts/area-chart-card.d.ts +7 -3
  15. package/dist/components/registry/charts/area-chart-card.js +52 -25
  16. package/dist/components/registry/charts/bar-chart-card.d.ts +1 -0
  17. package/dist/components/registry/charts/bar-chart-card.js +8 -24
  18. package/dist/components/registry/charts/line-chart-card.d.ts +2 -2
  19. package/dist/components/registry/charts/line-chart-card.js +42 -7
  20. package/dist/components/registry/dashboard-01/section-cards.d.ts +2 -0
  21. package/dist/components/registry/dashboard-01/section-cards.js +3 -0
  22. package/dist/components/registry/heatmap/calendar-heatmap.js +2 -2
  23. package/dist/components/registry/heatmap/status-heatmap.js +2 -2
  24. package/dist/components/registry/heatmap/weekday-heatmap.js +2 -2
  25. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +1 -1
  26. package/dist/components/siyahi/histogram-chart.js +8 -8
  27. package/dist/components/siyahi/scatter-chart.js +1 -1
  28. package/dist/components/siyahi/slope-chart.js +1 -1
  29. package/dist/components/siyahi/stale-note.d.ts +9 -0
  30. package/dist/components/siyahi/stale-note.js +12 -0
  31. package/dist/components/siyahi/treemap.js +1 -1
  32. package/dist/components/siyahi/waterfall-chart.js +4 -4
  33. package/dist/components/ui/field.d.ts +26 -0
  34. package/dist/components/ui/field.js +109 -0
  35. package/dist/components/ui/label.d.ts +5 -0
  36. package/dist/components/ui/label.js +13 -0
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.js +5 -2
  39. package/dist/lib/chart-frame.d.ts +28 -4
  40. package/dist/lib/chart-frame.js +256 -153
  41. package/dist/lib/chart-marks.d.ts +83 -0
  42. package/dist/lib/chart-marks.js +216 -0
  43. package/dist/lib/chart-series.d.ts +15 -0
  44. package/package.json +1 -1
  45. package/report.css +6 -4
  46. package/styles/siyahi.css +260 -18
package/DESIGN.md CHANGED
@@ -10,8 +10,8 @@ component; this file says how to use them.
10
10
  Colour is placed, not spread. A page is neutral greys plus colour that carries
11
11
  a meaning:
12
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
13
+ - **Ink indigo** is the accent: links, the selected sidebar item, the first
14
+ chart series, a chart's "today" rule and an `accent` `Callout`. Buttons, badges, toggles and focus
15
15
  stay monochrome.
16
16
  - **Green and red** mean a gain and a loss. A signed figure is `Delta`: the figure in the text colour with its
17
17
  `+` or `−`, and only a small trend icon in green or red. In plain text (labels, tooltips) write the sign; never
@@ -22,8 +22,9 @@ a meaning:
22
22
  apart is `Callout` (`neutral`, `accent`, `warning`, `positive`,
23
23
  `negative`); a quotation is `Quote`. Each takes a faint wash of its colour
24
24
  inside a 1px border of the same colour all round. Large marks follow the same
25
- rule: gain and loss bars are a tint inside a full-colour edge, not solid
26
- slabs.
25
+ rule: every bar is a tint inside a 1px full-colour edge, not a solid slab.
26
+ A bar is read by its edge, which reaches 3:1 against the card; the tint is a
27
+ fill carried by that edge.
27
28
  - **Never a stripe down one side.** Do not mark a notice, card, row or quote
28
29
  with a left (or any single-side) coloured border; tint the box or add an
29
30
  icon. 1px neutral dividers between columns are separators, not accents, and
@@ -33,8 +34,13 @@ a meaning:
33
34
  canvas and the card; dividers, card rings, gridlines and the outlines of
34
35
  buttons, toggles and code blocks are the `--border` hairline (the text colour
35
36
  at 18% in light, white at 20% in dark), firm enough to carry the page's
36
- structure. Focus is an opaque
37
- `--ring`, or the opaque destructive red on a destructive control. A selected
37
+ structure. Focus is drawn inside the control, so no neighbour or clipping box
38
+ covers it: a 2px ring in the text colour (`--ring`) on its edge with a 2px
39
+ ring of the page colour inside, which reads on a black, white or hovered
40
+ fill alike. A field shows it as its own edge, 2px in the text colour; a chart
41
+ mark as a dashed stroke, apart from a picked mark's solid one. A destructive
42
+ control and an invalid field focus in the destructive red. Menu items show
43
+ focus as their highlight. A selected
38
44
  toggle or pill tab fills with the foreground. A chart bar that a selection
39
45
  dims keeps its full-colour outline and fades only inside.
40
46
  - **Destructive is not loss.** `--destructive` (delete, an invalid field) is a
@@ -82,6 +88,9 @@ mono face and the link colour, so the text can point to one.
82
88
  `rounded-*` class to a box.
83
89
  - **Joined toggles.** The items of a `ToggleGroup` sit flush and share their
84
90
  borders, one control; the pressed item fills with the foreground.
91
+ - **A code block's copy button sits in a header row** (`CodeBlockHeader`),
92
+ beside its title or alone, never floating over the code, where it would
93
+ cover the end of a line. An untitled snippet takes the extra row.
85
94
  - Badges stay boxed (an outline), the primary button and the pressed toggle
86
95
  black (white in dark), titles in the sans face, at normal spacing.
87
96
 
@@ -93,9 +102,69 @@ the actions at the right, and a hairline under it. One per page; no hero card.
93
102
 
94
103
  ## Labels and totals
95
104
 
96
- - **Labels over figures are small monospaced capitals**: a stat tile's label
97
- and footer lines, a `MetricStrip` label, a table's column heads. The
98
- components and the theme set them; never set a label in this style by hand
99
- elsewhere, and never set a figure or a title in it.
105
+ - **Labels are small monospaced capitals**: a stat tile's label and footer
106
+ lines, a `MetricStrip` label, a table's column heads, a sidebar group's
107
+ label, and a form field's label (muted, above the field). The components and
108
+ the theme set them; never set a label in this style by hand elsewhere, and
109
+ never set a figure or a title in it.
100
110
  - **A total row reads as a ledger's**: in `TableFooter`, one rule above it and
101
111
  a double rule under it in the text colour, no band.
112
+
113
+ ## Charts
114
+
115
+ - **Names at the line ends.** A line chart and overlapping areas name each
116
+ series at its line's end, in the text colour beside a square of its colour,
117
+ in a gutter after the last point; ends that crowd are spread 14px apart with
118
+ a short leader. A legend goes below instead on a card under 400px wide (a
119
+ phone), where the names would take more than 30% of it or not fit the
120
+ plot's height, and where a line ends short of the plot's right edge (a
121
+ missing last reading, a single point, an x axis running past the data). Bars and stacks
122
+ keep a legend.
123
+ - **Five colours, then grey.** Past five series, the page names the few it is
124
+ about in chart colours and gives the rest `var(--muted-foreground)`: they
125
+ draw first, in grey. Every line is still named: a grey one's name at its end
126
+ is muted with no square, and its place says which line it is. Never a count
127
+ standing in for names. In the legend a grey name is a button that raises its
128
+ line in the text colour, since a shared grey square cannot tell lines apart;
129
+ a stack's grey bands read in the legend's order, bottom to top.
130
+ - **No fill under overlapping areas**: from three series on, fills bury each
131
+ other. A stack fills each band with a flat tint, never a gradient.
132
+ - **Forecast and today.** `forecastFrom` dashes every line from that point,
133
+ hatches the stretch faintly behind lines on card-coloured halos, labels it
134
+ FORECAST and hollows the end dots (a stack keeps solid edges and no hatching:
135
+ its fills cover the stretch);
136
+ `today` is a 1px rule in the link colour, labelled TODAY.
137
+ - Gridlines are solid hairlines. Lines draw in left to right over 1.3s on
138
+ first draw, a stack's fills revealed with them; then the names fade in, and
139
+ each end dot as its line finishes; bars fade in rather than grow. Reduced
140
+ motion shows them at once.
141
+ - **Stale data is marked by the page**: `stale` on a chart card or a
142
+ `SectionCards` tile, with since when ("3 Nov"). The block greys and its
143
+ chart or sparkline draws at about 65% strength, a tile's figure and badge
144
+ turn muted, and a small "Stale · 3 Nov" note says so. A stale block is meant
145
+ to be harder to read, so its marks are exempt from the 3:1 mark bar; the
146
+ note and the figure keep their text contrast. Never infer it from dates.
147
+
148
+ ## Tables
149
+
150
+ Tables are dense, as a ledger is: compact rows, a 1px rule in the text colour
151
+ under the column heads, the first column (what the row is) in medium weight,
152
+ and figures in the mono face. A figure is a cell marked `data-numeric`, which
153
+ also right-aligns it (the data table marks its number columns; a plain
154
+ `TableCell` or `TableHead` takes the attribute), and any `Delta` in a cell.
155
+ Alignment alone is not a figure. Row hairlines stay; never stripes, never
156
+ column rules.
157
+
158
+ ## Forms
159
+
160
+ - Every field is a `Field` with a `FieldLabel` above the control, a
161
+ `FieldDescription` hint or a `FieldError` under it. Never write a bare
162
+ `<label>` with its own styles.
163
+ - An invalid field (`aria-invalid` on the control, `data-invalid` on its
164
+ `Field`) keeps its red edge and takes a faint red wash.
165
+
166
+ ## Sidebar
167
+
168
+ The sidebar is parted from the page by a hairline on its page-side edge, its
169
+ group labels are small monospaced capitals and its rows are compact. Its fill,
170
+ icons and the current page's mark stay as shadcn draws them.
@@ -1,14 +1,24 @@
1
- import { Avatar, AvatarFallback } from "@siyahi/ui";
1
+ import { Avatar, AvatarBadge, AvatarFallback, AvatarGroup } from "@siyahi/ui";
2
2
 
3
3
  export default function AvatarExample() {
4
4
  return (
5
- <div className="flex gap-2">
5
+ <div className="flex items-center gap-6">
6
6
  <Avatar>
7
7
  <AvatarFallback>JB</AvatarFallback>
8
8
  </Avatar>
9
- <Avatar>
10
- <AvatarFallback>HU</AvatarFallback>
11
- </Avatar>
9
+ {/* A group overlaps its avatars; the first one's badge stays over its neighbour. */}
10
+ <AvatarGroup>
11
+ <Avatar>
12
+ <AvatarFallback>JB</AvatarFallback>
13
+ <AvatarBadge aria-label="Online" />
14
+ </Avatar>
15
+ <Avatar>
16
+ <AvatarFallback>HU</AvatarFallback>
17
+ </Avatar>
18
+ <Avatar>
19
+ <AvatarFallback>AK</AvatarFallback>
20
+ </Avatar>
21
+ </AvatarGroup>
12
22
  </div>
13
23
  );
14
24
  }
@@ -0,0 +1,36 @@
1
+ import { LineChartCard, useFormat, type ChartSeries } from "@siyahi/ui";
2
+
3
+ // Net worth's growth against the plan since May; November and December are a forecast.
4
+ const worth = [
5
+ { month: "May", actual: 0, plan: 0 },
6
+ { month: "Jun", actual: 0.025, plan: 0.022 },
7
+ { month: "Jul", actual: 0.061, plan: 0.044 },
8
+ { month: "Aug", actual: 0.047, plan: 0.067 },
9
+ { month: "Sep", actual: 0.11, plan: 0.089 },
10
+ { month: "Oct", actual: 0.156, plan: 0.111 },
11
+ { month: "Nov", actual: 0.177, plan: 0.133 },
12
+ { month: "Dec", actual: 0.202, plan: 0.156 },
13
+ ];
14
+ const series: ChartSeries<(typeof worth)[number]> = [
15
+ { key: "actual", label: "Net worth", color: "var(--chart-1)" },
16
+ { key: "plan", label: "Plan", color: "var(--chart-2)" },
17
+ ];
18
+
19
+ // Today is a rule in the link colour; from forecastFrom the lines dash, a hatched band covers the stretch and the end
20
+ // dots are hollow.
21
+ export default function ChartLineForecastExample() {
22
+ const { pct } = useFormat();
23
+ return (
24
+ <LineChartCard
25
+ title="Growth against plan"
26
+ description="Since May, month-end; November and December are a forecast"
27
+ data={worth}
28
+ xKey="month"
29
+ series={series}
30
+ today="Oct"
31
+ forecastFrom="Oct"
32
+ valueFormatter={pct}
33
+ height={256}
34
+ />
35
+ );
36
+ }
@@ -0,0 +1,114 @@
1
+ import { LineChartCard, useFormat, type ChartSeries } from "@siyahi/ui";
2
+
3
+ // Eight funds' month-end growth; November and December are a forecast.
4
+ const growth = [
5
+ { month: "May", flexi: 0, debt: 0, small: 0, intl: 0, liquid: 0, midcap: 0, nifty: 0, gold: 0 },
6
+ {
7
+ month: "Jun",
8
+ flexi: 0.018,
9
+ debt: 0.006,
10
+ small: 0.034,
11
+ intl: 0.012,
12
+ liquid: 0.005,
13
+ midcap: 0.022,
14
+ nifty: 0.025,
15
+ gold: 0.041,
16
+ },
17
+ {
18
+ month: "Jul",
19
+ flexi: 0.049,
20
+ debt: 0.013,
21
+ small: 0.012,
22
+ intl: 0.038,
23
+ liquid: 0.011,
24
+ midcap: 0.03,
25
+ nifty: 0.061,
26
+ gold: 0.072,
27
+ },
28
+ {
29
+ month: "Aug",
30
+ flexi: 0.058,
31
+ debt: 0.02,
32
+ small: -0.041,
33
+ intl: 0.066,
34
+ liquid: 0.017,
35
+ midcap: 0.011,
36
+ nifty: 0.047,
37
+ gold: 0.118,
38
+ },
39
+ {
40
+ month: "Sep",
41
+ flexi: 0.094,
42
+ debt: 0.027,
43
+ small: -0.018,
44
+ intl: 0.081,
45
+ liquid: 0.023,
46
+ midcap: 0.052,
47
+ nifty: 0.11,
48
+ gold: 0.153,
49
+ },
50
+ {
51
+ month: "Oct",
52
+ flexi: 0.141,
53
+ debt: 0.034,
54
+ small: -0.052,
55
+ intl: 0.119,
56
+ liquid: 0.029,
57
+ midcap: 0.078,
58
+ nifty: 0.156,
59
+ gold: 0.196,
60
+ },
61
+ {
62
+ month: "Nov",
63
+ flexi: 0.166,
64
+ debt: 0.043,
65
+ small: -0.04,
66
+ intl: 0.142,
67
+ liquid: 0.037,
68
+ midcap: 0.099,
69
+ nifty: 0.177,
70
+ gold: 0.221,
71
+ },
72
+ {
73
+ month: "Dec",
74
+ flexi: 0.189,
75
+ debt: 0.052,
76
+ small: -0.031,
77
+ intl: 0.164,
78
+ liquid: 0.046,
79
+ midcap: 0.12,
80
+ nifty: 0.202,
81
+ gold: 0.245,
82
+ },
83
+ ];
84
+
85
+ // The palette has five colours, so past five the page names the few it is about in colour; the rest are muted, draw
86
+ // first in grey, each still named at its end.
87
+ const QUIET = "var(--muted-foreground)";
88
+ const series: ChartSeries<(typeof growth)[number]> = [
89
+ { key: "flexi", label: "Flexi cap", color: QUIET },
90
+ { key: "debt", label: "Debt", color: QUIET },
91
+ { key: "small", label: "Small cap", color: QUIET },
92
+ { key: "intl", label: "International", color: QUIET },
93
+ { key: "liquid", label: "Liquid", color: QUIET },
94
+ { key: "midcap", label: "Mid cap", color: QUIET },
95
+ { key: "nifty", label: "Nifty 50", color: "var(--chart-1)" },
96
+ { key: "gold", label: "Gold", color: "var(--chart-2)" },
97
+ ];
98
+
99
+ export default function ChartLineQuietExample() {
100
+ const { pct } = useFormat();
101
+ return (
102
+ <LineChartCard
103
+ title="Growth by fund"
104
+ description="Eight funds; the two you are watching in colour"
105
+ data={growth}
106
+ xKey="month"
107
+ series={series}
108
+ today="Oct"
109
+ forecastFrom="Oct"
110
+ valueFormatter={pct}
111
+ height={256}
112
+ />
113
+ );
114
+ }
@@ -0,0 +1,94 @@
1
+ import { AreaChartCard, BarChartCard, LineChartCard, PanelGrid, useFormat, type ChartSeries } from "@siyahi/ui";
2
+
3
+ // Month-end growth of five funds; November and December are a forecast.
4
+ const growth = [
5
+ { month: "May", nifty: 0, flexi: 0, gold: 0, debt: 0, small: 0 },
6
+ { month: "Jun", nifty: 0.025, flexi: 0.018, gold: 0.041, debt: 0.006, small: 0.034 },
7
+ { month: "Jul", nifty: 0.061, flexi: 0.049, gold: 0.072, debt: 0.013, small: 0.012 },
8
+ { month: "Aug", nifty: 0.047, flexi: 0.058, gold: 0.118, debt: 0.02, small: -0.041 },
9
+ { month: "Sep", nifty: 0.11, flexi: 0.094, gold: 0.153, debt: 0.027, small: -0.018 },
10
+ { month: "Oct", nifty: 0.156, flexi: 0.141, gold: 0.196, debt: 0.034, small: -0.052 },
11
+ { month: "Nov", nifty: 0.177, flexi: 0.166, gold: 0.221, debt: 0.043, small: -0.04 },
12
+ { month: "Dec", nifty: 0.202, flexi: 0.189, gold: 0.245, debt: 0.052, small: -0.031 },
13
+ ];
14
+ const funds: ChartSeries<(typeof growth)[number]> = [
15
+ { key: "nifty", label: "Nifty 50", color: "var(--chart-1)" },
16
+ { key: "flexi", label: "Flexi cap", color: "var(--chart-2)" },
17
+ { key: "gold", label: "Gold", color: "var(--chart-3)" },
18
+ { key: "debt", label: "Debt", color: "var(--chart-4)" },
19
+ { key: "small", label: "Small cap", color: "var(--chart-5)" },
20
+ ];
21
+
22
+ const value = [
23
+ { month: "May", equity: 2150000, debt: 600000, gold: 320000, intl: 390000, cash: 150000 },
24
+ { month: "Jun", equity: 2230000, debt: 612000, gold: 334000, intl: 402000, cash: 160000 },
25
+ { month: "Jul", equity: 2390000, debt: 625000, gold: 352000, intl: 421000, cash: 172000 },
26
+ { month: "Aug", equity: 2310000, debt: 637000, gold: 371000, intl: 447000, cash: 185000 },
27
+ { month: "Sep", equity: 2620000, debt: 648000, gold: 390000, intl: 462000, cash: 198000 },
28
+ { month: "Oct", equity: 2800000, debt: 655000, gold: 410500, intl: 486300, cash: 212000 },
29
+ ];
30
+ const added = [
31
+ { quarter: "Q1", equity: 180000, debt: 60000, gold: 20000, intl: 40000, cash: 25000 },
32
+ { quarter: "Q2", equity: 220000, debt: 40000, gold: 45000, intl: 35000, cash: 15000 },
33
+ { quarter: "Q3", equity: 150000, debt: 80000, gold: 30000, intl: 55000, cash: 30000 },
34
+ { quarter: "Q4", equity: 260000, debt: 50000, gold: 60000, intl: 70000, cash: 20000 },
35
+ ];
36
+ const kinds = [
37
+ { key: "equity", label: "Equity", color: "var(--chart-1)" },
38
+ { key: "debt", label: "Debt", color: "var(--chart-2)" },
39
+ { key: "gold", label: "Gold", color: "var(--chart-3)" },
40
+ { key: "intl", label: "International", color: "var(--chart-4)" },
41
+ { key: "cash", label: "Cash", color: "var(--chart-5)" },
42
+ ] as const;
43
+
44
+ // Four charts in a PanelGrid: lines named at their ends, a forecast after today, overlapping areas as lines, and bars
45
+ // as tints inside full-colour edges, grouped and stacked.
46
+ export default function ChartPanelsExample() {
47
+ const { pct, money, moneyCompact } = useFormat();
48
+ return (
49
+ <PanelGrid>
50
+ <LineChartCard
51
+ title="Growth by fund"
52
+ description="Since May, month-end; November and December are a forecast"
53
+ data={growth}
54
+ xKey="month"
55
+ series={funds}
56
+ today="Oct"
57
+ forecastFrom="Oct"
58
+ valueFormatter={pct}
59
+ height={256}
60
+ />
61
+ <AreaChartCard
62
+ title="Value by kind"
63
+ description="Month-end value of each kind of holding"
64
+ data={value}
65
+ xKey="month"
66
+ series={kinds}
67
+ valueFormatter={money}
68
+ yTickFormatter={moneyCompact}
69
+ height={256}
70
+ />
71
+ <BarChartCard
72
+ title="Money added by kind and quarter"
73
+ description="Side by side"
74
+ data={added}
75
+ xKey="quarter"
76
+ series={kinds}
77
+ valueFormatter={money}
78
+ yTickFormatter={moneyCompact}
79
+ height={256}
80
+ />
81
+ <BarChartCard
82
+ title="Money added by kind and quarter"
83
+ description="Stacked into the quarter's total"
84
+ data={added}
85
+ xKey="quarter"
86
+ series={kinds}
87
+ stacked
88
+ valueFormatter={money}
89
+ yTickFormatter={moneyCompact}
90
+ height={256}
91
+ />
92
+ </PanelGrid>
93
+ );
94
+ }
@@ -28,9 +28,11 @@ export default function CodeBlockExample() {
28
28
  <CodeBlockContent wrap>{CHECK}</CodeBlockContent>
29
29
  </CodeBlock>
30
30
  <CodeBlock size="sm">
31
- <CodeBlockActions>
32
- <CodeBlockCopyButton value={LOG} />
33
- </CodeBlockActions>
31
+ <CodeBlockHeader>
32
+ <CodeBlockActions>
33
+ <CodeBlockCopyButton value={LOG} />
34
+ </CodeBlockActions>
35
+ </CodeBlockHeader>
34
36
  <CodeBlockContent className="max-h-48">{LOG}</CodeBlockContent>
35
37
  </CodeBlock>
36
38
  </div>
@@ -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
+ }
@@ -5,13 +5,13 @@ function Row({ label }: { label: string }) {
5
5
  return (
6
6
  <TableRow>
7
7
  <TableCell>{label}</TableCell>
8
- <TableCell className="text-right">{count(153110)}</TableCell>
9
- <TableCell className="text-right">{money(153110)}</TableCell>
10
- <TableCell className="text-right">{moneyCompact(99999)}</TableCell>
11
- <TableCell className="text-right">{moneyCompact(240000)}</TableCell>
12
- <TableCell className="text-right">{moneyCompact(400000000)}</TableCell>
13
- <TableCell className="text-right">{pct(0.1234)}</TableCell>
14
- <TableCell className="text-right">{time("2026-10-20T09:00:00Z")}</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
15
  </TableRow>
16
16
  );
17
17
  }
@@ -22,13 +22,13 @@ export default function FormattersExample() {
22
22
  <TableHeader>
23
23
  <TableRow>
24
24
  <TableHead>Locale</TableHead>
25
- <TableHead className="text-right">count</TableHead>
26
- <TableHead className="text-right">money</TableHead>
27
- <TableHead className="text-right">moneyCompact</TableHead>
28
- <TableHead className="text-right">moneyCompact</TableHead>
29
- <TableHead className="text-right">moneyCompact</TableHead>
30
- <TableHead className="text-right">pct</TableHead>
31
- <TableHead className="text-right">time</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
32
  </TableRow>
33
33
  </TableHeader>
34
34
  <TableBody>
@@ -1,33 +1,23 @@
1
- import { Input } from "@siyahi/ui";
1
+ import { Field, FieldDescription, FieldError, FieldLabel, Input } from "@siyahi/ui";
2
2
 
3
3
  // A field is its label, the input and one line under it: a hint, or the error that replaces it.
4
4
  export default function InputStatesExample() {
5
5
  return (
6
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>
7
+ <Field>
8
+ <FieldLabel htmlFor="budget">Monthly budget</FieldLabel>
11
9
  <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 tax
14
- </p>
15
- </div>
16
- <div className="grid gap-1.5">
17
- <label htmlFor="pin" className="text-sm font-medium">
18
- PIN code
19
- </label>
10
+ <FieldDescription id="budget-hint">In rupees, before tax</FieldDescription>
11
+ </Field>
12
+ <Field data-invalid>
13
+ <FieldLabel htmlFor="pin">PIN code</FieldLabel>
20
14
  <Input id="pin" defaultValue="56/001" aria-invalid aria-describedby="pin-error" />
21
- <p id="pin-error" className="text-sm text-destructive">
22
- A PIN code is six digits, such as 560001
23
- </p>
24
- </div>
25
- <div className="grid gap-1.5">
26
- <label htmlFor="team" className="text-sm font-medium text-muted-foreground">
27
- Team
28
- </label>
15
+ <FieldError id="pin-error">A PIN code is six digits, such as 560001</FieldError>
16
+ </Field>
17
+ <Field data-disabled>
18
+ <FieldLabel htmlFor="team">Team</FieldLabel>
29
19
  <Input id="team" defaultValue="Finance" disabled />
30
- </div>
20
+ </Field>
31
21
  </div>
32
22
  );
33
23
  }
@@ -75,8 +75,8 @@ function Holdings({ rows }: { rows: Holding[] }) {
75
75
  <TableRow>
76
76
  <TableHead>Holding</TableHead>
77
77
  <TableHead>Status</TableHead>
78
- <TableHead className="text-right">Value</TableHead>
79
- <TableHead className="text-right">12 months</TableHead>
78
+ <TableHead data-numeric>Value</TableHead>
79
+ <TableHead data-numeric>12 months</TableHead>
80
80
  </TableRow>
81
81
  </TableHeader>
82
82
  <TableBody>
@@ -99,8 +99,8 @@ function Holdings({ rows }: { rows: Holding[] }) {
99
99
  <TableCell>
100
100
  <StatusBadge tone={row.tone}>{row.status}</StatusBadge>
101
101
  </TableCell>
102
- <TableCell className="text-right">{money(row.value)}</TableCell>
103
- <TableCell className="text-right">
102
+ <TableCell data-numeric>{money(row.value)}</TableCell>
103
+ <TableCell data-numeric>
104
104
  <Delta value={row.change} />
105
105
  </TableCell>
106
106
  </TableRow>
@@ -109,8 +109,8 @@ function Holdings({ rows }: { rows: Holding[] }) {
109
109
  <TableFooter>
110
110
  <TableRow>
111
111
  <TableCell colSpan={2}>Total</TableCell>
112
- <TableCell className="text-right">{money(total)}</TableCell>
113
- <TableCell className="text-right">{yearAgo > 0 && pct(total / yearAgo - 1)}</TableCell>
112
+ <TableCell data-numeric>{money(total)}</TableCell>
113
+ <TableCell data-numeric>{yearAgo > 0 && pct(total / yearAgo - 1)}</TableCell>
114
114
  </TableRow>
115
115
  </TableFooter>
116
116
  </Table>