@siyahi/ui 0.1.9-next.35 → 0.1.9-next.36

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 (32) hide show
  1. package/DESIGN.md +37 -21
  2. package/README.md +7 -9
  3. package/THIRD_PARTY_NOTICES.md +10 -11
  4. package/agents/examples/chart-panels.tsx +0 -4
  5. package/agents/examples/{section-cards-select.tsx → metric-strip-select.tsx} +5 -5
  6. package/agents/examples/metric-strip-trend.tsx +33 -0
  7. package/agents/examples/overview-dashboard.tsx +4 -4
  8. package/agents/examples/scatter-chart.tsx +0 -1
  9. package/agents/examples/stale-blocks.tsx +9 -9
  10. package/agents/examples/surfaces.tsx +2 -3
  11. package/agents/manifest.json +27 -47
  12. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +14 -6
  13. package/dist/components/registry/dashboardcn/kpi-row-card.js +49 -32
  14. package/dist/components/siyahi/panel-grid.d.ts +5 -6
  15. package/dist/components/siyahi/panel-grid.js +10 -9
  16. package/dist/components/siyahi/scatter-chart.d.ts +3 -3
  17. package/dist/components/siyahi/scatter-chart.js +5 -2
  18. package/dist/components/siyahi/siyahi-provider.d.ts +5 -6
  19. package/dist/components/siyahi/siyahi-provider.js +1 -1
  20. package/dist/index.d.ts +2 -3
  21. package/dist/index.js +2 -2
  22. package/dist/lib/chart-frame.d.ts +1 -1
  23. package/dist/lib/chart-frame.js +3 -1
  24. package/dist/lib/chart-series.d.ts +10 -2
  25. package/dist/lib/chart-series.js +10 -0
  26. package/package.json +1 -1
  27. package/registry.lock.json +0 -8
  28. package/styles/siyahi.css +60 -63
  29. package/agents/examples/section-cards-trend.tsx +0 -32
  30. package/agents/examples/section-cards.tsx +0 -48
  31. package/dist/components/registry/dashboard-01/section-cards.d.ts +0 -30
  32. package/dist/components/registry/dashboard-01/section-cards.js +0 -74
package/styles/siyahi.css CHANGED
@@ -175,7 +175,7 @@
175
175
  --chart-1: oklch(0.4 0.17 275);
176
176
  --chart-2: oklch(0.66 0.13 70);
177
177
  --chart-3: oklch(0.38 0.13 335);
178
- --chart-4: oklch(0.65 0.11 235);
178
+ --chart-4: oklch(0.64 0.11 235);
179
179
  --chart-5: oklch(0.36 0.06 205);
180
180
  --sidebar: oklch(0.985 0 0);
181
181
  --sidebar-foreground: oklch(0.145 0 0);
@@ -189,8 +189,8 @@
189
189
  @variant dark {
190
190
  /*
191
191
  * The canvas lifts just off black and text sits a step below white:
192
- * near-white on near-black (19:1) blurs for many readers, and with the
193
- * cards' grey gone on a ruled page the canvas is what text sits on.
192
+ * near-white on near-black (19:1) blurs for many readers, and on an open
193
+ * page, where cards have no fill, the canvas is what text sits on.
194
194
  * Claude Code (#181818, about #e8e8e8 text) and T3 Code (#0a0a0a, about
195
195
  * #bebebe) both narrow that gap; this sits between them, at 14.9:1.
196
196
  * Cards sit one step above the canvas, so data reads as raised, as the
@@ -325,7 +325,7 @@
325
325
 
326
326
  /*
327
327
  * A component that lifts a part of itself with a z-index is a stacking context
328
- * of its own: SectionCards' picker button, an avatar's badge, a calendar's
328
+ * of its own: MetricStrip's picker buttons, an avatar's badge, a calendar's
329
329
  * days, a focused toggle, a code block's buttons, a price chart's layers. The
330
330
  * lifted part then ranks only against the rest of its component, and can never
331
331
  * sit over, or take a tap from, a page's sticky header or section strip (also
@@ -757,13 +757,13 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
757
757
  }
758
758
 
759
759
  /*
760
- * An exception the page marks, a stat tile (SectionCard's tone), a MetricStrip
761
- * figure (its item's tone) or a table row (<TableRow data-tone="negative">, a
762
- * DataTableBody's getRowTone), takes a faint wash of its colour over the card,
763
- * and a tile or figure an outline of it, so the one figure needing attention
764
- * stands out of a run of neutral ones. Unlayered, to outrank a row's hover band.
760
+ * An exception the page marks, a MetricStrip figure (its item's tone) or a
761
+ * table row (<TableRow data-tone="negative">, a DataTableBody's getRowTone),
762
+ * takes a faint wash of its colour over the card, and a figure an outline of
763
+ * it, so the one figure needing attention stands out of a run of neutral
764
+ * ones. Unlayered, to outrank a row's hover band.
765
765
  */
766
- :is([data-slot="card"], [data-slot="metric-strip-item"], [data-slot="table-row"]) {
766
+ :is([data-slot="metric-strip-item"], [data-slot="table-row"]) {
767
767
  &[data-tone="negative"] {
768
768
  --exception: var(--negative);
769
769
  }
@@ -771,24 +771,22 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
771
771
  --exception: var(--warning);
772
772
  }
773
773
  }
774
- :is([data-slot="card"], [data-slot="metric-strip-item"]):is([data-tone="negative"], [data-tone="warning"]) {
775
- background-color: color-mix(in oklab, var(--exception) var(--wash), var(--card));
776
- }
777
- [data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
778
- --tw-ring-color: color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
779
- }
780
774
  /*
781
- * A figure's outline lies on the four hairlines around it, as a tile's ring does: its neighbours' rules before and
782
- * above it, and its own end and bottom rules, which sit outside the padding box the inset counts from.
775
+ * A MetricStrip figure has no padding of its own, so its text lines up with its neighbours'; its wash and outline
776
+ * reach 8px past it, into the gap, as a picked figure's outline does. --figure-wash is the wash the whole box takes.
783
777
  */
784
- [data-slot="metric-strip-item"]:is([data-tone="negative"], [data-tone="warning"]) {
785
- position: relative;
786
- &::after {
787
- content: "";
788
- position: absolute;
789
- inset: -1px;
790
- border: 1px solid color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
791
- pointer-events: none;
778
+ [data-slot="metric-strip-item"] {
779
+ --figure-reach: calc(var(--spacing) * 2);
780
+ &:is([data-tone="negative"], [data-tone="warning"]) {
781
+ --figure-wash: color-mix(in oklab, var(--exception) var(--wash), var(--card));
782
+ background-color: var(--figure-wash);
783
+ box-shadow: 0 0 0 var(--figure-reach) var(--figure-wash);
784
+ outline: 1px solid color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
785
+ outline-offset: var(--figure-reach);
786
+ }
787
+ &[data-picked] {
788
+ outline: 2px solid var(--primary);
789
+ outline-offset: var(--figure-reach);
792
790
  }
793
791
  }
794
792
  /* A pinned cell paints the card under itself (niko-table's lib/styles), so it lays the row's wash over that. */
@@ -830,11 +828,10 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
830
828
  }
831
829
 
832
830
  /*
833
- * A data table inside a card drops its own border: the card's ring or the
834
- * ruled page's hairlines already frame it. On an open page, with no frame,
835
- * it keeps its border.
831
+ * A data table inside a boxed card drops its own border: the card's ring
832
+ * already frames it. On an open page, with no frame, it keeps its border.
836
833
  */
837
- @scope (:root, [data-surface="ruled"], [data-surface="card"]) to ([data-surface="open"]) {
834
+ @scope ([data-surface="card"]) to ([data-surface="open"]) {
838
835
  [data-slot="card"] [data-slot="table-container"] {
839
836
  border-color: transparent;
840
837
  }
@@ -888,21 +885,22 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
888
885
  }
889
886
 
890
887
  /*
891
- * A block the page marks stale (stale on a chart card or a SectionCards tile)
892
- * loses its colour: its chart, sparkline and badge turn grey, and a tile's
893
- * figure and badge turn muted. The chart and sparkline also draw at 65%, so a
888
+ * A block the page marks stale (stale on a chart card or a MetricStrip figure)
889
+ * loses its colour: its chart, sparkline and badge turn grey, and a figure
890
+ * turns muted. The chart and sparkline also draw at 65%, so a
894
891
  * dark slot's line does not read heavier than a fresh one: a stale block is
895
892
  * meant to be harder to read, so its marks are exempt from the 3:1 mark bar.
896
893
  * The small "Stale · 3 Nov" note under the title and the figure keep their
897
894
  * text contrast.
898
895
  */
899
- [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"], [data-slot="badge"]) {
896
+ :is([data-slot="card"], [data-slot="metric-strip-item"])[data-stale]
897
+ :is([data-slot="chart"], [data-slot="sparkline"], [data-slot="badge"], [data-slot="delta"]) {
900
898
  filter: grayscale(1);
901
899
  }
902
- [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"]) {
900
+ :is([data-slot="card"], [data-slot="metric-strip-item"])[data-stale] :is([data-slot="chart"], [data-slot="sparkline"]) {
903
901
  opacity: 0.65;
904
902
  }
905
- [data-slot="section-card"] [data-slot="card"][data-stale] :is([data-slot="card-title"], [data-slot="badge"]) {
903
+ [data-slot="metric-strip-item"][data-stale] [data-slot="metric-strip-value"] {
906
904
  color: var(--muted-foreground);
907
905
  }
908
906
 
@@ -1002,40 +1000,39 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
1002
1000
  }
1003
1001
 
1004
1002
  /*
1005
- * Surfaces, from SiyahiProvider's surface prop. Ruled, the default, keeps
1006
- * every card's fill and drops its ring: PanelGrid and SectionCards fill their
1007
- * grid with the card colour and draw hairlines between the blocks, with a rule
1008
- * above and below the group and none on its left and right edges, and a card
1009
- * that stands alone, such as a MetricStrip or a table's card, takes those
1010
- * rules above and below itself. Card is a filled square card with a hairline
1011
- * ring and no shadow, 8px from the next, for a page of separate blocks. Open
1012
- * takes the box off every core Card: no fill, no ring, no tinted footer band,
1013
- * and no padding, so a block's content sits level with the page's text. Each
1014
- * scope stops at a provider that says otherwise. Unlayered, like the rules
1015
- * above, to outrank the core components' utility classes; a picked
1016
- * SectionCards tile keeps its ring, which is important, and an exception
1017
- * keeps its outline, which is more specific.
1003
+ * Surfaces, from SiyahiProvider's surface prop. Open, the default, takes the
1004
+ * box off every core Card: no fill, no ring, no tinted footer band, and no
1005
+ * padding, so a block's content sits level with the page's text and space,
1006
+ * not a box or a rule, parts it from the next. Card is a filled square card
1007
+ * with a hairline ring and no shadow, 8px from the next, for a page of
1008
+ * separate blocks. Each scope stops at a provider that says otherwise.
1009
+ * Unlayered, like the rules above, to outrank the core components' utility
1010
+ * classes.
1018
1011
  */
1019
- @scope (:root, [data-surface="ruled"]) to ([data-surface="card"], [data-surface="open"]) {
1020
- [data-slot="card"] {
1021
- --tw-ring-color: transparent;
1022
- }
1023
- /*
1024
- * A card inside a panel or a tile's cell, or nested in another card, is ruled by what holds it; only a holder
1025
- * inside this scope counts, so a ruled provider inside a card starts its own lone cards afresh.
1026
- */
1027
- [data-slot="card"]:not(
1028
- :scope :is([data-slot="panel"], [data-slot="section-card"], [data-slot="card"]) [data-slot="card"]
1029
- ) {
1030
- border-block: 1px solid var(--border);
1031
- }
1012
+ /*
1013
+ * An open card renames --card to the page colour for everything inside it, and a custom property inherits past a
1014
+ * scope's end, so a card provider nested in an open card would fill its boxes with the page colour. --card-fill keeps
1015
+ * the theme's card colour, set once at the root, and each card provider takes it back.
1016
+ */
1017
+ :root {
1018
+ --card-fill: var(--card);
1019
+ }
1020
+ [data-surface="card"] {
1021
+ --card: var(--card-fill);
1032
1022
  }
1033
1023
 
1034
- @scope ([data-surface="open"]) to ([data-surface="ruled"], [data-surface="card"]) {
1024
+ @scope (:root, [data-surface="open"]) to ([data-surface="card"]) {
1035
1025
  [data-slot="table-container"] {
1036
1026
  --table-fill: var(--background);
1037
1027
  }
1028
+ /*
1029
+ * With no fill, what lies under a card's marks is the page, so --card names the page colour inside it: a forecast's
1030
+ * halos, a dot's ring, a bar's tint and an exception's wash mix into the canvas, not into a grey no box shows.
1031
+ */
1038
1032
  [data-slot="card"] {
1033
+ --card: var(--background);
1034
+ /* No box, so no edge to clip at: a figure's wash or picker reaching past its text into the gap stays whole. */
1035
+ overflow: visible;
1039
1036
  background: none;
1040
1037
  --tw-ring-color: transparent;
1041
1038
  --card-spacing: 0px;
@@ -1,32 +0,0 @@
1
- import { Delta, SectionCards, Sparkline, useFormat, type SectionCard } from "@siyahi/ui";
2
-
3
- export default function SectionCardsTrendExample() {
4
- const { money, moneyCompact } = useFormat();
5
- const items: SectionCard[] = [
6
- {
7
- label: "Revenue",
8
- value: money(4512300),
9
- badge: <Delta value={0.031} />,
10
- trend: <Sparkline data={[3820000, 3950000, 4110000, 4020000, 4380000, 4512300]} />,
11
- detail: "Month-end, last six months",
12
- },
13
- {
14
- label: "Cash flow this year",
15
- value: moneyCompact(-61200),
16
- badge: <Delta value={-61200} format="money" />,
17
- // A run of losses: the meaning colour, not a chart slot.
18
- trend: <Sparkline data={[0, -8200, -14100, -30400, -52800, -61200]} color="var(--negative)" />,
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",
22
- },
23
- {
24
- label: "Gift cards this year",
25
- value: moneyCompact(38400),
26
- badge: <Delta value={0.18} />,
27
- trend: <Sparkline data={[4100, 0, 12800, 3300, 9900, 8300]} variant="line" curve="linear" />,
28
- detail: "Sold each month",
29
- },
30
- ];
31
- return <SectionCards items={items} />;
32
- }
@@ -1,48 +0,0 @@
1
- import { Delta, SectionCards, type SectionCard, useFormat } from "@siyahi/ui";
2
-
3
- export default function SectionCardsExample() {
4
- const { money, moneyCompact } = useFormat();
5
- const items: SectionCard[] = [
6
- {
7
- label: "Revenue",
8
- value: money(4512300),
9
- badge: <Delta value={0.031} />,
10
- headline: "Up for the third month",
11
- detail: "Paid orders, less refunds",
12
- },
13
- {
14
- label: "Target",
15
- value: money(4025000),
16
- headline: "Set at the start of the quarter",
17
- detail: "Across 4 stores",
18
- },
19
- {
20
- label: "Ahead of target",
21
- value: moneyCompact(487300),
22
- badge: <Delta value={0.121} />,
23
- headline: "Gross margin 14.2%",
24
- detail: "Since April",
25
- },
26
- {
27
- label: "Cash flow this year",
28
- value: moneyCompact(-61200),
29
- badge: <Delta value={-61200} format="money" />,
30
- headline: "Two large inventory buys",
31
- detail: "Before tax",
32
- },
33
- {
34
- label: "Gift cards this year",
35
- value: moneyCompact(38400),
36
- badge: <Delta value={0.18} />,
37
- headline: "Sold in 11 stores",
38
- detail: "Paid at purchase",
39
- },
40
- ];
41
- return (
42
- <div className="flex flex-col gap-6">
43
- <SectionCards items={items.slice(0, 3)} />
44
- <SectionCards items={items.slice(0, 4)} />
45
- <SectionCards items={items} />
46
- </div>
47
- );
48
- }
@@ -1,30 +0,0 @@
1
- import * as React$1 from "react";
2
- //#region src/components/registry/dashboard-01/section-cards.d.ts
3
- export interface SectionCard {
4
- /** Muted label above the value. */
5
- label: string;
6
- value: React$1.ReactNode;
7
- /** Outline badge, top right; usually a <Delta />. */
8
- badge?: React$1.ReactNode;
9
- /** A small chart under the value, usually a <Sparkline /> of the recent run. */
10
- trend?: React$1.ReactNode;
11
- /** Bold first footer line. */
12
- headline?: React$1.ReactNode;
13
- /** Muted second footer line. */
14
- detail?: React$1.ReactNode;
15
- /** In a picker, a tile that cannot be picked, such as one with nothing in it: faded, and its button disabled. */
16
- disabled?: boolean | undefined;
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" | undefined;
19
- /** Marks the tile's data as old, with since when, such as "3 Nov": the tile greys out and says so. */
20
- stale?: React$1.ReactNode;
21
- }
22
- export interface SectionCardsProps {
23
- items: SectionCard[];
24
- /** The label of the picked tile. */
25
- selected?: string | undefined;
26
- /** Makes each tile one button, so keep tile content non-interactive; gets the label of the tile picked. */
27
- onSelect?: ((label: string) => void) | undefined;
28
- }
29
- export declare function SectionCards({ items, selected, onSelect }: SectionCardsProps): React$1.JSX.Element;
30
- //#endregion
@@ -1,74 +0,0 @@
1
- "use client";
2
- import { Badge } from "../../ui/badge.js";
3
- import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
4
- import { cn } from "../../../lib/utils.js";
5
- import { StaleNote } from "../../siyahi/stale-note.js";
6
- import { useSurface } from "../../siyahi/siyahi-provider.js";
7
- import { jsx, jsxs } from "react/jsx-runtime";
8
- import "react";
9
- //#region src/components/registry/dashboard-01/section-cards.tsx
10
- const COLUMNS = {
11
- 2: "@xl/main:grid-cols-2",
12
- 3: "@3xl/main:grid-cols-3",
13
- 4: "@xl/main:grid-cols-2 @5xl/main:grid-cols-4",
14
- 5: "@3xl/main:grid-cols-3 @7xl/main:grid-cols-5",
15
- 6: "@3xl/main:grid-cols-3"
16
- };
17
- function SectionCards({ items, selected, onSelect }) {
18
- const surface = useSurface();
19
- const ruled = surface === "ruled";
20
- return /* @__PURE__ */ jsx("div", {
21
- "data-surface": surface,
22
- className: cn("@container/main", ruled && "overflow-hidden border-y"),
23
- children: /* @__PURE__ */ jsx("div", {
24
- className: cn("grid grid-cols-1", ruled ? "-me-px -mb-px bg-card" : surface === "open" ? "gap-4" : "gap-2", COLUMNS[items.length] ?? COLUMNS[4]),
25
- children: items.map((item) => /* @__PURE__ */ jsx("div", {
26
- "data-slot": "section-card",
27
- className: cn("min-w-0", ruled && "border-e border-b"),
28
- children: /* @__PURE__ */ jsxs(Card, {
29
- "data-tone": item.tone,
30
- "data-stale": item.stale ? "" : void 0,
31
- 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"),
32
- children: [
33
- onSelect && /* @__PURE__ */ jsx("button", {
34
- type: "button",
35
- "aria-label": item.label,
36
- "aria-pressed": item.label === selected,
37
- disabled: item.disabled,
38
- onClick: () => onSelect(item.label),
39
- className: "absolute inset-0 z-10 rounded-[inherit] outline-none focus-visible:ring-3 focus-visible:ring-ring focus-visible:ring-inset"
40
- }),
41
- /* @__PURE__ */ jsxs(CardHeader, { children: [
42
- /* @__PURE__ */ jsx(CardDescription, {
43
- className: "font-mono text-2xs tracking-wider uppercase",
44
- children: item.label
45
- }),
46
- item.stale && /* @__PURE__ */ jsx(StaleNote, { since: item.stale }),
47
- /* @__PURE__ */ jsx(CardTitle, {
48
- className: "text-2xl font-semibold tabular-nums",
49
- children: item.value
50
- }),
51
- item.badge && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, {
52
- variant: "outline",
53
- children: item.badge
54
- }) })
55
- ] }),
56
- item.trend && /* @__PURE__ */ jsx(CardContent, { children: item.trend }),
57
- (item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
58
- className: "flex-col items-start gap-1 font-mono text-2xs tracking-wider uppercase",
59
- children: [item.headline && /* @__PURE__ */ jsx("div", {
60
- className: "flex flex-wrap gap-2 font-medium",
61
- children: item.headline
62
- }), item.detail && /* @__PURE__ */ jsx("div", {
63
- className: "text-muted-foreground",
64
- children: item.detail
65
- })]
66
- })
67
- ]
68
- })
69
- }, item.label))
70
- })
71
- });
72
- }
73
- //#endregion
74
- export { SectionCards };