@siyahi/ui 0.1.8-next.4 → 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 (90) hide show
  1. package/README.md +75 -23
  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 +11 -2
  10. package/dist/components/registry/dashboard-01/section-cards.js +44 -26
  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/components/data-table-search-filter.d.ts +35 -0
  22. package/dist/components/registry/niko-table/components/data-table-search-filter.js +44 -0
  23. package/dist/components/registry/niko-table/filters/table-search-filter.d.ts +38 -0
  24. package/dist/components/registry/niko-table/filters/table-search-filter.js +103 -0
  25. package/dist/components/registry/niko-table/ui/table.js +2 -2
  26. package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -1
  27. package/dist/components/siyahi/data-bar.d.ts +19 -0
  28. package/dist/components/siyahi/data-bar.js +29 -0
  29. package/dist/components/siyahi/data-list.d.ts +14 -0
  30. package/dist/components/siyahi/data-list.js +25 -0
  31. package/dist/components/siyahi/data-table-row-count.d.ts +11 -0
  32. package/dist/components/siyahi/data-table-row-count.js +18 -0
  33. package/dist/components/siyahi/data-table-sort-header.d.ts +15 -0
  34. package/dist/components/siyahi/data-table-sort-header.js +36 -0
  35. package/dist/components/siyahi/inline-code.d.ts +7 -0
  36. package/dist/components/siyahi/inline-code.js +11 -0
  37. package/dist/components/siyahi/page-section.d.ts +21 -0
  38. package/dist/components/siyahi/page-section.js +39 -0
  39. package/dist/components/siyahi/panel-grid.d.ts +22 -0
  40. package/dist/components/siyahi/panel-grid.js +34 -0
  41. package/dist/components/siyahi/siyahi-provider.d.ts +13 -2
  42. package/dist/components/siyahi/siyahi-provider.js +17 -4
  43. package/dist/components/siyahi/tone-progress.d.ts +15 -0
  44. package/dist/components/siyahi/tone-progress.js +18 -0
  45. package/dist/components/siyahi/treemap.d.ts +7 -0
  46. package/dist/components/siyahi/treemap.js +69 -2
  47. package/dist/components/ui/accordion.js +5 -5
  48. package/dist/components/ui/alert.js +2 -2
  49. package/dist/components/ui/badge.js +1 -1
  50. package/dist/components/ui/breadcrumb.js +2 -2
  51. package/dist/components/ui/button.js +10 -10
  52. package/dist/components/ui/calendar.js +1 -1
  53. package/dist/components/ui/card.js +4 -4
  54. package/dist/components/ui/chart.js +1 -1
  55. package/dist/components/ui/code-block.d.ts +28 -0
  56. package/dist/components/ui/code-block.js +123 -0
  57. package/dist/components/ui/command.js +7 -6
  58. package/dist/components/ui/context-menu.js +7 -7
  59. package/dist/components/ui/dialog.js +5 -5
  60. package/dist/components/ui/dropdown-menu.js +8 -8
  61. package/dist/components/ui/input-group.js +11 -11
  62. package/dist/components/ui/input.js +1 -1
  63. package/dist/components/ui/popover.js +3 -3
  64. package/dist/components/ui/progress.js +1 -1
  65. package/dist/components/ui/select.js +6 -6
  66. package/dist/components/ui/sheet.js +5 -5
  67. package/dist/components/ui/sidebar.js +17 -18
  68. package/dist/components/ui/skeleton.js +1 -1
  69. package/dist/components/ui/slider.js +2 -2
  70. package/dist/components/ui/table.js +2 -2
  71. package/dist/components/ui/tabs.js +2 -2
  72. package/dist/components/ui/textarea.js +1 -1
  73. package/dist/components/ui/toggle-group.js +2 -2
  74. package/dist/components/ui/toggle.js +4 -4
  75. package/dist/components/ui/tooltip.js +2 -2
  76. package/dist/index.d.ts +25 -11
  77. package/dist/index.js +20 -6
  78. package/dist/lib/chart-frame.d.ts +18 -4
  79. package/dist/lib/chart-frame.js +183 -56
  80. package/dist/lib/chart-series.d.ts +58 -3
  81. package/dist/lib/format.d.ts +3 -0
  82. package/dist/lib/format.js +6 -1
  83. package/dist/lib/treemap.d.ts +4 -0
  84. package/dist/lib/treemap.js +9 -4
  85. package/package.json +1 -1
  86. package/registry.lock.json +48 -0
  87. package/report.css +11 -11
  88. package/styles/siyahi.css +53 -11
  89. package/dist/lib/recharts.d.ts +0 -2
  90. package/dist/lib/recharts.js +0 -1
@@ -0,0 +1,29 @@
1
+ import { cn } from "cn";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/components/siyahi/data-bar.tsx
4
+ /**
5
+ * A table cell's figure behind a short bar whose length is the figure's share of max, so a column reads as a chart.
6
+ * The bar has a fixed width, so every row draws to the same scale, and sits on the figure's line, so the row
7
+ * keeps its height.
8
+ */
9
+ function DataBar({ value, max, tone = "chart", children, className }) {
10
+ const share = max > 0 ? Math.min(1, Math.abs(value) / max) : 0;
11
+ const fill = tone === "chart" ? "bg-chart-1" : value < 0 ? "bg-negative" : "bg-positive";
12
+ return /* @__PURE__ */ jsxs("div", {
13
+ "data-slot": "data-bar",
14
+ className: cn("flex items-center gap-2", className),
15
+ children: [/* @__PURE__ */ jsx("div", {
16
+ "aria-hidden": "true",
17
+ className: "h-1.5 w-16 shrink-0 overflow-hidden rounded-full bg-muted",
18
+ children: /* @__PURE__ */ jsx("div", {
19
+ className: cn("h-full rounded-full", fill),
20
+ style: { width: `${share * 100}%` }
21
+ })
22
+ }), /* @__PURE__ */ jsx("span", {
23
+ className: "tabular-nums",
24
+ children
25
+ })]
26
+ });
27
+ }
28
+ //#endregion
29
+ export { DataBar };
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/siyahi/data-list.d.ts
3
+ /** Label and value pairs, such as a record's fields in a card. */
4
+ export declare function DataList({ className, children }: {
5
+ className?: string;
6
+ children: ReactNode;
7
+ }): import("react").JSX.Element;
8
+ export interface DataListItemProps {
9
+ /** A short muted label; may hold a badge beside the words. */
10
+ label: ReactNode;
11
+ children: ReactNode;
12
+ }
13
+ export declare function DataListItem({ label, children }: DataListItemProps): import("react").JSX.Element;
14
+ //#endregion
@@ -0,0 +1,25 @@
1
+ import { cn } from "cn";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/components/siyahi/data-list.tsx
4
+ /** Label and value pairs, such as a record's fields in a card. */
5
+ function DataList({ className, children }) {
6
+ return /* @__PURE__ */ jsx("dl", {
7
+ className: cn("flex flex-col gap-4", className),
8
+ children
9
+ });
10
+ }
11
+ function DataListItem({ label, children }) {
12
+ return /* @__PURE__ */ jsxs("div", {
13
+ "data-slot": "data-list-item",
14
+ className: "flex flex-col gap-1",
15
+ children: [/* @__PURE__ */ jsx("dt", {
16
+ className: "flex items-center gap-2 text-xs font-medium text-muted-foreground",
17
+ children: label
18
+ }), /* @__PURE__ */ jsx("dd", {
19
+ className: "min-w-0",
20
+ children
21
+ })]
22
+ });
23
+ }
24
+ //#endregion
25
+ export { DataList, DataListItem };
@@ -0,0 +1,11 @@
1
+ import { RowData } from "@tanstack/react-table";
2
+ //#region src/components/siyahi/data-table-row-count.d.ts
3
+ export interface DataTableRowCountProps {
4
+ /** What a row is, plural: "positions" prints "48 positions" while nothing is filtered. */
5
+ unit: string;
6
+ /** Rows before any filter. Defaults to the table's data; pass it when the page narrowed the data before the table. */
7
+ total?: number;
8
+ }
9
+ /** How many rows the filters leave: "12 of 48", or "48 positions" when they leave all. For a table toolbar, at its end. */
10
+ export declare function DataTableRowCount<TData extends RowData>({ unit, total }: DataTableRowCountProps): import("react").JSX.Element;
11
+ //#endregion
@@ -0,0 +1,18 @@
1
+ import { useFormat } from "../../lib/format.js";
2
+ import { useDataTable } from "../registry/niko-table/core/data-table-context.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/siyahi/data-table-row-count.tsx
5
+ /** How many rows the filters leave: "12 of 48", or "48 positions" when they leave all. For a table toolbar, at its end. */
6
+ function DataTableRowCount({ unit, total }) {
7
+ const { table } = useDataTable();
8
+ const { count } = useFormat();
9
+ const all = total ?? table.getCoreRowModel().rows.length;
10
+ const shown = table.getFilteredRowModel().rows.length;
11
+ return /* @__PURE__ */ jsx("span", {
12
+ "data-slot": "data-table-row-count",
13
+ className: "ml-auto text-sm text-muted-foreground tabular-nums",
14
+ children: shown === all ? `${count(all)} ${unit}` : `${count(shown)} of ${count(all)}`
15
+ });
16
+ }
17
+ //#endregion
18
+ export { DataTableRowCount };
@@ -0,0 +1,15 @@
1
+ import { RowData } from "@tanstack/react-table";
2
+ //#region src/components/siyahi/data-table-sort-header.d.ts
3
+ /**
4
+ * A column header that sorts on one click, largest (or Z) first, as shadcn's
5
+ * data table guide draws it: a ghost button with the direction's arrow. The
6
+ * column menu takes two clicks for the same. Use it as a column's `header`:
7
+ * `header: () => <DataTableSortHeader />`; the title defaults to `meta.label`.
8
+ */
9
+ export declare function DataTableSortHeader<TData extends RowData>({ title }: {
10
+ title?: string;
11
+ }): import("react").JSX.Element;
12
+ export declare namespace DataTableSortHeader {
13
+ var displayName: string;
14
+ }
15
+ //#endregion
@@ -0,0 +1,36 @@
1
+ import { Button } from "../ui/button.js";
2
+ import { DataTableColumnHeader, useColumnHeaderContext } from "../registry/niko-table/components/data-table-column-header.js";
3
+ import { useDerivedColumnTitle } from "../registry/niko-table/hooks/use-derived-column-title.js";
4
+ import { registerComponentFeatures } from "../registry/niko-table/config/feature-detection.js";
5
+ import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon } from "lucide-react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/components/siyahi/data-table-sort-header.tsx
8
+ /**
9
+ * A column header that sorts on one click, largest (or Z) first, as shadcn's
10
+ * data table guide draws it: a ghost button with the direction's arrow. The
11
+ * column menu takes two clicks for the same. Use it as a column's `header`:
12
+ * `header: () => <DataTableSortHeader />`; the title defaults to `meta.label`.
13
+ */
14
+ function DataTableSortHeader({ title }) {
15
+ const { column } = useColumnHeaderContext(true);
16
+ const label = useDerivedColumnTitle(column, column.id, title);
17
+ const sorted = column.getIsSorted();
18
+ return /* @__PURE__ */ jsx(DataTableColumnHeader, { children: /* @__PURE__ */ jsxs(Button, {
19
+ variant: "ghost",
20
+ size: "sm",
21
+ className: "-ml-2",
22
+ onClick: () => column.toggleSorting(sorted !== "desc"),
23
+ children: [
24
+ label,
25
+ /* @__PURE__ */ jsx(sorted === "asc" ? ArrowUpIcon : sorted === "desc" ? ArrowDownIcon : ArrowUpDownIcon, { "aria-hidden": "true" }),
26
+ sorted && /* @__PURE__ */ jsxs("span", {
27
+ className: "sr-only",
28
+ children: [", sorted ", sorted === "asc" ? "ascending" : "descending"]
29
+ })
30
+ ]
31
+ }) });
32
+ }
33
+ DataTableSortHeader.displayName = "DataTableSortHeader";
34
+ registerComponentFeatures(DataTableSortHeader.displayName, { enableSorting: true });
35
+ //#endregion
36
+ export { DataTableSortHeader };
@@ -0,0 +1,7 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/siyahi/inline-code.d.ts
3
+ /** A command, file name or identifier inside prose, in Geist Mono on a muted chip, at the size of the text around it. */
4
+ export declare function InlineCode({ children }: {
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ //#region src/components/siyahi/inline-code.tsx
3
+ /** A command, file name or identifier inside prose, in Geist Mono on a muted chip, at the size of the text around it. */
4
+ function InlineCode({ children }) {
5
+ return /* @__PURE__ */ jsx("code", {
6
+ className: "rounded-sm bg-muted px-1 font-mono",
7
+ children
8
+ });
9
+ }
10
+ //#endregion
11
+ export { InlineCode };
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/siyahi/page-section.d.ts
3
+ export interface PageSectionProps {
4
+ /** Anchor for a table of contents; the section scrolls in with room above it. */
5
+ id?: string;
6
+ /** Small muted line above the title, such as "Part 2". */
7
+ eyebrow?: ReactNode;
8
+ title: ReactNode;
9
+ /** Muted prose under the title: a sentence, or several paragraphs as elements. */
10
+ lead?: ReactNode;
11
+ /** A control for the whole section, such as a copy or mode toggle; it sits right, beside the heading. */
12
+ action?: ReactNode;
13
+ children?: ReactNode;
14
+ }
15
+ /** One part of a long page: a rule above, then the heading and lead on the canvas, then its content. */
16
+ export declare function PageSection({ id, eyebrow, title, lead, action, children }: PageSectionProps): import("react").JSX.Element;
17
+ /** A heading inside a PageSection, one level under its title. */
18
+ export declare function PageSubheading({ children }: {
19
+ children: ReactNode;
20
+ }): import("react").JSX.Element;
21
+ //#endregion
@@ -0,0 +1,39 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/components/siyahi/page-section.tsx
3
+ /** One part of a long page: a rule above, then the heading and lead on the canvas, then its content. */
4
+ function PageSection({ id, eyebrow, title, lead, action, children }) {
5
+ return /* @__PURE__ */ jsxs("section", {
6
+ id,
7
+ "data-slot": "page-section",
8
+ className: "flex scroll-mt-6 flex-col gap-6 border-t pt-10",
9
+ children: [/* @__PURE__ */ jsxs("div", {
10
+ className: "flex flex-wrap items-end justify-between gap-4",
11
+ children: [/* @__PURE__ */ jsxs("div", {
12
+ className: "flex flex-col gap-2",
13
+ children: [
14
+ eyebrow && /* @__PURE__ */ jsx("p", {
15
+ className: "text-sm text-muted-foreground",
16
+ children: eyebrow
17
+ }),
18
+ /* @__PURE__ */ jsx("h2", {
19
+ className: "text-2xl font-semibold tracking-tight",
20
+ children: title
21
+ }),
22
+ lead && /* @__PURE__ */ jsx("div", {
23
+ className: "flex max-w-prose flex-col gap-2 text-muted-foreground",
24
+ children: lead
25
+ })
26
+ ]
27
+ }), action]
28
+ }), children]
29
+ });
30
+ }
31
+ /** A heading inside a PageSection, one level under its title. */
32
+ function PageSubheading({ children }) {
33
+ return /* @__PURE__ */ jsx("h3", {
34
+ className: "text-lg font-semibold tracking-tight",
35
+ children
36
+ });
37
+ }
38
+ //#endregion
39
+ export { PageSection, PageSubheading };
@@ -0,0 +1,22 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/siyahi/panel-grid.d.ts
3
+ declare const COLUMNS: {
4
+ readonly 1: "";
5
+ readonly 2: "@2xl/panels:grid-cols-2";
6
+ readonly 3: "@2xl/panels:grid-cols-2 @5xl/panels:grid-cols-3";
7
+ readonly 4: "@2xl/panels:grid-cols-2 @5xl/panels:grid-cols-4";
8
+ };
9
+ export interface PanelGridProps {
10
+ /** Blocks across on a wide page; two on a medium one, and one on a phone. */
11
+ columns?: keyof typeof COLUMNS;
12
+ children: ReactNode;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface a hairline parts them across and
17
+ * down, with a rule above and below the grid; open leaves whitespace between them, and card a gap between boxes.
18
+ * Each block's left and top edges carry its rules, and the grid clips the ones on its own edges.
19
+ */
20
+ export declare function PanelGrid({ columns, children, className }: PanelGridProps): import("react").JSX.Element;
21
+ //#endregion
22
+ export {};
@@ -0,0 +1,34 @@
1
+ import { useSurface } from "./siyahi-provider.js";
2
+ import { cn } from "cn";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { Children } from "react";
5
+ //#region src/components/siyahi/panel-grid.tsx
6
+ const COLUMNS = {
7
+ 1: "",
8
+ 2: "@2xl/panels:grid-cols-2",
9
+ 3: "@2xl/panels:grid-cols-2 @5xl/panels:grid-cols-3",
10
+ 4: "@2xl/panels:grid-cols-2 @5xl/panels:grid-cols-4"
11
+ };
12
+ /**
13
+ * Data blocks side by side: chart cards, tables, a MetricStrip. On a ruled surface a hairline parts them across and
14
+ * down, with a rule above and below the grid; open leaves whitespace between them, and card a gap between boxes.
15
+ * Each block's left and top edges carry its rules, and the grid clips the ones on its own edges.
16
+ */
17
+ function PanelGrid({ columns = 2, children, className }) {
18
+ const surface = useSurface();
19
+ const ruled = surface === "ruled";
20
+ return /* @__PURE__ */ jsx("div", {
21
+ "data-slot": "panel-grid",
22
+ className: cn("@container/panels", ruled && "overflow-hidden border-y", className),
23
+ children: /* @__PURE__ */ jsx("div", {
24
+ className: cn("grid grid-cols-1", COLUMNS[columns], ruled ? "-mt-px -ml-px" : surface === "open" ? "gap-x-10 gap-y-8" : "gap-4"),
25
+ children: Children.map(children, (child) => /* @__PURE__ */ jsx("div", {
26
+ "data-slot": "panel",
27
+ className: cn("min-w-0", ruled && "border-t border-l"),
28
+ children: child
29
+ }))
30
+ })
31
+ });
32
+ }
33
+ //#endregion
34
+ export { PanelGrid };
@@ -1,12 +1,23 @@
1
1
  import { SiyahiLocale } from "../../lib/locale.js";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/components/siyahi/siyahi-provider.d.ts
4
+ /**
5
+ * How a data block sits on the page. "ruled": no box, hairlines between blocks in a PanelGrid. "open": no box and no
6
+ * lines, the block's content level with the page's text. "card": shadcn's own card, a filled box with a 1px ring.
7
+ */
8
+ export type Surface = "ruled" | "open" | "card";
4
9
  export interface SiyahiProviderProps {
5
10
  /** Any setting left out keeps its default: en-IN, INR, Asia/Kolkata. */
6
11
  locale?: Partial<SiyahiLocale>;
12
+ /**
13
+ * 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 "ruled".
15
+ */
16
+ surface?: Surface;
7
17
  children: ReactNode;
8
18
  }
9
- /** Wrap the app once. Carries the locale and the tooltip provider shadcn asks for. */
10
- export declare function SiyahiProvider({ locale, children }: SiyahiProviderProps): import("react").JSX.Element;
19
+ /** Wrap the app once. Carries the locale, the surface and the tooltip provider shadcn asks for. */
20
+ export declare function SiyahiProvider({ locale, surface, children }: SiyahiProviderProps): import("react").JSX.Element;
11
21
  export declare function useLocale(): SiyahiLocale;
22
+ export declare function useSurface(): Surface;
12
23
  //#endregion
@@ -4,8 +4,9 @@ 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
- /** Wrap the app once. Carries the locale and the tooltip provider shadcn asks for. */
8
- function SiyahiProvider({ locale, children }) {
7
+ const SurfaceContext = createContext("ruled");
8
+ /** Wrap the app once. Carries the locale, the surface and the tooltip provider shadcn asks for. */
9
+ function SiyahiProvider({ locale, surface, children }) {
9
10
  const { numberLocale, currency, timeZone } = {
10
11
  ...defaultLocale,
11
12
  ...locale
@@ -19,13 +20,25 @@ function SiyahiProvider({ locale, children }) {
19
20
  currency,
20
21
  timeZone
21
22
  ]);
23
+ const around = use(SurfaceContext);
22
24
  return /* @__PURE__ */ jsx(LocaleContext, {
23
25
  value,
24
- children: /* @__PURE__ */ jsx(TooltipProvider, { children })
26
+ children: /* @__PURE__ */ jsx(SurfaceContext, {
27
+ value: surface ?? around,
28
+ children: /* @__PURE__ */ jsx(TooltipProvider, { children: surface ? /* @__PURE__ */ jsx("div", {
29
+ "data-slot": "siyahi-provider",
30
+ "data-surface": surface,
31
+ className: "contents",
32
+ children
33
+ }) : children })
34
+ })
25
35
  });
26
36
  }
27
37
  function useLocale() {
28
38
  return use(LocaleContext);
29
39
  }
40
+ function useSurface() {
41
+ return use(SurfaceContext);
42
+ }
30
43
  //#endregion
31
- export { SiyahiProvider, useLocale };
44
+ export { SiyahiProvider, useLocale, useSurface };
@@ -0,0 +1,15 @@
1
+ import { Progress } from "../ui/progress.js";
2
+ import { ComponentProps } from "react";
3
+ //#region src/components/siyahi/tone-progress.d.ts
4
+ declare const TONE: {
5
+ readonly positive: "**:data-[slot=progress-indicator]:bg-positive";
6
+ readonly negative: "**:data-[slot=progress-indicator]:bg-negative";
7
+ };
8
+ export interface ToneProgressProps extends ComponentProps<typeof Progress> {
9
+ /** The meaning colour of the bar: a gain's share in --positive, a loss's in --negative. */
10
+ tone: keyof typeof TONE;
11
+ }
12
+ /** shadcn's Progress with its bar in a meaning colour, such as each position's share of the booked P&L. */
13
+ export declare function ToneProgress({ tone, className, ...props }: ToneProgressProps): import("react").JSX.Element;
14
+ //#endregion
15
+ export {};
@@ -0,0 +1,18 @@
1
+ import { Progress } from "../ui/progress.js";
2
+ import { cn } from "cn";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/siyahi/tone-progress.tsx
5
+ const TONE = {
6
+ positive: "**:data-[slot=progress-indicator]:bg-positive",
7
+ negative: "**:data-[slot=progress-indicator]:bg-negative"
8
+ };
9
+ /** shadcn's Progress with its bar in a meaning colour, such as each position's share of the booked P&L. */
10
+ function ToneProgress({ tone, className, ...props }) {
11
+ return /* @__PURE__ */ jsx(Progress, {
12
+ "data-tone": tone,
13
+ className: cn(TONE[tone], className),
14
+ ...props
15
+ });
16
+ }
17
+ //#endregion
18
+ export { ToneProgress };
@@ -28,4 +28,11 @@ export interface TreemapProps<T extends TreemapItem> {
28
28
  }
29
29
  /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
30
30
  export declare function Treemap<T extends TreemapItem>({ items, height, range, onHover, selected, onSelect }: TreemapProps<T>): import("react").JSX.Element;
31
+ export interface TreemapLegendProps {
32
+ /** The Treemap's own range, so each swatch matches its tiles. */
33
+ range?: number;
34
+ className?: string;
35
+ }
36
+ /** The Treemap's colour scale under the map: five steps of loss, no change, five steps of gain, deepest at the ends. */
37
+ export declare function TreemapLegend({ range, className }: TreemapLegendProps): import("react").JSX.Element;
31
38
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { useFormat } from "../../lib/format.js";
2
- import { heat, layout } from "../../lib/treemap.js";
2
+ import { heat, heatFill, heatRange, layout } from "../../lib/treemap.js";
3
+ import { cn } from "cn";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
5
6
  //#region src/components/siyahi/treemap.tsx
@@ -96,5 +97,71 @@ function Treemap({ items, height, range, onHover, selected, onSelect }) {
96
97
  })
97
98
  });
98
99
  }
100
+ function Swatch({ label, fill }) {
101
+ return /* @__PURE__ */ jsxs("svg", {
102
+ "data-slot": "treemap-legend-swatch",
103
+ className: "h-3 flex-1",
104
+ children: [/* @__PURE__ */ jsx("title", { children: label }), /* @__PURE__ */ jsx("rect", {
105
+ width: "100%",
106
+ height: "100%",
107
+ rx: 2,
108
+ style: { fill }
109
+ })]
110
+ });
111
+ }
112
+ /** The Treemap's colour scale under the map: five steps of loss, no change, five steps of gain, deepest at the ends. */
113
+ function TreemapLegend({ range, className }) {
114
+ const { pct } = useFormat();
115
+ const step = heatRange(range) / 5;
116
+ const digits = Number.isInteger(Math.round(step * 1e6) / 1e4) ? 0 : 1;
117
+ const over = pct(step * 4, digits);
118
+ const swatch = (side, n) => {
119
+ const word = side === "positive" ? "up" : "down";
120
+ const label = n === 5 ? `${word} over ${over}` : `${word} ${pct(step * (n - 1), digits)}–${pct(step * n, digits)}`;
121
+ return /* @__PURE__ */ jsx(Swatch, {
122
+ label,
123
+ fill: heatFill(side, n)
124
+ }, `${side}-${n}`);
125
+ };
126
+ const steps = Array.from({ length: 5 }, (_, i) => i + 1);
127
+ return /* @__PURE__ */ jsxs("div", {
128
+ "data-slot": "treemap-legend",
129
+ className: cn("flex w-full max-w-xs flex-col gap-1 text-xs text-muted-foreground", className),
130
+ children: [
131
+ /* @__PURE__ */ jsxs("div", {
132
+ className: "flex gap-0.5",
133
+ "aria-hidden": "true",
134
+ children: [
135
+ steps.toReversed().map((n) => swatch("negative", n)),
136
+ /* @__PURE__ */ jsx(Swatch, {
137
+ label: "no change, or unknown",
138
+ fill: heat(null)
139
+ }),
140
+ steps.map((n) => swatch("positive", n))
141
+ ]
142
+ }),
143
+ /* @__PURE__ */ jsxs("div", {
144
+ className: "flex justify-between gap-2 tabular-nums",
145
+ children: [
146
+ /* @__PURE__ */ jsxs("span", { children: ["▼ over ", over] }),
147
+ /* @__PURE__ */ jsxs("span", { children: [pct(step, digits), " a step"] }),
148
+ /* @__PURE__ */ jsxs("span", { children: ["▲ over ", over] })
149
+ ]
150
+ }),
151
+ /* @__PURE__ */ jsxs("p", {
152
+ className: "sr-only",
153
+ children: [
154
+ "Tile colours run in ",
155
+ 5,
156
+ " steps of ",
157
+ pct(step, digits),
158
+ " each way, deepest over ",
159
+ over,
160
+ "; a muted tile is no change or unknown."
161
+ ]
162
+ })
163
+ ]
164
+ });
165
+ }
99
166
  //#endregion
100
- export { Treemap };
167
+ export { Treemap, TreemapLegend };
@@ -6,14 +6,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  function Accordion({ className, ...props }) {
7
7
  return /* @__PURE__ */ jsx(Accordion$1.Root, {
8
8
  "data-slot": "accordion",
9
- className: cn("flex w-full flex-col overflow-hidden rounded-2xl border", className),
9
+ className: cn("flex w-full flex-col", className),
10
10
  ...props
11
11
  });
12
12
  }
13
13
  function AccordionItem({ className, ...props }) {
14
14
  return /* @__PURE__ */ jsx(Accordion$1.Item, {
15
15
  "data-slot": "accordion-item",
16
- className: cn("not-last:border-b data-open:bg-muted/50", className),
16
+ className: cn("not-last:border-b", className),
17
17
  ...props
18
18
  });
19
19
  }
@@ -22,7 +22,7 @@ function AccordionTrigger({ className, children, ...props }) {
22
22
  className: "flex",
23
23
  children: /* @__PURE__ */ jsxs(Accordion$1.Trigger, {
24
24
  "data-slot": "accordion-trigger",
25
- className: cn("group/accordion-trigger relative flex flex-1 items-start justify-between gap-6 border border-transparent p-4 text-left text-sm font-medium transition-all outline-none hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground", className),
25
+ className: cn("group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground", className),
26
26
  ...props,
27
27
  children: [
28
28
  children,
@@ -41,10 +41,10 @@ function AccordionTrigger({ className, children, ...props }) {
41
41
  function AccordionContent({ className, children, ...props }) {
42
42
  return /* @__PURE__ */ jsx(Accordion$1.Panel, {
43
43
  "data-slot": "accordion-content",
44
- className: "overflow-hidden px-4 text-sm data-open:animate-accordion-down data-closed:animate-accordion-up",
44
+ className: "overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up",
45
45
  ...props,
46
46
  children: /* @__PURE__ */ jsx("div", {
47
- className: cn("h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", className),
47
+ className: cn("h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", className),
48
48
  children
49
49
  })
50
50
  });
@@ -3,7 +3,7 @@ import { jsx } from "react/jsx-runtime";
3
3
  import "react";
4
4
  import { cva } from "class-variance-authority";
5
5
  //#region src/components/ui/alert.tsx
6
- const alertVariants = cva("group/alert relative grid w-full gap-0.5 rounded-2xl border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", {
6
+ const alertVariants = cva("group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", {
7
7
  variants: { variant: {
8
8
  default: "bg-card text-card-foreground",
9
9
  destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
@@ -35,7 +35,7 @@ function AlertDescription({ className, ...props }) {
35
35
  function AlertAction({ className, ...props }) {
36
36
  return /* @__PURE__ */ jsx("div", {
37
37
  "data-slot": "alert-action",
38
- className: cn("absolute top-2.5 right-3", className),
38
+ className: cn("absolute top-2 right-2", className),
39
39
  ...props
40
40
  });
41
41
  }
@@ -3,7 +3,7 @@ import { cva } from "class-variance-authority";
3
3
  import { mergeProps } from "@base-ui/react/merge-props";
4
4
  import { useRender } from "@base-ui/react/use-render";
5
5
  //#region src/components/ui/badge.tsx
6
- const badgeVariants = cva("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-3xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
6
+ const badgeVariants = cva("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
7
7
  variants: { variant: {
8
8
  default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
9
9
  secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
@@ -16,14 +16,14 @@ function Breadcrumb({ className, ...props }) {
16
16
  function BreadcrumbList({ className, ...props }) {
17
17
  return /* @__PURE__ */ jsx("ol", {
18
18
  "data-slot": "breadcrumb-list",
19
- className: cn("flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground sm:gap-2.5", className),
19
+ className: cn("flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground", className),
20
20
  ...props
21
21
  });
22
22
  }
23
23
  function BreadcrumbItem({ className, ...props }) {
24
24
  return /* @__PURE__ */ jsx("li", {
25
25
  "data-slot": "breadcrumb-item",
26
- className: cn("inline-flex items-center gap-1.5", className),
26
+ className: cn("inline-flex items-center gap-1", className),
27
27
  ...props
28
28
  });
29
29
  }
@@ -3,25 +3,25 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { Button as Button$1 } from "@base-ui/react/button";
5
5
  //#region src/components/ui/button.tsx
6
- const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
6
+ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
7
7
  variants: {
8
8
  variant: {
9
9
  default: "bg-primary text-primary-foreground hover:bg-primary/80",
10
- outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-transparent dark:hover:bg-input/30",
10
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
11
11
  secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
12
12
  ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
13
13
  destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
14
14
  link: "text-primary underline-offset-4 hover:underline"
15
15
  },
16
16
  size: {
17
- default: "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
18
- xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
19
- sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
20
- lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
21
- icon: "size-9",
22
- "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
23
- "icon-sm": "size-8",
24
- "icon-lg": "size-10"
17
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
18
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
19
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
20
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
21
+ icon: "size-8",
22
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
23
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
24
+ "icon-lg": "size-9"
25
25
  }
26
26
  },
27
27
  defaultVariants: {
@@ -10,7 +10,7 @@ function Calendar({ className, classNames, showOutsideDays = true, captionLayout
10
10
  const defaultClassNames = getDefaultClassNames();
11
11
  return /* @__PURE__ */ jsx(DayPicker, {
12
12
  showOutsideDays,
13
- className: cn("group/calendar bg-background p-3 [--cell-radius:var(--radius-4xl)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
13
+ className: cn("group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
14
14
  captionLayout,
15
15
  locale,
16
16
  formatters: {