@siyahi/ui 0.1.9-next.28 → 0.1.9-next.29
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 +60 -2
- package/README.md +17 -6
- package/agents/examples/overview-dashboard.tsx +172 -10
- package/agents/examples/section-cards-select.tsx +5 -8
- package/agents/examples/section-cards-trend.tsx +2 -0
- package/agents/examples/surfaces.tsx +2 -2
- package/agents/examples/table.tsx +11 -1
- package/agents/manifest.json +27 -3
- package/dist/components/registry/dashboard-01/section-cards.d.ts +2 -0
- package/dist/components/registry/dashboard-01/section-cards.js +11 -5
- package/dist/components/registry/dashboardcn/distribution-bar.js +1 -1
- package/dist/components/registry/dashboardcn/kpi-row-card.js +3 -3
- package/dist/components/siyahi/change-bar.js +2 -2
- package/dist/components/siyahi/data-bar.js +2 -2
- package/dist/components/siyahi/page-hero.d.ts +9 -11
- package/dist/components/siyahi/page-hero.js +32 -30
- package/dist/components/siyahi/panel-grid.d.ts +5 -3
- package/dist/components/siyahi/panel-grid.js +8 -5
- package/dist/components/siyahi/range-position.js +2 -2
- package/dist/components/siyahi/siyahi-provider.d.ts +6 -4
- package/dist/components/siyahi/siyahi-provider.js +1 -1
- package/package.json +2 -1
- package/report.css +9 -5
- package/styles/siyahi.css +167 -28
package/DESIGN.md
CHANGED
|
@@ -28,10 +28,12 @@ a meaning:
|
|
|
28
28
|
with a left (or any single-side) coloured border; tint the box or add an
|
|
29
29
|
icon. 1px neutral dividers between columns are separators, not accents, and
|
|
30
30
|
are fine.
|
|
31
|
-
- **Edges that matter reach 3:1; the rest
|
|
31
|
+
- **Edges that matter reach 3:1; the rest are hairlines.** A field's edge (input,
|
|
32
32
|
textarea, select, input group, the command search) is `--input`, at 3:1 on the
|
|
33
33
|
canvas and the card; dividers, card rings, gridlines and the outlines of
|
|
34
|
-
buttons, toggles and code blocks are the
|
|
34
|
+
buttons, toggles and code blocks are the `--border` hairline (the text colour
|
|
35
|
+
at 18% in light, white at 20% in dark), firm enough to carry the page's
|
|
36
|
+
structure. Focus is an opaque
|
|
35
37
|
`--ring`, or the opaque destructive red on a destructive control. A selected
|
|
36
38
|
toggle or pill tab fills with the foreground. A chart bar that a selection
|
|
37
39
|
dims keeps its full-colour outline and fades only inside.
|
|
@@ -41,3 +43,59 @@ a meaning:
|
|
|
41
43
|
- Categories (a team, a region, a product line) stay neutral: outline badges,
|
|
42
44
|
never a hue each. Ordered buckets (largest 5, next 5, rest) use steps of one
|
|
43
45
|
colour, not separate hues.
|
|
46
|
+
- **Colour marks exceptions.** The one stat tile or table row that needs a look
|
|
47
|
+
is marked by the page: `tone: "negative"` or `"warning"` on a `SectionCards`
|
|
48
|
+
item, `data-tone="negative"` or `"warning"` on a `TableRow`. It takes a faint
|
|
49
|
+
wash of the colour over the card (7% in light, 12% in dark), and a tile an
|
|
50
|
+
outline of it. The page decides, never the sign: a falling cost is good news.
|
|
51
|
+
Mark one or two; a page where every row is washed marks nothing.
|
|
52
|
+
|
|
53
|
+
## Surfaces
|
|
54
|
+
|
|
55
|
+
`SiyahiProvider`'s `surface` sets how a data block sits on the page. The page
|
|
56
|
+
and card colours are fixed: never tint the canvas.
|
|
57
|
+
|
|
58
|
+
- **`ruled`** (the default, with or without a provider): ruled on white. The
|
|
59
|
+
blocks of a `PanelGrid`, the tiles of a `SectionCards` row and the figures
|
|
60
|
+
of a `MetricStrip` are filled with the card colour and parted by hairlines,
|
|
61
|
+
with a rule above and below the group and none on its left and right edges.
|
|
62
|
+
A card standing alone (a `MetricStrip`, a table's card) takes the same rules
|
|
63
|
+
above and below itself. A short last row ends in a blank cell, ruled off
|
|
64
|
+
like the rest. A stat tile keeps its footer's top rule and faint band. Set
|
|
65
|
+
nothing to get it.
|
|
66
|
+
- **`card`**: tight square cards, for a page of separate blocks, such as an
|
|
67
|
+
index of links. Each block is filled with the card colour inside a hairline
|
|
68
|
+
ring, with no shadow, 8px from the next in a `PanelGrid` or a row of
|
|
69
|
+
`SectionCards`. Ask for it with `surface="card"`.
|
|
70
|
+
- **`open`**: no fill, no lines and no padding, so a block sits level with the
|
|
71
|
+
page's text, for pages read top to bottom.
|
|
72
|
+
|
|
73
|
+
Every `PanelGrid` numbers its blocks 01, 02 before their card title, in the
|
|
74
|
+
mono face and the link colour, so the text can point to one.
|
|
75
|
+
|
|
76
|
+
## Shape
|
|
77
|
+
|
|
78
|
+
- **Square corners everywhere**: panels, cards, buttons, fields, badges,
|
|
79
|
+
toggles, menus, and bars and tracks (data bars, progress) alike (`--radius`
|
|
80
|
+
is 0). Only a dot, an avatar, a slider, a spinner or a scrollbar is round;
|
|
81
|
+
the stock chart tooltip's colour chips keep shadcn's 2px. Never add a
|
|
82
|
+
`rounded-*` class to a box.
|
|
83
|
+
- **Joined toggles.** The items of a `ToggleGroup` sit flush and share their
|
|
84
|
+
borders, one control; the pressed item fills with the foreground.
|
|
85
|
+
- Badges stay boxed (an outline), the primary button and the pressed toggle
|
|
86
|
+
black (white in dark), titles in the sans face, at normal spacing.
|
|
87
|
+
|
|
88
|
+
## Page header
|
|
89
|
+
|
|
90
|
+
An app page opens on its masthead, `PageHero`: a small mark and eyebrow in the
|
|
91
|
+
mono face and link colour, the title, one muted line saying what the page is,
|
|
92
|
+
the actions at the right, and a hairline under it. One per page; no hero card.
|
|
93
|
+
|
|
94
|
+
## Labels and totals
|
|
95
|
+
|
|
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.
|
|
100
|
+
- **A total row reads as a ledger's**: in `TableFooter`, one rule above it and
|
|
101
|
+
a double rule under it in the text colour, no band.
|
package/README.md
CHANGED
|
@@ -100,12 +100,23 @@ 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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
- `surface="ruled"` (default, even outside a provider): ruled on white.
|
|
104
|
+
`PanelGrid`, `SectionCards` and `MetricStrip` fill their blocks with the card
|
|
105
|
+
colour and part them with hairlines, with a rule above and below each group
|
|
106
|
+
and none on the sides; a card standing alone takes the rules above and below
|
|
107
|
+
itself.
|
|
108
|
+
- `surface="card"`: tight square cards, each filled inside a hairline, 8px
|
|
109
|
+
apart, no shadow, for a page of separate blocks.
|
|
110
|
+
- `surface="open"`: no fill, boxes or rules; card padding is removed.
|
|
111
|
+
|
|
112
|
+
`DESIGN.md` has the rest of the look: square corners, mono capital labels,
|
|
113
|
+
ledger totals, the masthead and exception tones.
|
|
114
|
+
|
|
115
|
+
Nest a provider to change one section. A dialog, sheet or popover renders
|
|
116
|
+
outside the page's provider element: `PanelGrid` and `SectionCards` carry
|
|
117
|
+
their surface into it, but a lone `Card` there is ruled unless the popup's
|
|
118
|
+
content sits in its own provider with an explicit surface, such as
|
|
119
|
+
`<SiyahiProvider surface="card">`; a provider without one draws no element. Stack `PageSection` blocks with
|
|
109
120
|
`flex flex-col gap-6`; use `PanelGrid` for blocks side by side. Categories use
|
|
110
121
|
neutral outline badges, leaving colour for meaning and chart series.
|
|
111
122
|
|
|
@@ -1,18 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Badge,
|
|
3
|
+
Button,
|
|
4
|
+
Callout,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
Delta,
|
|
11
|
+
Input,
|
|
12
|
+
PageHero,
|
|
13
|
+
PanelGrid,
|
|
14
|
+
StatusBadge,
|
|
15
|
+
Table,
|
|
16
|
+
TableBody,
|
|
17
|
+
TableCell,
|
|
18
|
+
TableFooter,
|
|
19
|
+
TableHead,
|
|
20
|
+
TableHeader,
|
|
21
|
+
TableRow,
|
|
22
|
+
ToggleGroup,
|
|
23
|
+
ToggleGroupItem,
|
|
24
|
+
useFormat,
|
|
25
|
+
} from "@siyahi/ui";
|
|
26
|
+
import { DownloadIcon, LightbulbIcon, PlusIcon } from "lucide-react";
|
|
27
|
+
import { useState } from "react";
|
|
1
28
|
import ChartArea from "./chart-area";
|
|
2
|
-
import
|
|
3
|
-
import MetricStrip from "./metric-strip";
|
|
29
|
+
import SectionCardsTrend from "./section-cards-trend";
|
|
4
30
|
|
|
5
|
-
|
|
31
|
+
interface Holding {
|
|
32
|
+
name: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
status: string;
|
|
35
|
+
tone: "positive" | "warning" | "negative";
|
|
36
|
+
value: number;
|
|
37
|
+
change: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const holdings: Holding[] = [
|
|
41
|
+
{ name: "Nifty 50 index fund", kind: "Equity", status: "On track", tone: "positive", value: 1840000, change: 0.124 },
|
|
42
|
+
{ name: "Flexi cap fund", kind: "Equity", status: "On track", tone: "positive", value: 962000, change: 0.087 },
|
|
43
|
+
{ name: "Gold bonds, 2031", kind: "Gold", status: "Review", tone: "warning", value: 410500, change: 0.211 },
|
|
44
|
+
{ name: "Short-term debt fund", kind: "Debt", status: "On track", tone: "positive", value: 655000, change: 0.031 },
|
|
45
|
+
{ name: "Small cap fund", kind: "Equity", status: "Below plan", tone: "negative", value: 318200, change: -0.064 },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// The catalogue's own ink drop, painted with currentColor so the masthead tints it.
|
|
49
|
+
function InkDrop() {
|
|
50
|
+
return (
|
|
51
|
+
<svg viewBox="0 0 16 16">
|
|
52
|
+
<path
|
|
53
|
+
fill="currentColor"
|
|
54
|
+
fillRule="evenodd"
|
|
55
|
+
d="M8 .5C8 .5 2.5 6.3 2.5 10a5.5 5.5 0 0 0 11 0C13.5 6.3 8 .5 8 .5ZM4.75 10A3.25 3.25 0 0 0 8 13.25v-1.5A1.75 1.75 0 0 1 6.25 10Z"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Holdings({ rows }: { rows: Holding[] }) {
|
|
62
|
+
const { money, pct } = useFormat();
|
|
63
|
+
const total = rows.reduce((sum, row) => sum + row.value, 0);
|
|
64
|
+
// The change of the whole: today's total against what the same holdings were worth a year ago.
|
|
65
|
+
const yearAgo = rows.reduce((sum, row) => sum + row.value / (1 + row.change), 0);
|
|
66
|
+
return (
|
|
67
|
+
<Card>
|
|
68
|
+
<CardHeader>
|
|
69
|
+
<CardTitle>Holdings</CardTitle>
|
|
70
|
+
<CardDescription>Value today and change over twelve months</CardDescription>
|
|
71
|
+
</CardHeader>
|
|
72
|
+
<CardContent>
|
|
73
|
+
<Table>
|
|
74
|
+
<TableHeader>
|
|
75
|
+
<TableRow>
|
|
76
|
+
<TableHead>Holding</TableHead>
|
|
77
|
+
<TableHead>Status</TableHead>
|
|
78
|
+
<TableHead className="text-right">Value</TableHead>
|
|
79
|
+
<TableHead className="text-right">12 months</TableHead>
|
|
80
|
+
</TableRow>
|
|
81
|
+
</TableHeader>
|
|
82
|
+
<TableBody>
|
|
83
|
+
{rows.length === 0 && (
|
|
84
|
+
<TableRow>
|
|
85
|
+
<TableCell colSpan={4}>
|
|
86
|
+
<span className="text-muted-foreground">No holding matches the search.</span>
|
|
87
|
+
</TableCell>
|
|
88
|
+
</TableRow>
|
|
89
|
+
)}
|
|
90
|
+
{rows.map((row) => (
|
|
91
|
+
// Only the holdings that need a look are marked; a row on track stays plain.
|
|
92
|
+
<TableRow key={row.name} data-tone={row.tone === "positive" ? undefined : row.tone}>
|
|
93
|
+
<TableCell>
|
|
94
|
+
<span className="flex flex-wrap items-center gap-2">
|
|
95
|
+
{row.name}
|
|
96
|
+
<Badge variant="outline">{row.kind}</Badge>
|
|
97
|
+
</span>
|
|
98
|
+
</TableCell>
|
|
99
|
+
<TableCell>
|
|
100
|
+
<StatusBadge tone={row.tone}>{row.status}</StatusBadge>
|
|
101
|
+
</TableCell>
|
|
102
|
+
<TableCell className="text-right">{money(row.value)}</TableCell>
|
|
103
|
+
<TableCell className="text-right">
|
|
104
|
+
<Delta value={row.change} />
|
|
105
|
+
</TableCell>
|
|
106
|
+
</TableRow>
|
|
107
|
+
))}
|
|
108
|
+
</TableBody>
|
|
109
|
+
<TableFooter>
|
|
110
|
+
<TableRow>
|
|
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>
|
|
114
|
+
</TableRow>
|
|
115
|
+
</TableFooter>
|
|
116
|
+
</Table>
|
|
117
|
+
</CardContent>
|
|
118
|
+
</Card>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// An app page on the default, ruled surface: masthead, controls, stat tiles, a chart and a table in a PanelGrid, and
|
|
123
|
+
// the takeaway. It sets no surface: ruled on white is what a page gets.
|
|
6
124
|
export default function OverviewDashboard() {
|
|
125
|
+
const [query, setQuery] = useState("");
|
|
126
|
+
const shown = holdings.filter((row) => row.name.toLowerCase().includes(query.trim().toLowerCase()));
|
|
7
127
|
return (
|
|
8
|
-
<div className="flex flex-col gap-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
128
|
+
<div className="flex flex-col gap-6">
|
|
129
|
+
<PageHero
|
|
130
|
+
mark={<InkDrop />}
|
|
131
|
+
eyebrow="The Book"
|
|
132
|
+
title="Holdings"
|
|
133
|
+
lead="What you own, what it is worth today, and how each part has moved."
|
|
134
|
+
detail="Prices as of the last market close."
|
|
135
|
+
actions={
|
|
136
|
+
<>
|
|
137
|
+
<Button>
|
|
138
|
+
<PlusIcon />
|
|
139
|
+
Add a holding
|
|
140
|
+
</Button>
|
|
141
|
+
<Button variant="outline">
|
|
142
|
+
<DownloadIcon />
|
|
143
|
+
Export
|
|
144
|
+
</Button>
|
|
145
|
+
</>
|
|
146
|
+
}
|
|
147
|
+
/>
|
|
148
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
149
|
+
<ToggleGroup defaultValue={["1y"]} variant="outline" aria-label="Period">
|
|
150
|
+
<ToggleGroupItem value="1m">1 month</ToggleGroupItem>
|
|
151
|
+
<ToggleGroupItem value="1y">1 year</ToggleGroupItem>
|
|
152
|
+
<ToggleGroupItem value="all">All</ToggleGroupItem>
|
|
153
|
+
</ToggleGroup>
|
|
154
|
+
<Input
|
|
155
|
+
className="w-56"
|
|
156
|
+
type="search"
|
|
157
|
+
placeholder="Search holdings"
|
|
158
|
+
aria-label="Search holdings"
|
|
159
|
+
value={query}
|
|
160
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
161
|
+
/>
|
|
162
|
+
<p className="text-sm text-muted-foreground">
|
|
163
|
+
Five holdings in three kinds.{" "}
|
|
164
|
+
<a className="text-link underline underline-offset-4" href="#/data">
|
|
165
|
+
How value is worked out
|
|
166
|
+
</a>
|
|
167
|
+
</p>
|
|
15
168
|
</div>
|
|
169
|
+
<SectionCardsTrend />
|
|
170
|
+
<PanelGrid>
|
|
171
|
+
<ChartArea />
|
|
172
|
+
<Holdings rows={shown} />
|
|
173
|
+
</PanelGrid>
|
|
174
|
+
<Callout tone="accent" icon={<LightbulbIcon />} title="The takeaway">
|
|
175
|
+
Equity is <strong>74%</strong> of the book against a plan of 65%. The small cap fund is the only holding below
|
|
176
|
+
plan.
|
|
177
|
+
</Callout>
|
|
16
178
|
</div>
|
|
17
179
|
);
|
|
18
180
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Delta, SectionCards,
|
|
1
|
+
import { Delta, SectionCards, useFormat, type SectionCard } from "@siyahi/ui";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
// Each sales channel's profit this quarter and its orders; made-up figures.
|
|
@@ -20,12 +20,9 @@ export default function SectionCardsSelectExample() {
|
|
|
20
20
|
disabled: c.orders === 0,
|
|
21
21
|
}));
|
|
22
22
|
return (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
<p className="text-sm text-muted-foreground">Showing {scope}</p>
|
|
28
|
-
</div>
|
|
29
|
-
</SiyahiProvider>
|
|
23
|
+
<div className="flex flex-col gap-3">
|
|
24
|
+
<SectionCards items={items} selected={scope} onSelect={setScope} />
|
|
25
|
+
<p className="text-sm text-muted-foreground">Showing {scope}</p>
|
|
26
|
+
</div>
|
|
30
27
|
);
|
|
31
28
|
}
|
|
@@ -17,6 +17,8 @@ export default function SectionCardsTrendExample() {
|
|
|
17
17
|
// A run of losses: the meaning colour, not a chart slot.
|
|
18
18
|
trend: <Sparkline data={[0, -8200, -14100, -30400, -52800, -61200]} color="var(--negative)" />,
|
|
19
19
|
detail: "Running total since April",
|
|
20
|
+
// The one figure on the page that needs a look, marked by the page: a falling cost would not be.
|
|
21
|
+
tone: "negative",
|
|
20
22
|
},
|
|
21
23
|
{
|
|
22
24
|
label: "Gift cards this year",
|
|
@@ -89,7 +89,7 @@ function Dashboard() {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export default function SurfacesExample() {
|
|
92
|
-
const [surface, setSurface] = useState<Surface>("
|
|
92
|
+
const [surface, setSurface] = useState<Surface>("ruled");
|
|
93
93
|
return (
|
|
94
94
|
<div className="flex flex-col gap-4">
|
|
95
95
|
<ToggleGroup
|
|
@@ -103,8 +103,8 @@ export default function SurfacesExample() {
|
|
|
103
103
|
aria-label="Surface"
|
|
104
104
|
>
|
|
105
105
|
{/* The default first. */}
|
|
106
|
-
<ToggleGroupItem value="card">Card</ToggleGroupItem>
|
|
107
106
|
<ToggleGroupItem value="ruled">Ruled</ToggleGroupItem>
|
|
107
|
+
<ToggleGroupItem value="card">Card</ToggleGroupItem>
|
|
108
108
|
<ToggleGroupItem value="open">Open</ToggleGroupItem>
|
|
109
109
|
</ToggleGroup>
|
|
110
110
|
{/* A nested provider sets the surface for its part of the page. */}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Table,
|
|
7
7
|
TableBody,
|
|
8
8
|
TableCell,
|
|
9
|
+
TableFooter,
|
|
9
10
|
TableHead,
|
|
10
11
|
TableHeader,
|
|
11
12
|
TableRow,
|
|
@@ -20,6 +21,7 @@ const rows = [
|
|
|
20
21
|
|
|
21
22
|
export default function TableExample() {
|
|
22
23
|
const { money } = useFormat();
|
|
24
|
+
const total = (key: "in" | "out") => rows.reduce((sum, row) => sum + row[key], 0);
|
|
23
25
|
return (
|
|
24
26
|
<Card>
|
|
25
27
|
<CardHeader>
|
|
@@ -36,13 +38,21 @@ export default function TableExample() {
|
|
|
36
38
|
</TableHeader>
|
|
37
39
|
<TableBody>
|
|
38
40
|
{rows.map((row) => (
|
|
39
|
-
|
|
41
|
+
// August's spending needs a look: the page marks the row, and the theme washes it amber.
|
|
42
|
+
<TableRow key={row.month} data-tone={row.out > 50000 ? "warning" : undefined}>
|
|
40
43
|
<TableCell>{row.month}</TableCell>
|
|
41
44
|
<TableCell className="text-right">{money(row.in)}</TableCell>
|
|
42
45
|
<TableCell className="text-right">{money(row.out)}</TableCell>
|
|
43
46
|
</TableRow>
|
|
44
47
|
))}
|
|
45
48
|
</TableBody>
|
|
49
|
+
<TableFooter>
|
|
50
|
+
<TableRow>
|
|
51
|
+
<TableCell>Total</TableCell>
|
|
52
|
+
<TableCell className="text-right">{money(total("in"))}</TableCell>
|
|
53
|
+
<TableCell className="text-right">{money(total("out"))}</TableCell>
|
|
54
|
+
</TableRow>
|
|
55
|
+
</TableFooter>
|
|
46
56
|
</Table>
|
|
47
57
|
</CardContent>
|
|
48
58
|
</Card>
|
package/agents/manifest.json
CHANGED
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
]
|
|
290
290
|
},
|
|
291
291
|
{
|
|
292
|
-
"need": "
|
|
292
|
+
"need": "Separate square cards instead of the default ruled panels, or no lines at all, for a page or a part of one",
|
|
293
293
|
"guidance": "`SiyahiProvider` with `surface=\"card\"` or `surface=\"open\"`",
|
|
294
294
|
"components": [
|
|
295
295
|
"SiyahiProvider"
|
|
@@ -936,7 +936,31 @@
|
|
|
936
936
|
},
|
|
937
937
|
{
|
|
938
938
|
"file": "agents/examples/overview-dashboard.tsx",
|
|
939
|
-
"components": [
|
|
939
|
+
"components": [
|
|
940
|
+
"Badge",
|
|
941
|
+
"Button",
|
|
942
|
+
"Callout",
|
|
943
|
+
"Card",
|
|
944
|
+
"CardContent",
|
|
945
|
+
"CardDescription",
|
|
946
|
+
"CardHeader",
|
|
947
|
+
"CardTitle",
|
|
948
|
+
"Delta",
|
|
949
|
+
"Input",
|
|
950
|
+
"PageHero",
|
|
951
|
+
"PanelGrid",
|
|
952
|
+
"StatusBadge",
|
|
953
|
+
"Table",
|
|
954
|
+
"TableBody",
|
|
955
|
+
"TableCell",
|
|
956
|
+
"TableFooter",
|
|
957
|
+
"TableHead",
|
|
958
|
+
"TableHeader",
|
|
959
|
+
"TableRow",
|
|
960
|
+
"ToggleGroup",
|
|
961
|
+
"ToggleGroupItem",
|
|
962
|
+
"useFormat"
|
|
963
|
+
]
|
|
940
964
|
},
|
|
941
965
|
{
|
|
942
966
|
"file": "agents/examples/page-hero.tsx",
|
|
@@ -1066,7 +1090,6 @@
|
|
|
1066
1090
|
"components": [
|
|
1067
1091
|
"Delta",
|
|
1068
1092
|
"SectionCards",
|
|
1069
|
-
"SiyahiProvider",
|
|
1070
1093
|
"useFormat"
|
|
1071
1094
|
]
|
|
1072
1095
|
},
|
|
@@ -1183,6 +1206,7 @@
|
|
|
1183
1206
|
"Table",
|
|
1184
1207
|
"TableBody",
|
|
1185
1208
|
"TableCell",
|
|
1209
|
+
"TableFooter",
|
|
1186
1210
|
"TableHead",
|
|
1187
1211
|
"TableHeader",
|
|
1188
1212
|
"TableRow",
|
|
@@ -14,6 +14,8 @@ export interface SectionCard {
|
|
|
14
14
|
detail?: React$1.ReactNode;
|
|
15
15
|
/** In a picker, a tile that cannot be picked, such as one with nothing in it: faded, and its button disabled. */
|
|
16
16
|
disabled?: boolean;
|
|
17
|
+
/** Marks the tile as the exception on the page: a faint wash of the colour and an outline of it. */
|
|
18
|
+
tone?: "negative" | "warning";
|
|
17
19
|
}
|
|
18
20
|
export interface SectionCardsProps {
|
|
19
21
|
items: SectionCard[];
|
|
@@ -14,15 +14,18 @@ const COLUMNS = {
|
|
|
14
14
|
6: "@3xl/main:grid-cols-3"
|
|
15
15
|
};
|
|
16
16
|
function SectionCards({ items, selected, onSelect }) {
|
|
17
|
-
const
|
|
17
|
+
const surface = useSurface();
|
|
18
|
+
const ruled = surface === "ruled";
|
|
18
19
|
return /* @__PURE__ */ jsx("div", {
|
|
20
|
+
"data-surface": surface,
|
|
19
21
|
className: cn("@container/main", ruled && "overflow-hidden border-y"),
|
|
20
22
|
children: /* @__PURE__ */ jsx("div", {
|
|
21
|
-
className: cn("grid grid-cols-1", ruled ? "-
|
|
23
|
+
className: cn("grid grid-cols-1", ruled ? "-me-px -mb-px bg-card" : surface === "open" ? "gap-4" : "gap-2", COLUMNS[items.length] ?? COLUMNS[4]),
|
|
22
24
|
children: items.map((item) => /* @__PURE__ */ jsx("div", {
|
|
23
25
|
"data-slot": "section-card",
|
|
24
|
-
className: cn("min-w-0", ruled && "border-
|
|
26
|
+
className: cn("min-w-0", ruled && "border-e border-b"),
|
|
25
27
|
children: /* @__PURE__ */ jsxs(Card, {
|
|
28
|
+
"data-tone": item.tone,
|
|
26
29
|
className: cn("@container/card h-full [--card-spacing:--spacing(3)]", onSelect && "relative", item.label === selected && "ring-2 ring-primary!", item.label === selected && ruled && "ring-inset", onSelect && item.disabled && "opacity-60"),
|
|
27
30
|
children: [
|
|
28
31
|
onSelect && /* @__PURE__ */ jsx("button", {
|
|
@@ -34,7 +37,10 @@ function SectionCards({ items, selected, onSelect }) {
|
|
|
34
37
|
className: "absolute inset-0 z-10 rounded-[inherit] outline-none focus-visible:ring-3 focus-visible:ring-ring focus-visible:ring-inset"
|
|
35
38
|
}),
|
|
36
39
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
37
|
-
/* @__PURE__ */ jsx(CardDescription, {
|
|
40
|
+
/* @__PURE__ */ jsx(CardDescription, {
|
|
41
|
+
className: "font-mono text-2xs tracking-wider uppercase",
|
|
42
|
+
children: item.label
|
|
43
|
+
}),
|
|
38
44
|
/* @__PURE__ */ jsx(CardTitle, {
|
|
39
45
|
className: "text-2xl font-semibold tabular-nums",
|
|
40
46
|
children: item.value
|
|
@@ -46,7 +52,7 @@ function SectionCards({ items, selected, onSelect }) {
|
|
|
46
52
|
] }),
|
|
47
53
|
item.trend && /* @__PURE__ */ jsx(CardContent, { children: item.trend }),
|
|
48
54
|
(item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
|
|
49
|
-
className: "flex-col items-start gap-1 text-
|
|
55
|
+
className: "flex-col items-start gap-1 font-mono text-2xs tracking-wider uppercase",
|
|
50
56
|
children: [item.headline && /* @__PURE__ */ jsx("div", {
|
|
51
57
|
className: "flex flex-wrap gap-2 font-medium",
|
|
52
58
|
children: item.headline
|
|
@@ -62,7 +62,7 @@ function DistributionBar({ segments, valueFormatter, showLegend = true, showValu
|
|
|
62
62
|
/* @__PURE__ */ jsx("div", {
|
|
63
63
|
role: "img",
|
|
64
64
|
"aria-label": ariaLabel ?? resolved.map((segment) => `${segment.name} ${figure(segment)}`).join(", "),
|
|
65
|
-
className: "bg-muted relative h-2.5 w-full overflow-hidden
|
|
65
|
+
className: "bg-muted relative h-2.5 w-full overflow-hidden",
|
|
66
66
|
onMouseLeave: onHover ? () => onHover(null) : void 0,
|
|
67
67
|
children: resolved.map((segment) => segment.width > 0 ? /* @__PURE__ */ jsx("div", {
|
|
68
68
|
"data-slot": "distribution-segment",
|
|
@@ -36,12 +36,12 @@ function MetricStrip({ title, description, action, items, footer, className, ...
|
|
|
36
36
|
"data-slot": "metric-strip",
|
|
37
37
|
className: "@container/strip -mx-4 -my-3 overflow-hidden",
|
|
38
38
|
children: /* @__PURE__ */ jsx("div", {
|
|
39
|
-
className: "-
|
|
39
|
+
className: "-me-px -mb-px flex flex-wrap",
|
|
40
40
|
children: items.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
41
41
|
"data-slot": "metric-strip-item",
|
|
42
|
-
className: cn("flex min-w-0 flex-col gap-1.5 border-
|
|
42
|
+
className: cn("flex min-w-0 flex-col gap-1.5 border-e border-b px-4 py-4", columns),
|
|
43
43
|
children: [/* @__PURE__ */ jsx("div", {
|
|
44
|
-
className: "truncate text-
|
|
44
|
+
className: "truncate font-mono text-2xs tracking-wider text-muted-foreground uppercase",
|
|
45
45
|
children: item.label
|
|
46
46
|
}), /* @__PURE__ */ jsxs("div", {
|
|
47
47
|
className: "flex flex-wrap items-end justify-between gap-x-3 gap-y-1",
|
|
@@ -19,10 +19,10 @@ function ChangeBar({ value, max, diverging = false, format, className }) {
|
|
|
19
19
|
className: "font-medium"
|
|
20
20
|
}), /* @__PURE__ */ jsxs("div", {
|
|
21
21
|
"aria-hidden": "true",
|
|
22
|
-
className: "relative h-0.75 w-full
|
|
22
|
+
className: "relative h-0.75 w-full bg-muted",
|
|
23
23
|
children: [/* @__PURE__ */ jsx("div", {
|
|
24
24
|
"data-slot": "change-bar-fill",
|
|
25
|
-
className: "absolute inset-y-0
|
|
25
|
+
className: "absolute inset-y-0 bg-foreground",
|
|
26
26
|
style: {
|
|
27
27
|
[side]: diverging ? "50%" : 0,
|
|
28
28
|
width: `${share * half}%`
|
|
@@ -14,9 +14,9 @@ function DataBar({ value, max, tone = "chart", children, className }) {
|
|
|
14
14
|
className: cn("flex items-center gap-2", className),
|
|
15
15
|
children: [/* @__PURE__ */ jsx("div", {
|
|
16
16
|
"aria-hidden": "true",
|
|
17
|
-
className: "h-1.5 w-16 shrink-0 overflow-hidden
|
|
17
|
+
className: "h-1.5 w-16 shrink-0 overflow-hidden bg-muted",
|
|
18
18
|
children: /* @__PURE__ */ jsx("div", {
|
|
19
|
-
className: cn("h-full
|
|
19
|
+
className: cn("h-full", fill),
|
|
20
20
|
style: { width: `${share * 100}%` }
|
|
21
21
|
})
|
|
22
22
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
//#region src/components/siyahi/page-hero.d.ts
|
|
3
3
|
export interface PageHeroProps {
|
|
4
|
-
/** A short line above the title in the link colour, such as
|
|
4
|
+
/** A short line above the title in the mono face and the link colour, such as the app or a section name. */
|
|
5
5
|
eyebrow?: ReactNode;
|
|
6
|
-
/** The page's h1
|
|
6
|
+
/** The page's h1. */
|
|
7
7
|
title: ReactNode;
|
|
8
|
-
/** What the page is, in a sentence
|
|
8
|
+
/** What the page is, in a sentence, in the muted colour. */
|
|
9
9
|
lead?: ReactNode;
|
|
10
|
-
/**
|
|
10
|
+
/** Scope, sources or what is left out, run on after the lead in the same muted line. */
|
|
11
11
|
detail?: ReactNode;
|
|
12
|
-
/** The one or two places a reader goes next, usually a primary Button and an outline one. */
|
|
12
|
+
/** The one or two places a reader goes next, usually a primary Button and an outline one, at the right. */
|
|
13
13
|
actions?: ReactNode;
|
|
14
|
-
/**
|
|
15
|
-
* A large decorative mark drawn faintly in the accent behind the top right, such as the product's logo as an
|
|
16
|
-
* SVG that paints with currentColor. It is hidden from assistive technology.
|
|
17
|
-
*/
|
|
14
|
+
/** A small mark before the eyebrow, such as the product's logo as an SVG that paints with currentColor. */
|
|
18
15
|
mark?: ReactNode;
|
|
19
16
|
}
|
|
20
17
|
/**
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
18
|
+
* The masthead at the top of a page: a small mark and eyebrow, the title, one muted line saying what the page is,
|
|
19
|
+
* the actions at the right, and a hairline under it all. One per page, above everything else; a section uses
|
|
20
|
+
* PageSection.
|
|
23
21
|
*/
|
|
24
22
|
export declare function PageHero({ eyebrow, title, lead, detail, actions, mark }: PageHeroProps): import("react").JSX.Element;
|
|
25
23
|
//#endregion
|
|
@@ -1,40 +1,42 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
//#region src/components/siyahi/page-hero.tsx
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
4
|
+
* The masthead at the top of a page: a small mark and eyebrow, the title, one muted line saying what the page is,
|
|
5
|
+
* the actions at the right, and a hairline under it all. One per page, above everything else; a section uses
|
|
6
|
+
* PageSection.
|
|
6
7
|
*/
|
|
7
8
|
function PageHero({ eyebrow, title, lead, detail, actions, mark }) {
|
|
8
9
|
return /* @__PURE__ */ jsxs("header", {
|
|
9
10
|
"data-slot": "page-hero",
|
|
10
|
-
className: "
|
|
11
|
-
children: [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
11
|
+
className: "flex flex-wrap items-end justify-between gap-x-6 gap-y-3 border-b pb-4",
|
|
12
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
13
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
14
|
+
children: [
|
|
15
|
+
(mark || eyebrow) && /* @__PURE__ */ jsxs("p", {
|
|
16
|
+
className: "flex items-center gap-1.5 font-mono text-xs text-link",
|
|
17
|
+
children: [mark && /* @__PURE__ */ jsx("span", {
|
|
18
|
+
"aria-hidden": true,
|
|
19
|
+
className: "size-3.5 shrink-0 [&>svg]:size-full",
|
|
20
|
+
children: mark
|
|
21
|
+
}), eyebrow]
|
|
22
|
+
}),
|
|
23
|
+
/* @__PURE__ */ jsx("h1", {
|
|
24
|
+
className: "text-3xl font-semibold tracking-tight text-balance",
|
|
25
|
+
children: title
|
|
26
|
+
}),
|
|
27
|
+
(lead || detail) && /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
className: "text-sm text-pretty text-muted-foreground",
|
|
29
|
+
children: [
|
|
30
|
+
lead,
|
|
31
|
+
lead && detail && " ",
|
|
32
|
+
detail
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
37
|
+
className: "flex flex-wrap gap-2",
|
|
38
|
+
children: actions
|
|
39
|
+
})]
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
//#endregion
|
|
@@ -13,9 +13,11 @@ export interface PanelGridProps {
|
|
|
13
13
|
className?: string;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface, the default, the grid is filled
|
|
17
|
+
* with the card colour and a hairline parts the blocks across and down, with a rule above and below the grid and none
|
|
18
|
+
* on its sides; open leaves whitespace between them, and card an 8px gap between boxes. Each block's bottom and end
|
|
19
|
+
* edges carry its rules and the grid clips the ones on its own edges, so an empty cell at the end of a short last row
|
|
20
|
+
* is a blank stretch of the sheet, ruled off like the rest. The theme numbers each block's card title 01, 02.
|
|
19
21
|
*/
|
|
20
22
|
export declare function PanelGrid({ columns, children, className }: PanelGridProps): import("react").JSX.Element;
|
|
21
23
|
//#endregion
|
|
@@ -10,21 +10,24 @@ const COLUMNS = {
|
|
|
10
10
|
4: "@2xl/panels:grid-cols-2 @5xl/panels:grid-cols-4"
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
* Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface, the default, the grid is filled
|
|
14
|
+
* with the card colour and a hairline parts the blocks across and down, with a rule above and below the grid and none
|
|
15
|
+
* on its sides; open leaves whitespace between them, and card an 8px gap between boxes. Each block's bottom and end
|
|
16
|
+
* edges carry its rules and the grid clips the ones on its own edges, so an empty cell at the end of a short last row
|
|
17
|
+
* is a blank stretch of the sheet, ruled off like the rest. The theme numbers each block's card title 01, 02.
|
|
16
18
|
*/
|
|
17
19
|
function PanelGrid({ columns = 2, children, className }) {
|
|
18
20
|
const surface = useSurface();
|
|
19
21
|
const ruled = surface === "ruled";
|
|
20
22
|
return /* @__PURE__ */ jsx("div", {
|
|
21
23
|
"data-slot": "panel-grid",
|
|
24
|
+
"data-surface": surface,
|
|
22
25
|
className: cn("@container/panels", ruled && "overflow-clip border-y", className),
|
|
23
26
|
children: /* @__PURE__ */ jsx("div", {
|
|
24
|
-
className: cn("grid grid-cols-1", COLUMNS[columns], ruled ? "-
|
|
27
|
+
className: cn("grid grid-cols-1", COLUMNS[columns], ruled ? "-me-px -mb-px bg-card" : surface === "open" ? "gap-x-8 gap-y-6" : "gap-2"),
|
|
25
28
|
children: Children.map(children, (child) => /* @__PURE__ */ jsx("div", {
|
|
26
29
|
"data-slot": "panel",
|
|
27
|
-
className: cn("min-w-0", ruled && "border-
|
|
30
|
+
className: cn("min-w-0", ruled && "border-e border-b"),
|
|
28
31
|
children: child
|
|
29
32
|
}))
|
|
30
33
|
})
|
|
@@ -19,10 +19,10 @@ function RangePosition({ low, high, value, lowLabel = "Low", highLabel = "High",
|
|
|
19
19
|
"aria-valuemax": high,
|
|
20
20
|
"aria-valuenow": value,
|
|
21
21
|
"aria-valuetext": `${write(value)}, ${f.pct(share, 0)} of the way from ${write(low)} to ${write(high)}`,
|
|
22
|
-
className: "relative h-1.5 min-w-12 flex-1
|
|
22
|
+
className: "relative h-1.5 min-w-12 flex-1 bg-muted",
|
|
23
23
|
children: [/* @__PURE__ */ jsx("div", {
|
|
24
24
|
"data-slot": "range-fill",
|
|
25
|
-
className: "absolute inset-y-0 left-0
|
|
25
|
+
className: "absolute inset-y-0 left-0 bg-foreground",
|
|
26
26
|
style: { width: at }
|
|
27
27
|
}), /* @__PURE__ */ jsx("div", {
|
|
28
28
|
"data-slot": "range-thumb",
|
|
@@ -2,16 +2,18 @@ import { SiyahiLocale } from "../../lib/locale.js";
|
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
//#region src/components/siyahi/siyahi-provider.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* How a data block sits on the page. "ruled"
|
|
6
|
-
*
|
|
5
|
+
* How a data block sits on the page. "ruled", the default: blocks filled with the card colour and parted by hairlines,
|
|
6
|
+
* with a rule above and below each group and none on its sides. "card": a filled square card in a hairline ring, 8px
|
|
7
|
+
* from the next, for a page of separate blocks. "open": no fill and no lines, the block's content level with the
|
|
8
|
+
* page's text.
|
|
7
9
|
*/
|
|
8
|
-
export type Surface = "ruled" | "
|
|
10
|
+
export type Surface = "ruled" | "card" | "open";
|
|
9
11
|
export interface SiyahiProviderProps {
|
|
10
12
|
/** Any setting left out keeps its default: en-IN, INR, Asia/Kolkata. */
|
|
11
13
|
locale?: Partial<SiyahiLocale>;
|
|
12
14
|
/**
|
|
13
15
|
* Set it on a nested provider for one part of a page. Left out, a provider keeps the surface around it, so a
|
|
14
|
-
* provider that only changes the locale changes nothing else; the outermost default is "
|
|
16
|
+
* provider that only changes the locale changes nothing else; the outermost default is "ruled".
|
|
15
17
|
*/
|
|
16
18
|
surface?: Surface;
|
|
17
19
|
children: ReactNode;
|
|
@@ -4,7 +4,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
4
4
|
import { createContext, use, useMemo } from "react";
|
|
5
5
|
//#region src/components/siyahi/siyahi-provider.tsx
|
|
6
6
|
const LocaleContext = createContext(defaultLocale);
|
|
7
|
-
const SurfaceContext = createContext("
|
|
7
|
+
const SurfaceContext = createContext("ruled");
|
|
8
8
|
/** Wrap the app once. Carries the locale, the surface and the tooltip provider shadcn asks for. */
|
|
9
9
|
function SiyahiProvider({ locale, surface, children }) {
|
|
10
10
|
const { numberLocale, currency, timeZone } = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siyahi/ui",
|
|
3
|
-
"version": "0.1.9-next.
|
|
3
|
+
"version": "0.1.9-next.29",
|
|
4
4
|
"description": "Stock shadcn/ui on Base UI and Tailwind v4, a finance theme, a few compositions and formatters.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"base-ui",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"@types/node": "24.19.2",
|
|
67
67
|
"@types/react": "~19.3.0",
|
|
68
68
|
"@types/react-dom": "~19.3.0",
|
|
69
|
+
"happy-dom": "^21.0.3",
|
|
69
70
|
"react": "19.3.1",
|
|
70
71
|
"react-dom": "19.3.1",
|
|
71
72
|
"shadcn": "4.22.1",
|
package/report.css
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
:root {
|
|
7
7
|
color-scheme: light dark;
|
|
8
|
-
--radius:
|
|
8
|
+
--radius: 0rem;
|
|
9
9
|
--background: oklch(0.985 0 0);
|
|
10
10
|
--foreground: oklch(0.145 0 0);
|
|
11
11
|
--card: oklch(1 0 0);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
--accent: oklch(0.97 0 0);
|
|
22
22
|
--accent-foreground: oklch(0.205 0 0);
|
|
23
23
|
--destructive: oklch(0.49 0.19 27.325);
|
|
24
|
-
--border: oklch(0.
|
|
24
|
+
--border: oklch(0.145 0 0 / 18%);
|
|
25
25
|
--input: oklch(0.64 0 0);
|
|
26
26
|
--ring: oklch(0.62 0.16 275);
|
|
27
27
|
--positive: oklch(0.527 0.145 150.069);
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
--heat-3: 36%;
|
|
36
36
|
--heat-4: 48%;
|
|
37
37
|
--heat-5: 60%;
|
|
38
|
+
--wash: 7%;
|
|
39
|
+
--wash-edge: 45%;
|
|
38
40
|
--link: oklch(0.51 0.2 275);
|
|
39
41
|
--chart-1: oklch(0.4 0.17 275);
|
|
40
42
|
--chart-2: oklch(0.66 0.13 70);
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
48
50
|
--sidebar-accent: oklch(0.97 0 0);
|
|
49
51
|
--sidebar-accent-foreground: oklch(0.45 0.2 275);
|
|
50
|
-
--sidebar-border: oklch(0.
|
|
52
|
+
--sidebar-border: oklch(0.145 0 0 / 18%);
|
|
51
53
|
--sidebar-ring: oklch(0.62 0.16 275);
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -68,7 +70,7 @@
|
|
|
68
70
|
--accent: oklch(0.269 0 0);
|
|
69
71
|
--accent-foreground: oklch(0.922 0 0);
|
|
70
72
|
--destructive: oklch(0.8 0.1 22.216);
|
|
71
|
-
--border: oklch(1 0 0 /
|
|
73
|
+
--border: oklch(1 0 0 / 20%);
|
|
72
74
|
--input: oklch(0.62 0 0);
|
|
73
75
|
--ring: oklch(0.6 0.14 275);
|
|
74
76
|
--positive: oklch(0.69 0.16 151.711);
|
|
@@ -81,6 +83,8 @@
|
|
|
81
83
|
--heat-2: 33.75%;
|
|
82
84
|
--heat-3: 42.5%;
|
|
83
85
|
--heat-4: 51.25%;
|
|
86
|
+
--wash: 12%;
|
|
87
|
+
--wash-edge: 55%;
|
|
84
88
|
--link: oklch(0.75 0.128 275);
|
|
85
89
|
--chart-1: oklch(0.58 0.2 275);
|
|
86
90
|
--chart-2: oklch(0.88 0.08 75);
|
|
@@ -93,7 +97,7 @@
|
|
|
93
97
|
--sidebar-primary-foreground: oklch(0.205 0 0);
|
|
94
98
|
--sidebar-accent: oklch(0.269 0 0);
|
|
95
99
|
--sidebar-accent-foreground: oklch(0.8 0.1 275);
|
|
96
|
-
--sidebar-border: oklch(1 0 0 /
|
|
100
|
+
--sidebar-border: oklch(1 0 0 / 20%);
|
|
97
101
|
--sidebar-ring: oklch(0.6 0.14 275);
|
|
98
102
|
}
|
|
99
103
|
}
|
package/styles/siyahi.css
CHANGED
|
@@ -34,6 +34,12 @@
|
|
|
34
34
|
* fallback draws and every prose block rewraps when Geist arrives.
|
|
35
35
|
*/
|
|
36
36
|
--max-width-prose: 43.1em;
|
|
37
|
+
/*
|
|
38
|
+
* 11px, for labels over figures: a stat tile's label and footer lines and a
|
|
39
|
+
* table's column heads, set as `font-mono text-2xs tracking-wider uppercase`.
|
|
40
|
+
*/
|
|
41
|
+
--text-2xs: 0.6875rem;
|
|
42
|
+
--text-2xs--line-height: 1rem;
|
|
37
43
|
--color-background: var(--background);
|
|
38
44
|
--color-foreground: var(--foreground);
|
|
39
45
|
--color-card: var(--card);
|
|
@@ -72,6 +78,7 @@
|
|
|
72
78
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
73
79
|
--color-sidebar-border: var(--sidebar-border);
|
|
74
80
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
81
|
+
--radius-xs: calc(var(--radius) * 0.3);
|
|
75
82
|
--radius-sm: calc(var(--radius) * 0.6);
|
|
76
83
|
--radius-md: calc(var(--radius) * 0.8);
|
|
77
84
|
--radius-lg: var(--radius);
|
|
@@ -83,8 +90,8 @@
|
|
|
83
90
|
|
|
84
91
|
:root {
|
|
85
92
|
color-scheme: light dark;
|
|
86
|
-
/*
|
|
87
|
-
--radius:
|
|
93
|
+
/* Square: panels, buttons, fields, badges and toggles alike. Only a dot, an avatar or a slider thumb stays round. */
|
|
94
|
+
--radius: 0rem;
|
|
88
95
|
--background: oklch(0.985 0 0);
|
|
89
96
|
--foreground: oklch(0.145 0 0);
|
|
90
97
|
--card: oklch(1 0 0);
|
|
@@ -102,7 +109,8 @@
|
|
|
102
109
|
--accent-foreground: oklch(0.205 0 0);
|
|
103
110
|
/* Deeper than the loss red, so a destructive button's label reads on its own red tint, hovered too. */
|
|
104
111
|
--destructive: oklch(0.49 0.19 27.325);
|
|
105
|
-
|
|
112
|
+
/* A step firmer than shadcn's 0.922 grey: with square, filled panels the hairlines carry the page's structure. */
|
|
113
|
+
--border: oklch(0.145 0 0 / 18%);
|
|
106
114
|
/* The edge of a field (input, textarea, select, input group) at 3:1: --border stays faint for dividers. */
|
|
107
115
|
--input: oklch(0.64 0 0);
|
|
108
116
|
--ring: oklch(0.62 0.16 275);
|
|
@@ -123,6 +131,12 @@
|
|
|
123
131
|
--heat-3: 36%;
|
|
124
132
|
--heat-4: 48%;
|
|
125
133
|
--heat-5: 60%;
|
|
134
|
+
/*
|
|
135
|
+
* An exception (a stat tile or a table row the page marks with a tone) takes this share of --negative or
|
|
136
|
+
* --warning over the card, and a tile an outline of the tone at --wash-edge.
|
|
137
|
+
*/
|
|
138
|
+
--wash: 7%;
|
|
139
|
+
--wash-edge: 45%;
|
|
126
140
|
--link: oklch(0.51 0.2 275);
|
|
127
141
|
/*
|
|
128
142
|
* Indigo, amber, plum, sky, teal: no green or red, so no series reads as a
|
|
@@ -145,7 +159,7 @@
|
|
|
145
159
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
146
160
|
--sidebar-accent: oklch(0.97 0 0);
|
|
147
161
|
--sidebar-accent-foreground: oklch(0.45 0.2 275);
|
|
148
|
-
--sidebar-border: oklch(0.
|
|
162
|
+
--sidebar-border: oklch(0.145 0 0 / 18%);
|
|
149
163
|
--sidebar-ring: oklch(0.62 0.16 275);
|
|
150
164
|
|
|
151
165
|
@variant dark {
|
|
@@ -175,7 +189,7 @@
|
|
|
175
189
|
--accent-foreground: oklch(0.922 0 0);
|
|
176
190
|
/* Paler and calmer than the loss red, so a destructive label reads on its red tint over the card, hovered too. */
|
|
177
191
|
--destructive: oklch(0.8 0.1 22.216);
|
|
178
|
-
--border: oklch(1 0 0 /
|
|
192
|
+
--border: oklch(1 0 0 / 20%);
|
|
179
193
|
--input: oklch(0.62 0 0);
|
|
180
194
|
--ring: oklch(0.6 0.14 275);
|
|
181
195
|
/*
|
|
@@ -195,6 +209,13 @@
|
|
|
195
209
|
--heat-3: 42.5%;
|
|
196
210
|
--heat-4: 51.25%;
|
|
197
211
|
--heat-5: 60%;
|
|
212
|
+
/*
|
|
213
|
+
* As with heat, a faint share of a dark card reads as the card itself; above
|
|
214
|
+
* 12% the amber wash takes --chart-1, a tile's Sparkline or a row's DataBar,
|
|
215
|
+
* under 3:1.
|
|
216
|
+
*/
|
|
217
|
+
--wash: 12%;
|
|
218
|
+
--wash-edge: 55%;
|
|
198
219
|
--link: oklch(0.75 0.128 275);
|
|
199
220
|
--chart-1: oklch(0.58 0.2 275);
|
|
200
221
|
--chart-2: oklch(0.88 0.08 75);
|
|
@@ -207,7 +228,7 @@
|
|
|
207
228
|
--sidebar-primary-foreground: oklch(0.205 0 0);
|
|
208
229
|
--sidebar-accent: oklch(0.269 0 0);
|
|
209
230
|
--sidebar-accent-foreground: oklch(0.8 0.1 275);
|
|
210
|
-
--sidebar-border: oklch(1 0 0 /
|
|
231
|
+
--sidebar-border: oklch(1 0 0 / 20%);
|
|
211
232
|
--sidebar-ring: oklch(0.6 0.14 275);
|
|
212
233
|
}
|
|
213
234
|
}
|
|
@@ -249,6 +270,31 @@
|
|
|
249
270
|
border-color: var(--foreground);
|
|
250
271
|
}
|
|
251
272
|
|
|
273
|
+
/*
|
|
274
|
+
* A toggle group's items sit flush and share their borders, one control with
|
|
275
|
+
* its parts side by side; stock spaces them 8px apart. A group set to
|
|
276
|
+
* spacing={0} already joins its items by dropping their inner borders. The
|
|
277
|
+
* pressed item rises over its neighbours, so its own edge shows on both sides.
|
|
278
|
+
*/
|
|
279
|
+
[data-slot="toggle-group"]:not([data-spacing="0"]) {
|
|
280
|
+
gap: 0;
|
|
281
|
+
> [data-slot="toggle-group-item"] {
|
|
282
|
+
&[aria-pressed="true"],
|
|
283
|
+
&[data-pressed] {
|
|
284
|
+
z-index: 1;
|
|
285
|
+
}
|
|
286
|
+
&[data-variant="outline"] + [data-slot="toggle-group-item"] {
|
|
287
|
+
margin-inline-start: -1px;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
&[data-orientation="vertical"]
|
|
291
|
+
> [data-slot="toggle-group-item"][data-variant="outline"]
|
|
292
|
+
+ [data-slot="toggle-group-item"] {
|
|
293
|
+
margin-inline-start: 0;
|
|
294
|
+
margin-block-start: -1px;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
252
298
|
/*
|
|
253
299
|
* --input is the edge of a field, where the reader types or picks. A button,
|
|
254
300
|
* a toggle and a code block are framed by their label or their content, so
|
|
@@ -304,6 +350,11 @@
|
|
|
304
350
|
border-color: var(--destructive);
|
|
305
351
|
}
|
|
306
352
|
|
|
353
|
+
/* Progress is a bar, square as every bar is; stock draws it as a pill. */
|
|
354
|
+
[data-slot="progress-track"] {
|
|
355
|
+
border-radius: 0;
|
|
356
|
+
}
|
|
357
|
+
|
|
307
358
|
/*
|
|
308
359
|
* A slider's unfilled track is outlined in the field edge, so its extent reads
|
|
309
360
|
* at 3:1; the muted track alone is 1.05:1 on the canvas.
|
|
@@ -326,14 +377,84 @@
|
|
|
326
377
|
}
|
|
327
378
|
|
|
328
379
|
/*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
380
|
+
* Column heads are labels, in small monospaced capitals; a sort header's
|
|
381
|
+
* button and a data table's column title take the head's type rather than
|
|
382
|
+
* their own. A total row reads as a
|
|
383
|
+
* ledger's: one rule above it and a double rule under it in the text colour,
|
|
384
|
+
* with no band.
|
|
332
385
|
*/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
386
|
+
[data-slot="table-head"] {
|
|
387
|
+
@apply font-mono text-2xs tracking-wider uppercase;
|
|
388
|
+
:is([class~="group/button"], [data-slot="column-title"]) {
|
|
389
|
+
font: inherit;
|
|
390
|
+
letter-spacing: inherit;
|
|
391
|
+
text-transform: inherit;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
[data-slot="table-footer"] {
|
|
395
|
+
background: none;
|
|
396
|
+
border-top: 1px solid var(--foreground);
|
|
397
|
+
border-bottom: 3px double var(--foreground);
|
|
398
|
+
font-weight: 600;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/*
|
|
402
|
+
* An exception the page marks, a stat tile (SectionCard's tone) or a table
|
|
403
|
+
* row (<TableRow data-tone="negative">), takes a faint wash of its colour over
|
|
404
|
+
* the card, and a tile an outline of it, so the one figure needing attention
|
|
405
|
+
* stands out of a run of neutral ones. Unlayered, to outrank a row's hover band.
|
|
406
|
+
*/
|
|
407
|
+
:is([data-slot="card"], [data-slot="table-row"]) {
|
|
408
|
+
&[data-tone="negative"] {
|
|
409
|
+
--exception: var(--negative);
|
|
336
410
|
}
|
|
411
|
+
&[data-tone="warning"] {
|
|
412
|
+
--exception: var(--warning);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
[data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
416
|
+
background-color: color-mix(in oklab, var(--exception) var(--wash), var(--card));
|
|
417
|
+
--tw-ring-color: color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
|
|
418
|
+
}
|
|
419
|
+
[data-slot="table-row"]:is([data-tone="negative"], [data-tone="warning"]) {
|
|
420
|
+
background-color: color-mix(in oklab, var(--exception) var(--wash), transparent);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/*
|
|
424
|
+
* Panels in a PanelGrid are numbered 01, 02 before their title, in the mono
|
|
425
|
+
* face and the link colour, so the text can point to one. A panel without a
|
|
426
|
+
* card title, such as a MetricStrip with none, takes no number; a block
|
|
427
|
+
* wrapped in its own SiyahiProvider (which draws no box) still counts. The panel
|
|
428
|
+
* counts, not the title: a card header is a size container, whose style
|
|
429
|
+
* containment would keep each title's count to itself.
|
|
430
|
+
*/
|
|
431
|
+
[data-slot="panel-grid"] {
|
|
432
|
+
counter-reset: panel;
|
|
433
|
+
}
|
|
434
|
+
[data-slot="panel"]:has(
|
|
435
|
+
> [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"],
|
|
436
|
+
> [data-slot="siyahi-provider"] > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"]
|
|
437
|
+
) {
|
|
438
|
+
counter-increment: panel;
|
|
439
|
+
}
|
|
440
|
+
:is([data-slot="panel"], [data-slot="panel"] > [data-slot="siyahi-provider"])
|
|
441
|
+
> [data-slot="card"]
|
|
442
|
+
> [data-slot="card-header"]
|
|
443
|
+
> [data-slot="card-title"]::before {
|
|
444
|
+
content: counter(panel, decimal-leading-zero);
|
|
445
|
+
@apply font-mono text-2xs font-medium text-link;
|
|
446
|
+
letter-spacing: 0.04em;
|
|
447
|
+
margin-inline-end: 0.7em;
|
|
448
|
+
position: relative;
|
|
449
|
+
top: -0.12em;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/*
|
|
453
|
+
* A data table inside a card drops its own border: the card's ring or the
|
|
454
|
+
* ruled page's hairlines already frame it. On an open page, with no frame,
|
|
455
|
+
* it keeps its border.
|
|
456
|
+
*/
|
|
457
|
+
@scope (:root, [data-surface="ruled"], [data-surface="card"]) to ([data-surface="open"]) {
|
|
337
458
|
[data-slot="card"] [data-slot="table-container"] {
|
|
338
459
|
border-color: transparent;
|
|
339
460
|
}
|
|
@@ -407,34 +528,52 @@
|
|
|
407
528
|
}
|
|
408
529
|
|
|
409
530
|
/*
|
|
410
|
-
* Surfaces, from SiyahiProvider's surface prop.
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
531
|
+
* Surfaces, from SiyahiProvider's surface prop. Ruled, the default, keeps
|
|
532
|
+
* every card's fill and drops its ring: PanelGrid and SectionCards fill their
|
|
533
|
+
* grid with the card colour and draw hairlines between the blocks, with a rule
|
|
534
|
+
* above and below the group and none on its left and right edges, and a card
|
|
535
|
+
* that stands alone, such as a MetricStrip or a table's card, takes those
|
|
536
|
+
* rules above and below itself. Card is a filled square card with a hairline
|
|
537
|
+
* ring and no shadow, 8px from the next, for a page of separate blocks. Open
|
|
538
|
+
* takes the box off every core Card: no fill, no ring, no tinted footer band,
|
|
539
|
+
* and no padding, so a block's content sits level with the page's text. Each
|
|
540
|
+
* scope stops at a provider that says otherwise. Unlayered, like the rules
|
|
541
|
+
* above, to outrank the core components' utility classes; a picked
|
|
542
|
+
* SectionCards tile keeps its ring, which is important, and an exception
|
|
543
|
+
* keeps its outline, which is more specific.
|
|
418
544
|
*/
|
|
419
|
-
|
|
545
|
+
[data-slot="card"] {
|
|
546
|
+
--tw-ring-color: var(--border);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@scope (:root, [data-surface="ruled"]) to ([data-surface="card"], [data-surface="open"]) {
|
|
420
550
|
[data-slot="card"] {
|
|
421
|
-
background: none;
|
|
422
|
-
border-radius: 0;
|
|
423
551
|
--tw-ring-color: transparent;
|
|
424
552
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
553
|
+
/*
|
|
554
|
+
* A card inside a panel or a tile's cell, or nested in another card, is ruled by what holds it; only a holder
|
|
555
|
+
* inside this scope counts, so a ruled provider inside a card starts its own lone cards afresh.
|
|
556
|
+
*/
|
|
557
|
+
[data-slot="card"]:not(
|
|
558
|
+
:scope :is([data-slot="panel"], [data-slot="section-card"], [data-slot="card"]) [data-slot="card"]
|
|
559
|
+
) {
|
|
560
|
+
border-block: 1px solid var(--border);
|
|
430
561
|
}
|
|
431
562
|
}
|
|
432
563
|
|
|
433
564
|
@scope ([data-surface="open"]) to ([data-surface="ruled"], [data-surface="card"]) {
|
|
434
565
|
[data-slot="card"] {
|
|
566
|
+
background: none;
|
|
567
|
+
--tw-ring-color: transparent;
|
|
435
568
|
--card-spacing: 0px;
|
|
436
569
|
gap: calc(var(--spacing) * 4);
|
|
437
570
|
}
|
|
571
|
+
[data-slot="card-footer"] {
|
|
572
|
+
background: none;
|
|
573
|
+
border-top-width: 0;
|
|
574
|
+
/* The band's own top padding stacked on the card's gap, and with no band it read as a hole under the figure. */
|
|
575
|
+
padding-top: 0;
|
|
576
|
+
}
|
|
438
577
|
}
|
|
439
578
|
|
|
440
579
|
/*
|