@siyahi/ui 0.1.9-next.1 → 0.1.9-next.10

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 (67) hide show
  1. package/README.md +48 -8
  2. package/THIRD_PARTY_NOTICES.md +11 -10
  3. package/dist/components/registry/charts/area-chart-card.d.ts +4 -3
  4. package/dist/components/registry/charts/area-chart-card.js +2 -2
  5. package/dist/components/registry/charts/bar-chart-card.d.ts +7 -1
  6. package/dist/components/registry/charts/bar-chart-card.js +35 -14
  7. package/dist/components/registry/charts/line-chart-card.d.ts +5 -2
  8. package/dist/components/registry/charts/line-chart-card.js +2 -2
  9. package/dist/components/registry/dashboard-01/section-cards.d.ts +4 -0
  10. package/dist/components/registry/dashboard-01/section-cards.js +42 -34
  11. package/dist/components/registry/dashboardcn/bar-list.d.ts +3 -1
  12. package/dist/components/registry/dashboardcn/bar-list.js +3 -3
  13. package/dist/components/registry/dashboardcn/distribution-bar.d.ts +38 -0
  14. package/dist/components/registry/dashboardcn/distribution-bar.js +118 -0
  15. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
  16. package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
  17. package/dist/components/registry/dashboardcn/sparkline.d.ts +18 -0
  18. package/dist/components/registry/dashboardcn/sparkline.js +95 -0
  19. package/dist/components/registry/minidev/copy-button.d.ts +11 -0
  20. package/dist/components/registry/minidev/copy-button.js +34 -0
  21. package/dist/components/registry/niko-table/ui/table.js +2 -2
  22. package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -1
  23. package/dist/components/siyahi/data-bar.d.ts +19 -0
  24. package/dist/components/siyahi/data-bar.js +29 -0
  25. package/dist/components/siyahi/panel-grid.d.ts +22 -0
  26. package/dist/components/siyahi/panel-grid.js +34 -0
  27. package/dist/components/siyahi/siyahi-provider.d.ts +13 -2
  28. package/dist/components/siyahi/siyahi-provider.js +17 -4
  29. package/dist/components/ui/accordion.js +5 -5
  30. package/dist/components/ui/alert.js +2 -2
  31. package/dist/components/ui/badge.js +1 -1
  32. package/dist/components/ui/breadcrumb.js +2 -2
  33. package/dist/components/ui/button.js +10 -10
  34. package/dist/components/ui/calendar.js +1 -1
  35. package/dist/components/ui/card.js +4 -4
  36. package/dist/components/ui/chart.js +1 -1
  37. package/dist/components/ui/code-block.js +13 -12
  38. package/dist/components/ui/command.js +7 -6
  39. package/dist/components/ui/context-menu.js +7 -7
  40. package/dist/components/ui/dialog.js +5 -5
  41. package/dist/components/ui/dropdown-menu.js +8 -8
  42. package/dist/components/ui/input-group.js +11 -11
  43. package/dist/components/ui/input.js +1 -1
  44. package/dist/components/ui/popover.js +3 -3
  45. package/dist/components/ui/progress.js +1 -1
  46. package/dist/components/ui/select.js +6 -6
  47. package/dist/components/ui/sheet.js +5 -5
  48. package/dist/components/ui/sidebar.js +17 -18
  49. package/dist/components/ui/skeleton.js +1 -1
  50. package/dist/components/ui/slider.js +2 -2
  51. package/dist/components/ui/table.js +2 -2
  52. package/dist/components/ui/tabs.js +2 -2
  53. package/dist/components/ui/textarea.js +1 -1
  54. package/dist/components/ui/toggle-group.js +2 -2
  55. package/dist/components/ui/toggle.js +4 -4
  56. package/dist/components/ui/tooltip.js +2 -2
  57. package/dist/index.d.ts +12 -7
  58. package/dist/index.js +9 -4
  59. package/dist/lib/chart-frame.d.ts +18 -4
  60. package/dist/lib/chart-frame.js +183 -56
  61. package/dist/lib/chart-series.d.ts +58 -3
  62. package/package.json +1 -1
  63. package/registry.lock.json +32 -0
  64. package/report.css +11 -11
  65. package/styles/siyahi.css +53 -11
  66. package/dist/lib/recharts.d.ts +0 -2
  67. package/dist/lib/recharts.js +0 -1
package/README.md CHANGED
@@ -67,12 +67,13 @@ Every merge to `main` also attaches both tarballs to a GitHub release
67
67
 
68
68
  ## What is in it
69
69
 
70
- - **shadcn/ui components** in the `base-luma` style, installed with the shadcn
71
- CLI and never edited.
70
+ - **shadcn/ui components** in the `base-nova` style, shadcn's flat and compact
71
+ one, installed with the shadcn CLI and never edited.
72
72
  - **Registry add-ons**, copied once with their origin and licence recorded:
73
73
  bar, area and line chart cards from shadcn's chart blocks, a data table on
74
74
  TanStack Table, calendar, weekday and status heatmaps, a ranked
75
- bar list, the `SectionCards` stat tile and the `sidebar-07` app shell.
75
+ bar list, a `Sparkline`, the `MetricStrip` row of figures, the
76
+ `SectionCards` stat tile and the `sidebar-07` app shell.
76
77
  - **Siyahi components**: `Delta` (a signed figure with ▲ ▼), `PriceChart`
77
78
  (TradingView Lightweight Charts in theme colours), `Treemap` (holdings heat
78
79
  map; hover or tap a tile for its figures) with `TreemapLegend` (its colour
@@ -80,9 +81,13 @@ Every merge to `main` also attaches both tarballs to a GitHub release
80
81
  in a tooltip), `ToneProgress` (Progress in a meaning colour), the page parts
81
82
  `PageSection`, `PageSubheading`, `DataList` and `InlineCode`, the data table
82
83
  parts `DataTableSortHeader` (sorts on one click) and `DataTableRowCount`
83
- ("12 of 48"), and `SiyahiProvider`.
84
- - **Cards and badges as installed**: every `Card`, stat tiles included, keeps
85
- luma's `rounded-4xl`, `shadow-md` and faint ring. A category (a sector, a
84
+ ("12 of 48"), `DataBar` (a figure in a table cell with a bar giving its size
85
+ against the column's largest), `PanelGrid` (data blocks side by side, with
86
+ hairlines between them on a ruled page), and `SiyahiProvider`.
87
+ - **Surfaces**: by default a data block has no box. Every `Card` on a ruled
88
+ page, chart cards and stat tiles included, loses its fill, corner and ring,
89
+ and a `PanelGrid` draws hairlines between blocks; see Surfaces below. A
90
+ category (a sector, a
86
91
  broker, a unit's kind) is an outline `Badge`: categories stay neutral, and
87
92
  colour is left to the four meaning colours and the charts. On a narrow card
88
93
  (header under 24rem, so any phone) a header action such as a toggle drops
@@ -104,8 +109,15 @@ Every merge to `main` also attaches both tarballs to a GitHub release
104
109
  | Counts or amounts per period, one series or several | `BarChartCard`; `stacked` when the series add up to a total |
105
110
  | A running level, such as a portfolio's value against money put in | `AreaChartCard` |
106
111
  | Series compared point by point, such as a return against an index | `LineChartCard` |
107
- | Ranked counts or amounts by category (a name, a bar and a figure) | `BarList` |
112
+ | Samples in time with limits and events marked | `LineChartCard` with `xDomain`, `xTicks` and `referenceLines` |
113
+ | Long category names, such as sectors | `BarChartCard` with `horizontal` |
114
+ | A gain above zero and a loss below it | a series with `color` and `negativeColor` |
115
+ | Ranked counts or amounts by category (a name, a bar and a figure) | `BarList`; give two lists one `max` to share a scale |
116
+ | Where a budget sits, a split of a whole, or losses against gains | `DistributionBar` |
108
117
  | Headline figures with their change | `SectionCards`, with a `Delta` in each badge |
118
+ | Many figures of equal weight, each with its change and recent run | `MetricStrip` |
119
+ | A run of values beside a figure, in a tile, a strip or a table cell | `Sparkline`; in a tile, `SectionCards`' `trend` |
120
+ | A table column that should read as a bar chart | `DataBar` in the column's cell |
109
121
  | One instrument's price history | `PriceChart` |
110
122
  | Holdings sized by weight and coloured by change | `Treemap` |
111
123
  | Activity by day, by weekday and hour, or a status over time | `CalendarHeatmap`, `WeekdayHeatmap`, `StatusHeatmap` |
@@ -117,6 +129,9 @@ Every merge to `main` also attaches both tarballs to a GitHub release
117
129
  | One part of a long page: rule, eyebrow, heading, lead, action | `PageSection`, with `PageSubheading` inside |
118
130
  | Label and value pairs, such as a record's fields | `DataList` and `DataListItem` |
119
131
  | A command or file to copy, wrapped or scrolling | `CodeBlock` with `CodeBlockCopyButton` |
132
+ | A labelled copy action, such as "Copy all" for a section | `CopyButton` |
133
+ | Data blocks side by side: charts, tables, a strip of figures | `PanelGrid` |
134
+ | Boxes back, or no lines at all, for a page or a part of one | `SiyahiProvider` with `surface="card"` or `surface="open"` |
120
135
  | A command, file or identifier inside prose | `InlineCode` |
121
136
  | A signed figure (a gain, a loss, a change) | `Delta` |
122
137
  | A time, with the viewer's own clock on hover | `LocalTime` |
@@ -125,7 +140,10 @@ The three chart cards take the rows, the field the x axis reads and the series,
125
140
  and own everything else: the card and its header action (a range or mode
126
141
  toggle), the axes, the dashed horizontal grid, Geist Mono ticks, a tooltip that
127
142
  names the hovered point and gives each series' formatted value, and the legend
128
- below. A page never puts Recharts axes, grids or tooltips together itself.
143
+ below. Props cover the rest: a footer, a placeholder while data loads,
144
+ reference lines, a numeric x axis, a fixed value axis, axis titles, a second
145
+ line under each tick, a picked category, sideways bars and straight segments.
146
+ A page never puts Recharts axes, grids or tooltips together itself.
129
147
 
130
148
  ```tsx
131
149
  <BarChartCard
@@ -158,6 +176,28 @@ loss, a change) goes through `<Delta>`, which adds ▲ or ▼ and the meaning
158
176
  colour. Time zone names come from the runtime's ICU data; a slim Node or
159
177
  browser build may print `GMT+5:30` where a full one prints `IST`.
160
178
 
179
+ ## Surfaces
180
+
181
+ A data block sits on the page in one of three ways, set by `SiyahiProvider`'s
182
+ `surface`:
183
+
184
+ - `"ruled"`, the default: no box. Every `Card` loses its fill, corner and ring,
185
+ and blocks side by side in a `PanelGrid` are parted by hairlines, with a rule
186
+ above and below the grid. One surface, as on a trading screen.
187
+ - `"open"`: no box and no lines. The card's padding goes too, so a block's
188
+ content sits level with the page's text; for pages read top to bottom.
189
+ - `"card"`: shadcn's own Nova card, a filled box with a 1px ring.
190
+
191
+ A component outside any provider is ruled. A nested provider sets the surface
192
+ for one part of a page. A picked `SectionCards` tile keeps its ring on every
193
+ surface.
194
+
195
+ ```tsx
196
+ <SiyahiProvider surface="open">
197
+ <PageSection title="Why XIRR fell">…</PageSection>
198
+ </SiyahiProvider>
199
+ ```
200
+
161
201
  ## Dark mode
162
202
 
163
203
  The colour scheme follows the device (`prefers-color-scheme`). There is no
@@ -8,16 +8,17 @@ file as copied; `packages/ui/registry.lock.json` lists them all and
8
8
 
9
9
  ## Copied into `@siyahi/ui`
10
10
 
11
- | What | Where in the package | Origin | Licence |
12
- | ------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
13
- | shadcn/ui core components (installed with `shadcn add`, never edited) | `src/components/ui/*`, `src/hooks/use-mobile.ts` | https://github.com/shadcn-ui/ui | MIT, Copyright (c) 2023 shadcn |
14
- | shadcn `tailwind.css` (variants and utilities the components use) | `styles/shadcn.css` | https://github.com/shadcn-ui/ui/tree/main/packages/shadcn | MIT, Copyright (c) 2023 shadcn |
15
- | shadcn block `sidebar-07` | `src/components/registry/sidebar-07/*` | https://ui.shadcn.com/r/styles/base-vega/sidebar-07.json | MIT, Copyright (c) 2023 shadcn |
16
- | shadcn block `dashboard-01`, `SectionCards` only | `src/components/registry/dashboard-01/section-cards.tsx` | https://ui.shadcn.com/r/styles/base-vega/dashboard-01.json | MIT, Copyright (c) 2023 shadcn |
17
- | Heatmaps: calendar, weekday by hour, status timeline | `src/components/registry/heatmap/*` | https://shadcn-heatmap.pages.dev/r/calendar-heatmap.json, https://shadcn-heatmap.pages.dev/r/weekday-heatmap.json, https://shadcn-heatmap.pages.dev/r/status-heatmap.json (source: https://github.com/rutopio/shadcn-heatmap) | MIT, Copyright (c) 2026 ChingRu |
18
- | niko-table data table on TanStack Table | `src/components/registry/niko-table/*` | https://niko-table.com/r/data-table.json, https://niko-table.com/r/data-table-core.json, https://niko-table.com/r/data-table-ui.json, https://niko-table.com/r/data-table-faceted-filter.json, https://niko-table.com/r/data-table-column-sort.json, https://niko-table.com/r/data-table-search-filter.json (source: https://github.com/Semkoo/niko-table-registry) | MIT, Copyright (c) 2025 Semir N.; original work Copyright (c) 2025 WebDevSimplified |
19
- | shadcn chart blocks `chart-bar-multiple`, `chart-area-stacked`, `chart-line-multiple` | `src/components/registry/charts/*` | https://ui.shadcn.com/r/styles/new-york-v4/chart-bar-multiple.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-area-stacked.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-line-multiple.json | MIT, Copyright (c) 2023 shadcn |
20
- | dashboardcn ranked list (`BarList`) | `src/components/registry/dashboardcn/bar-list.tsx` | https://dashboardcn.com/r/bar-list.json (source: https://github.com/NoahGdev/dashboardcn) | MIT, Copyright (c) 2026 dashboardcn contributors |
11
+ | What | Where in the package | Origin | Licence |
12
+ | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
13
+ | shadcn/ui core components (installed with `shadcn add`, never edited) | `src/components/ui/*`, `src/hooks/use-mobile.ts` | https://github.com/shadcn-ui/ui | MIT, Copyright (c) 2023 shadcn |
14
+ | shadcn `tailwind.css` (variants and utilities the components use) | `styles/shadcn.css` | https://github.com/shadcn-ui/ui/tree/main/packages/shadcn | MIT, Copyright (c) 2023 shadcn |
15
+ | shadcn block `sidebar-07` | `src/components/registry/sidebar-07/*` | https://ui.shadcn.com/r/styles/base-vega/sidebar-07.json | MIT, Copyright (c) 2023 shadcn |
16
+ | shadcn block `dashboard-01`, `SectionCards` only | `src/components/registry/dashboard-01/section-cards.tsx` | https://ui.shadcn.com/r/styles/base-vega/dashboard-01.json | MIT, Copyright (c) 2023 shadcn |
17
+ | Heatmaps: calendar, weekday by hour, status timeline | `src/components/registry/heatmap/*` | https://shadcn-heatmap.pages.dev/r/calendar-heatmap.json, https://shadcn-heatmap.pages.dev/r/weekday-heatmap.json, https://shadcn-heatmap.pages.dev/r/status-heatmap.json (source: https://github.com/rutopio/shadcn-heatmap) | MIT, Copyright (c) 2026 ChingRu |
18
+ | niko-table data table on TanStack Table | `src/components/registry/niko-table/*` | https://niko-table.com/r/data-table.json, https://niko-table.com/r/data-table-core.json, https://niko-table.com/r/data-table-ui.json, https://niko-table.com/r/data-table-faceted-filter.json, https://niko-table.com/r/data-table-column-sort.json, https://niko-table.com/r/data-table-search-filter.json (source: https://github.com/Semkoo/niko-table-registry) | MIT, Copyright (c) 2025 Semir N.; original work Copyright (c) 2025 WebDevSimplified |
19
+ | shadcn chart blocks `chart-bar-multiple`, `chart-area-stacked`, `chart-line-multiple` | `src/components/registry/charts/*` | https://ui.shadcn.com/r/styles/new-york-v4/chart-bar-multiple.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-area-stacked.json, https://ui.shadcn.com/r/styles/new-york-v4/chart-line-multiple.json | MIT, Copyright (c) 2023 shadcn |
20
+ | dashboardcn ranked list (`BarList`), stacked bar (`DistributionBar`), `Sparkline` and `MetricStrip` | `src/components/registry/dashboardcn/*` | https://dashboardcn.com/r/bar-list.json, https://dashboardcn.com/r/distribution-bar.json, https://dashboardcn.com/r/sparkline.json, https://dashboardcn.com/r/kpi-row-card.json (source: https://github.com/NoahGdev/dashboardcn) | MIT, Copyright (c) 2026 dashboardcn contributors |
21
+ | MiniDev copy button (`CopyButton`) | `src/components/registry/minidev/copy-button.tsx` | https://ui.minidev.pro/r/copy-button.json (source: https://github.com/amankaushik481/minidev-ui) | MIT, Copyright (c) 2026 MiniDev |
21
22
 
22
23
  ## Runtime dependencies with attribution terms
23
24
 
@@ -1,9 +1,10 @@
1
- import { SeriesChartProps } from "../../../lib/chart-series.js";
1
+ import { ChartCurve, NumericXProps, SeriesChartProps } from "../../../lib/chart-series.js";
2
2
  //#region src/components/registry/charts/area-chart-card.d.ts
3
- export interface AreaChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K> {
3
+ export interface AreaChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps {
4
4
  /** Stack the series into a total instead of overlapping them. */
5
5
  stacked?: boolean;
6
+ curve?: ChartCurve;
6
7
  }
7
8
  /** Values over an ordered axis as filled areas: one series, or several overlapping or stacked. */
8
- export declare function AreaChartCard<T extends object, K extends keyof T>({ stacked, ...props }: AreaChartCardProps<T, K>): import("react").JSX.Element;
9
+ export declare function AreaChartCard<T extends object, K extends keyof T>({ stacked, curve, ...props }: AreaChartCardProps<T, K>): import("react").JSX.Element;
9
10
  //#endregion
@@ -4,13 +4,13 @@ import { jsx } from "react/jsx-runtime";
4
4
  import { Area, AreaChart } from "recharts";
5
5
  //#region src/components/registry/charts/area-chart-card.tsx
6
6
  /** Values over an ordered axis as filled areas: one series, or several overlapping or stacked. */
7
- function AreaChartCard({ stacked = false, ...props }) {
7
+ function AreaChartCard({ stacked = false, curve = "monotone", ...props }) {
8
8
  return /* @__PURE__ */ jsx(ChartFrame, {
9
9
  chart: AreaChart,
10
10
  ...props,
11
11
  children: props.series.map(({ key, color }) => /* @__PURE__ */ jsx(Area, {
12
12
  dataKey: key,
13
- type: "monotone",
13
+ type: curve,
14
14
  fill: color,
15
15
  fillOpacity: stacked ? .4 : .2,
16
16
  stroke: color,
@@ -3,7 +3,13 @@ import { SeriesChartProps } from "../../../lib/chart-series.js";
3
3
  export interface BarChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K> {
4
4
  /** Stack the series in one bar per point instead of side by side. */
5
5
  stacked?: boolean;
6
+ /** Bars run sideways, categories down the left: for long names, such as sectors. */
7
+ horizontal?: boolean;
8
+ /** The picked category's x value: its bars stay solid and the rest fade. */
9
+ selected?: T[K] | null;
10
+ /** Called with a clicked category's x value, or null when the picked one is clicked again. */
11
+ onSelect?: (value: T[K] | null) => void;
6
12
  }
7
13
  /** Values over an ordered axis as bars: one series, or several grouped or stacked. */
8
- export declare function BarChartCard<T extends object, K extends keyof T>({ stacked, ...props }: BarChartCardProps<T, K>): import("react").JSX.Element;
14
+ export declare function BarChartCard<T extends object, K extends keyof T>({ stacked, selected, onSelect, ...props }: BarChartCardProps<T, K>): import("react").JSX.Element;
9
15
  //#endregion
@@ -1,24 +1,45 @@
1
1
  "use client";
2
- import { ChartFrame } from "../../../lib/chart-frame.js";
2
+ import { ChartFrame, seriesFill } from "../../../lib/chart-frame.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
- import { Bar, BarChart } from "recharts";
4
+ import { Bar, BarChart, Cell } from "recharts";
5
5
  //#region src/components/registry/charts/bar-chart-card.tsx
6
+ const DOWN_FROM_ZERO = ["auto", 0];
6
7
  /** Values over an ordered axis as bars: one series, or several grouped or stacked. */
7
- function BarChartCard({ stacked = false, ...props }) {
8
+ function BarChartCard({ stacked = false, selected = null, onSelect, ...props }) {
8
9
  return /* @__PURE__ */ jsx(ChartFrame, {
9
10
  chart: BarChart,
10
11
  ...props,
11
- children: props.series.map(({ key, color }, index) => /* @__PURE__ */ jsx(Bar, {
12
- dataKey: key,
13
- fill: color,
14
- stackId: stacked ? "stack" : void 0,
15
- radius: !stacked ? 2 : index === props.series.length - 1 ? [
16
- 2,
17
- 2,
18
- 0,
19
- 0
20
- ] : 0
21
- }, key))
12
+ yDomain: props.yDomain ?? (props.data.some((row) => props.series.some(({ key }) => Number(row[key]) > 0)) ? void 0 : DOWN_FROM_ZERO),
13
+ stackOffset: stacked ? "sign" : void 0,
14
+ children: props.series.map((item, index) => {
15
+ const { key, label, color, negativeColor } = item;
16
+ return /* @__PURE__ */ jsx(Bar, {
17
+ dataKey: key,
18
+ name: label,
19
+ fill: seriesFill(item, props.data),
20
+ stackId: stacked ? "stack" : void 0,
21
+ radius: !stacked ? 2 : index === props.series.length - 1 ? props.horizontal ? [
22
+ 0,
23
+ 2,
24
+ 2,
25
+ 0
26
+ ] : [
27
+ 2,
28
+ 2,
29
+ 0,
30
+ 0
31
+ ] : 0,
32
+ className: onSelect ? "cursor-pointer" : void 0,
33
+ onClick: onSelect && ((bar) => {
34
+ const value = bar.payload?.[props.xKey];
35
+ if (value !== void 0) onSelect(value === selected ? null : value);
36
+ }),
37
+ children: (negativeColor || selected !== null) && props.data.map((row, i) => /* @__PURE__ */ jsx(Cell, {
38
+ fill: negativeColor && Number(row[key]) < 0 ? negativeColor : color,
39
+ fillOpacity: selected === null || row[props.xKey] === selected ? 1 : .35
40
+ }, i))
41
+ }, key);
42
+ })
22
43
  });
23
44
  }
24
45
  //#endregion
@@ -1,5 +1,8 @@
1
- import { SeriesChartProps } from "../../../lib/chart-series.js";
1
+ import { ChartCurve, NumericXProps, SeriesChartProps } from "../../../lib/chart-series.js";
2
2
  //#region src/components/registry/charts/line-chart-card.d.ts
3
+ export interface LineChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps {
4
+ curve?: ChartCurve;
5
+ }
3
6
  /** Values over an ordered axis as lines: one series, or several to compare, such as a portfolio against its index. */
4
- export declare function LineChartCard<T extends object, K extends keyof T>(props: SeriesChartProps<T, K>): import("react").JSX.Element;
7
+ export declare function LineChartCard<T extends object, K extends keyof T>({ curve, ...props }: LineChartCardProps<T, K>): import("react").JSX.Element;
5
8
  //#endregion
@@ -4,13 +4,13 @@ import { jsx } from "react/jsx-runtime";
4
4
  import { Line, LineChart } from "recharts";
5
5
  //#region src/components/registry/charts/line-chart-card.tsx
6
6
  /** Values over an ordered axis as lines: one series, or several to compare, such as a portfolio against its index. */
7
- function LineChartCard(props) {
7
+ function LineChartCard({ curve = "monotone", ...props }) {
8
8
  return /* @__PURE__ */ jsx(ChartFrame, {
9
9
  chart: LineChart,
10
10
  ...props,
11
11
  children: props.series.map(({ key, color }) => /* @__PURE__ */ jsx(Line, {
12
12
  dataKey: key,
13
- type: "monotone",
13
+ type: curve,
14
14
  stroke: color,
15
15
  strokeWidth: 2,
16
16
  dot: false
@@ -6,10 +6,14 @@ export interface SectionCard {
6
6
  value: React$1.ReactNode;
7
7
  /** Outline badge, top right; usually a <Delta />. */
8
8
  badge?: React$1.ReactNode;
9
+ /** A small chart under the value, usually a <Sparkline /> of the recent run. */
10
+ trend?: React$1.ReactNode;
9
11
  /** Bold first footer line. */
10
12
  headline?: React$1.ReactNode;
11
13
  /** Muted second footer line. */
12
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;
13
17
  }
14
18
  export interface SectionCardsProps {
15
19
  items: SectionCard[];
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { Badge } from "../../ui/badge.js";
3
- import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
3
+ import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../ui/card.js";
4
4
  import { cn } from "../../../lib/utils.js";
5
+ import { useSurface } from "../../siyahi/siyahi-provider.js";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  import "react";
7
8
  //#region src/components/registry/dashboard-01/section-cards.tsx
@@ -13,42 +14,49 @@ const COLUMNS = {
13
14
  6: "@3xl/main:grid-cols-3"
14
15
  };
15
16
  function SectionCards({ items, selected, onSelect }) {
17
+ const ruled = useSurface() === "ruled";
16
18
  return /* @__PURE__ */ jsx("div", {
17
- className: "@container/main",
19
+ className: cn("@container/main", ruled && "overflow-hidden border-y"),
18
20
  children: /* @__PURE__ */ jsx("div", {
19
- className: cn("grid grid-cols-1 gap-4", COLUMNS[items.length] ?? COLUMNS[4]),
20
- children: items.map((item) => /* @__PURE__ */ jsxs(Card, {
21
- className: cn("@container/card", onSelect && "relative", item.label === selected && "ring-2 ring-primary!"),
22
- children: [
23
- onSelect && /* @__PURE__ */ jsx("button", {
24
- type: "button",
25
- "aria-label": item.label,
26
- "aria-pressed": item.label === selected,
27
- onClick: () => onSelect(item.label),
28
- className: "absolute inset-0 z-10 rounded-4xl outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset"
29
- }),
30
- /* @__PURE__ */ jsxs(CardHeader, { children: [
31
- /* @__PURE__ */ jsx(CardDescription, { children: item.label }),
32
- /* @__PURE__ */ jsx(CardTitle, {
33
- className: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl",
34
- children: item.value
21
+ className: cn("grid grid-cols-1", ruled ? "-mt-px -ml-px" : "gap-4", COLUMNS[items.length] ?? COLUMNS[4]),
22
+ children: items.map((item) => /* @__PURE__ */ jsx("div", {
23
+ "data-slot": "section-card",
24
+ className: cn("min-w-0", ruled && "border-t border-l"),
25
+ children: /* @__PURE__ */ jsxs(Card, {
26
+ className: cn("@container/card h-full", onSelect && "relative", item.label === selected && "ring-2 ring-primary!", item.label === selected && ruled && "ring-inset", onSelect && item.disabled && "opacity-60"),
27
+ children: [
28
+ onSelect && /* @__PURE__ */ jsx("button", {
29
+ type: "button",
30
+ "aria-label": item.label,
31
+ "aria-pressed": item.label === selected,
32
+ disabled: item.disabled,
33
+ onClick: () => onSelect(item.label),
34
+ className: "absolute inset-0 z-10 rounded-[inherit] outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset"
35
35
  }),
36
- item.badge && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, {
37
- variant: "outline",
38
- children: item.badge
39
- }) })
40
- ] }),
41
- (item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
42
- className: "flex-col items-start gap-1.5 text-sm",
43
- children: [item.headline && /* @__PURE__ */ jsx("div", {
44
- className: "line-clamp-1 flex gap-2 font-medium",
45
- children: item.headline
46
- }), item.detail && /* @__PURE__ */ jsx("div", {
47
- className: "text-muted-foreground",
48
- children: item.detail
49
- })]
50
- })
51
- ]
36
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
37
+ /* @__PURE__ */ jsx(CardDescription, { children: item.label }),
38
+ /* @__PURE__ */ jsx(CardTitle, {
39
+ className: "text-2xl font-semibold tabular-nums @[250px]/card:text-3xl",
40
+ children: item.value
41
+ }),
42
+ item.badge && /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, {
43
+ variant: "outline",
44
+ children: item.badge
45
+ }) })
46
+ ] }),
47
+ item.trend && /* @__PURE__ */ jsx(CardContent, { children: item.trend }),
48
+ (item.headline || item.detail) && /* @__PURE__ */ jsxs(CardFooter, {
49
+ className: "flex-col items-start gap-1.5 text-sm",
50
+ children: [item.headline && /* @__PURE__ */ jsx("div", {
51
+ className: "flex flex-wrap gap-2 font-medium",
52
+ children: item.headline
53
+ }), item.detail && /* @__PURE__ */ jsx("div", {
54
+ className: "text-muted-foreground",
55
+ children: item.detail
56
+ })]
57
+ })
58
+ ]
59
+ })
52
60
  }, item.label))
53
61
  })
54
62
  });
@@ -19,8 +19,10 @@ export interface BarListProps extends React$1.ComponentProps<"div"> {
19
19
  color?: string;
20
20
  /** Show each row's share of the total next to its value. */
21
21
  showPercentage?: boolean;
22
+ /** The value a full-width bar stands for. Give two lists the same max to draw them on one scale. Defaults to the largest value. */
23
+ max?: number;
22
24
  onItemClick?: (item: BarListItem) => void;
23
25
  }
24
- declare function BarList({ data, valueFormatter, sortOrder, color, showPercentage, onItemClick, className, ...props }: BarListProps): React$1.JSX.Element;
26
+ declare function BarList({ data, valueFormatter, sortOrder, color, showPercentage, max: scale, onItemClick, className, ...props }: BarListProps): React$1.JSX.Element;
25
27
  //#endregion
26
28
  export { BarList };
@@ -1,10 +1,10 @@
1
- import { useLocale } from "../../siyahi/siyahi-provider.js";
2
1
  import { cn } from "../../../lib/utils.js";
2
+ import { useLocale } from "../../siyahi/siyahi-provider.js";
3
3
  import { pct } from "../../../lib/format.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import * as React$1 from "react";
6
6
  //#region src/components/registry/dashboardcn/bar-list.tsx
7
- function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(--chart-1)", showPercentage = false, onItemClick, className, ...props }) {
7
+ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(--chart-1)", showPercentage = false, max: scale, onItemClick, className, ...props }) {
8
8
  const locale = useLocale();
9
9
  const format = valueFormatter ?? ((value) => new Intl.NumberFormat(locale.numberLocale, {
10
10
  notation: "compact",
@@ -15,7 +15,7 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
15
15
  const sorted = [...data].sort((a, b) => a.value - b.value);
16
16
  return sortOrder === "descending" ? sorted.reverse() : sorted;
17
17
  }, [data, sortOrder]);
18
- const max = Math.max(...items.map((item) => item.value), 0);
18
+ const max = scale ?? Math.max(...items.map((item) => item.value), 0);
19
19
  const total = items.reduce((sum, item) => sum + item.value, 0);
20
20
  return /* @__PURE__ */ jsx("div", {
21
21
  "data-slot": "bar-list",
@@ -0,0 +1,38 @@
1
+ import { ChartColor } from "../../../lib/chart-series.js";
2
+ import * as React$1 from "react";
3
+ //#region src/components/registry/dashboardcn/distribution-bar.d.ts
4
+ export interface DistributionSegment {
5
+ name: string;
6
+ value: number;
7
+ /** Defaults to chart-1 through chart-5 in order; var(--muted) for space that is free. */
8
+ color?: ChartColor | "var(--muted)";
9
+ /** Stable key when names can repeat. */
10
+ key?: string;
11
+ }
12
+ export interface DistributionMarker {
13
+ /** The value the line marks, on the bar's scale. */
14
+ at: number;
15
+ label?: string;
16
+ }
17
+ export interface DistributionBarProps extends Omit<React$1.ComponentProps<"div">, "onMouseLeave"> {
18
+ segments: DistributionSegment[];
19
+ valueFormatter?: (value: number) => string;
20
+ showLegend?: boolean;
21
+ /** Show absolute values in the legend in addition to the share. */
22
+ showValues?: boolean;
23
+ /** The value the bar's right end stands for. Defaults to the positive segments' total. */
24
+ max?: number;
25
+ /**
26
+ * The value the bar's left end stands for. Defaults to the negative segments' total, or 0 when there are none.
27
+ * Above zero, segments (which start at zero) fall off the bar.
28
+ */
29
+ min?: number;
30
+ markers?: DistributionMarker[];
31
+ /** The key (or name) of the one segment to keep solid; the rest fade. */
32
+ highlighted?: string | null;
33
+ /** Called with the segment under the pointer, and null when it leaves the bar. */
34
+ onHover?: (segment: DistributionSegment | null) => void;
35
+ }
36
+ declare function DistributionBar({ segments, valueFormatter, showLegend, showValues, max, min, markers, highlighted, onHover, className, "aria-label": ariaLabel, ...props }: DistributionBarProps): React$1.JSX.Element;
37
+ //#endregion
38
+ export { DistributionBar };
@@ -0,0 +1,118 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { useLocale } from "../../siyahi/siyahi-provider.js";
3
+ import { pct } from "../../../lib/format.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import "react";
6
+ //#region src/components/registry/dashboardcn/distribution-bar.tsx
7
+ const defaultColors = [
8
+ "var(--chart-1)",
9
+ "var(--chart-2)",
10
+ "var(--chart-3)",
11
+ "var(--chart-4)",
12
+ "var(--chart-5)"
13
+ ];
14
+ function DistributionBar({ segments, valueFormatter, showLegend = true, showValues = false, max, min, markers = [], highlighted = null, onHover, className, "aria-label": ariaLabel, ...props }) {
15
+ const locale = useLocale();
16
+ const format = valueFormatter ?? ((value) => new Intl.NumberFormat(locale.numberLocale, {
17
+ notation: "compact",
18
+ maximumFractionDigits: 1
19
+ }).format(value));
20
+ const total = segments.reduce((sum, segment) => sum + Math.abs(segment.value), 0);
21
+ const gains = segments.reduce((sum, segment) => sum + Math.max(segment.value, 0), 0);
22
+ const losses = segments.reduce((sum, segment) => sum + Math.min(segment.value, 0), 0);
23
+ const bottom = min ?? losses;
24
+ const top = max ?? gains;
25
+ const span = top - bottom || 1;
26
+ const position = (value) => (value - bottom) / span * 100;
27
+ const shown = markers.filter((marker) => marker.at >= bottom && marker.at <= top).map((marker, index) => ({
28
+ ...marker,
29
+ key: `${marker.at}:${marker.label ?? ""}:${index}`
30
+ }));
31
+ let above = 0;
32
+ let below = 0;
33
+ const resolved = segments.map((segment, index) => {
34
+ const key = segment.key ?? segment.name;
35
+ const size = Math.abs(segment.value);
36
+ const start = segment.value < 0 ? below -= size : (above += size) - size;
37
+ return {
38
+ ...segment,
39
+ key,
40
+ color: segment.color ?? defaultColors[index % defaultColors.length],
41
+ share: total > 0 ? size / total : 0,
42
+ left: position(start),
43
+ width: size / span * 100
44
+ };
45
+ });
46
+ const figure = (segment) => max === void 0 ? pct(segment.share, locale) : format(segment.value);
47
+ return /* @__PURE__ */ jsxs("div", {
48
+ "data-slot": "distribution-bar",
49
+ className: cn("flex flex-col gap-3", className),
50
+ ...props,
51
+ children: [/* @__PURE__ */ jsxs("div", {
52
+ className: "relative",
53
+ children: [
54
+ shown.some((m) => m.label) ? /* @__PURE__ */ jsx("div", {
55
+ className: "text-muted-foreground relative h-4 font-mono text-xs",
56
+ children: shown.map((marker) => marker.label ? /* @__PURE__ */ jsx("span", {
57
+ className: cn("absolute top-0 whitespace-nowrap", position(marker.at) <= 5 ? "translate-x-0" : position(marker.at) >= 95 ? "-translate-x-full" : "-translate-x-1/2"),
58
+ style: { left: `${position(marker.at)}%` },
59
+ children: marker.label
60
+ }, marker.key) : null)
61
+ }) : null,
62
+ /* @__PURE__ */ jsx("div", {
63
+ role: "img",
64
+ "aria-label": ariaLabel ?? resolved.map((segment) => `${segment.name} ${figure(segment)}`).join(", "),
65
+ className: "bg-muted relative h-2.5 w-full overflow-hidden rounded-full",
66
+ onMouseLeave: onHover ? () => onHover(null) : void 0,
67
+ children: resolved.map((segment) => segment.width > 0 ? /* @__PURE__ */ jsx("div", {
68
+ "data-slot": "distribution-segment",
69
+ className: "border-card absolute inset-y-0 border-x transition-[width,left,opacity] duration-300",
70
+ style: {
71
+ left: `${segment.left}%`,
72
+ width: `${segment.width}%`,
73
+ backgroundColor: segment.color,
74
+ opacity: highlighted === null || highlighted === segment.key ? 1 : .35
75
+ },
76
+ onMouseEnter: onHover ? () => onHover(segment) : void 0
77
+ }, segment.key) : null)
78
+ }),
79
+ shown.map((marker) => /* @__PURE__ */ jsx("div", {
80
+ "data-slot": "distribution-marker",
81
+ "aria-hidden": "true",
82
+ className: "bg-foreground absolute bottom-0 h-2.5 w-0.5 -translate-x-1/2",
83
+ style: { left: `${position(marker.at)}%` }
84
+ }, marker.key))
85
+ ]
86
+ }), showLegend ? /* @__PURE__ */ jsx("ul", {
87
+ className: "flex flex-wrap gap-x-4 gap-y-1.5 text-sm",
88
+ children: resolved.map((segment) => /* @__PURE__ */ jsxs("li", {
89
+ className: "flex items-center gap-2",
90
+ children: [
91
+ /* @__PURE__ */ jsx("span", {
92
+ "aria-hidden": "true",
93
+ className: "size-2.5 shrink-0 rounded-xs",
94
+ style: { backgroundColor: segment.color }
95
+ }),
96
+ /* @__PURE__ */ jsx("span", {
97
+ className: "text-muted-foreground",
98
+ children: segment.name
99
+ }),
100
+ /* @__PURE__ */ jsx("span", {
101
+ className: "font-medium tabular-nums",
102
+ children: figure(segment)
103
+ }),
104
+ showValues && max === void 0 ? /* @__PURE__ */ jsxs("span", {
105
+ className: "text-muted-foreground tabular-nums",
106
+ children: [
107
+ "(",
108
+ format(segment.value),
109
+ ")"
110
+ ]
111
+ }) : null
112
+ ]
113
+ }, segment.key))
114
+ }) : null]
115
+ });
116
+ }
117
+ //#endregion
118
+ export { DistributionBar };
@@ -0,0 +1,29 @@
1
+ import { Card } from "../../ui/card.js";
2
+ import * as React$1 from "react";
3
+ //#region src/components/registry/dashboardcn/kpi-row-card.d.ts
4
+ export interface MetricStripItem {
5
+ /** Muted label above the figure. */
6
+ label: string;
7
+ /** The figure, formatted by the page: money(4512300). */
8
+ value: React$1.ReactNode;
9
+ /** The signed change, drawn as a Delta under the figure; its sign colours the trend. For "pct", a fraction. */
10
+ change?: number;
11
+ changeFormat?: "pct" | "money";
12
+ /** Muted words after the change, such as "vs last month". */
13
+ note?: React$1.ReactNode;
14
+ /** Recent values, oldest first, drawn as a sparkline beside the figure. */
15
+ trend?: number[];
16
+ }
17
+ export interface MetricStripProps extends Omit<React$1.ComponentProps<typeof Card>, "title"> {
18
+ title?: React$1.ReactNode;
19
+ description?: React$1.ReactNode;
20
+ /** A range toggle, top right, that changes every figure. */
21
+ action?: React$1.ReactNode;
22
+ items: MetricStripItem[];
23
+ /** Lines under the strip, such as what the figures cover. */
24
+ footer?: React$1.ReactNode;
25
+ }
26
+ /** A card holding a row of small figures, each with its change and a sparkline: more numbers in less room than stat tiles. */
27
+ declare function MetricStrip({ title, description, action, items, footer, className, ...props }: MetricStripProps): React$1.JSX.Element;
28
+ //#endregion
29
+ export { MetricStrip };