@siyahi/ui 0.1.1-next.4 → 0.1.3-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,12 +19,14 @@ is Tailwind source, so a plain CSS import leaves every component unstyled.
19
19
 
20
20
  ```sh
21
21
  npm install @siyahi/ui@next tailwindcss @tailwindcss/vite
22
+ npm install --save-dev @siyahi/tools@next
22
23
  ```
23
24
 
24
25
  ```ts
25
26
  // vite.config.ts
26
27
  import tailwindcss from "@tailwindcss/vite";
27
- export default defineConfig({ plugins: [react(), tailwindcss()] });
28
+ import { preloadFonts } from "@siyahi/tools/vite";
29
+ export default defineConfig({ plugins: [react(), tailwindcss(), preloadFonts()] });
28
30
  ```
29
31
 
30
32
  ```css
@@ -43,6 +45,14 @@ createRoot(root).render(
43
45
  );
44
46
  ```
45
47
 
48
+ `preloadFonts()` adds `<link rel="preload">` tags for Geist and Geist Mono to
49
+ the built `index.html`, so the fonts download with the scripts instead of after
50
+ the first paint. Until they arrive the theme draws Arial (or its Linux twins,
51
+ Liberation Sans and Arimo) scaled to Geist's metrics, so nothing moves when
52
+ Geist swaps in. An app that renders its own HTML adds the tags itself, with
53
+ the hashed URLs of `geist-latin-wght-normal.woff2`,
54
+ `geist-latin-ext-wght-normal.woff2` and `geist-mono-latin-wght-normal.woff2`.
55
+
46
56
  Everything comes from `@siyahi/ui`: the shadcn components, the registry
47
57
  add-ons (data table, heatmaps, stat tiles, sidebar), Siyahi's own components
48
58
  and the Recharts pieces charts are built from. Add `lucide-react` only if your
@@ -137,17 +147,18 @@ npm install --save-dev @siyahi/tools@next oxlint
137
147
  ```
138
148
 
139
149
  `@siyahi/tools` also has `siyahi-shots` (screenshots of a page at 1920 and 390
140
- pixels wide in both schemes, with layout checks) and `siyahi-contrast` (the
150
+ pixels wide in both schemes, with layout checks), `siyahi-cls` (how far a page
151
+ moves after its first paint on a slow network) and `siyahi-contrast` (the
141
152
  theme contrast check). See [packages/tools](packages/tools/README.md).
142
153
 
143
154
  ## This repository
144
155
 
145
- | Path | What |
146
- | ----------------- | --------------------------------------------------------------------------------- |
147
- | `packages/ui` | `@siyahi/ui`. The theme is `styles/siyahi.css`, the only file with colour values. |
148
- | `packages/tools` | `@siyahi/tools`: lint plugin, contrast check, screenshot script. |
149
- | `apps/catalogue` | The catalogue site, a Vite app deployed as a Cloudflare Worker. |
150
- | `assets/icon.svg` | The mark. |
156
+ | Path | What |
157
+ | ----------------- | ---------------------------------------------------------------------------------- |
158
+ | `packages/ui` | `@siyahi/ui`. The theme is `styles/siyahi.css`, the only file with colour values. |
159
+ | `packages/tools` | `@siyahi/tools`: lint plugin, font preload, contrast, screenshot and shift checks. |
160
+ | `apps/catalogue` | The catalogue site, a Vite app deployed as a Cloudflare Worker. |
161
+ | `assets/icon.svg` | The mark. |
151
162
 
152
163
  Development uses pnpm and [Vite+](https://viteplus.dev):
153
164
 
@@ -6,6 +6,8 @@ export interface BarListItem {
6
6
  /** Renders the label as a link. */
7
7
  href?: string;
8
8
  icon?: React$1.ReactNode;
9
+ /** Secondary figure after the label, such as a `Delta`. */
10
+ detail?: React$1.ReactNode;
9
11
  /** Stable key when names can repeat. */
10
12
  key?: string;
11
13
  }
@@ -31,28 +31,36 @@ function BarList({ data, valueFormatter, sortOrder = "descending", color = "var(
31
31
  className: "flex items-center gap-3 text-sm",
32
32
  children: [/* @__PURE__ */ jsxs("div", {
33
33
  className: "relative flex h-8 min-w-0 flex-1 items-center",
34
- children: [/* @__PURE__ */ jsx("div", {
35
- "aria-hidden": "true",
36
- className: "absolute inset-y-0 left-0 rounded-md transition-[width] duration-300",
37
- style: {
38
- width: `${width}%`,
39
- backgroundColor: `color-mix(in oklab, ${color} 20%, transparent)`
40
- }
41
- }), /* @__PURE__ */ jsxs(Label, {
42
- ...item.href ? { href: item.href } : {},
43
- ...onItemClick && !item.href ? {
44
- type: "button",
45
- onClick: () => onItemClick(item)
46
- } : {},
47
- className: cn("relative flex min-w-0 items-center gap-2 px-2 text-left", interactive && "hover:underline"),
48
- children: [item.icon ? /* @__PURE__ */ jsx("span", {
49
- className: "text-muted-foreground shrink-0 [&>svg]:size-4",
50
- children: item.icon
51
- }) : null, /* @__PURE__ */ jsx("span", {
52
- className: "truncate",
53
- children: item.name
54
- })]
55
- })]
34
+ children: [
35
+ /* @__PURE__ */ jsx("div", {
36
+ "aria-hidden": "true",
37
+ className: "absolute inset-y-0 left-0 rounded-md transition-[width] duration-300",
38
+ style: {
39
+ width: `${width}%`,
40
+ backgroundColor: `color-mix(in oklab, ${color} 20%, transparent)`
41
+ }
42
+ }),
43
+ /* @__PURE__ */ jsxs(Label, {
44
+ ...item.href ? { href: item.href } : {},
45
+ ...onItemClick && !item.href ? {
46
+ type: "button",
47
+ onClick: () => onItemClick(item)
48
+ } : {},
49
+ className: cn("relative flex min-w-0 items-center gap-2 px-2 text-left", interactive && "hover:underline"),
50
+ children: [item.icon ? /* @__PURE__ */ jsx("span", {
51
+ className: "text-muted-foreground shrink-0 [&>svg]:size-4",
52
+ children: item.icon
53
+ }) : null, /* @__PURE__ */ jsx("span", {
54
+ className: "truncate",
55
+ children: item.name
56
+ })]
57
+ }),
58
+ item.detail ? /* @__PURE__ */ jsx("span", {
59
+ "data-slot": "bar-list-detail",
60
+ className: "text-muted-foreground relative shrink-0 pr-2 text-xs tabular-nums",
61
+ children: item.detail
62
+ }) : null
63
+ ]
56
64
  }), /* @__PURE__ */ jsxs("div", {
57
65
  className: "flex shrink-0 items-center gap-2 tabular-nums",
58
66
  children: [/* @__PURE__ */ jsx("span", {
@@ -27,7 +27,7 @@ const DataTableHeader = React.memo(function DataTableHeader({ className, sticky
27
27
  const flexColumnIds = resolveFlexColumnIds(table);
28
28
  const columnSizing = table.state.columnSizing;
29
29
  return /* @__PURE__ */ jsx(TableHeader, {
30
- className: cn(sticky && "sticky top-0 z-30 bg-background", sticky && "after:absolute after:right-0 after:bottom-0 after:left-0 after:h-px after:bg-border", className),
30
+ className: cn(sticky && "sticky top-0 z-30 bg-card", sticky && "after:absolute after:right-0 after:bottom-0 after:left-0 after:h-px after:bg-border", className),
31
31
  children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
32
32
  const isFlex = flexColumnIds.has(header.column.id);
33
33
  const headerStyle = {
@@ -83,7 +83,7 @@ const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId,
83
83
  "data-flash": flashing ? "true" : void 0,
84
84
  "data-col-id": cell.column.id,
85
85
  style: cellStyle,
86
- className: cn(!cell.getIsGrouped() && "truncate", cell.column.getIsPinned() && "bg-background group-hover:bg-muted/50 group-data-[context-menu-open]:bg-muted/50 group-data-[state=selected]:bg-muted"),
86
+ className: cn(!cell.getIsGrouped() && "truncate", cell.column.getIsPinned() && "bg-linear-to-r group-hover:from-muted/50 group-hover:to-muted/50 group-data-[context-menu-open]:from-muted/50 group-data-[context-menu-open]:to-muted/50 group-data-[state=selected]:from-muted group-data-[state=selected]:to-muted"),
87
87
  children: renderCellContent(cell)
88
88
  }, cell.id);
89
89
  })
@@ -1,5 +1,5 @@
1
1
  import { Button } from "../../../ui/button.js";
2
- import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger } from "../../../ui/dropdown-menu.js";
2
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuTrigger } from "../../../ui/dropdown-menu.js";
3
3
  import { cn } from "../../../../lib/utils.js";
4
4
  import "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -34,13 +34,13 @@ function TableColumnActions({ children, className, label = "Column Actions", isA
34
34
  className: "sr-only",
35
35
  children: label
36
36
  })]
37
- }) }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
37
+ }) }), /* @__PURE__ */ jsx(DropdownMenuContent, {
38
38
  align,
39
39
  className: "w-48",
40
- children: [/* @__PURE__ */ jsx(DropdownMenuLabel, {
40
+ children: /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [/* @__PURE__ */ jsx(DropdownMenuLabel, {
41
41
  className: "text-xs font-normal text-muted-foreground",
42
42
  children: label
43
- }), children]
43
+ }), children] })
44
44
  })] });
45
45
  }
46
46
  TableColumnActions.displayName = "TableColumnActions";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Button } from "../../../ui/button.js";
3
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../../../ui/dropdown-menu.js";
3
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../../../ui/dropdown-menu.js";
4
4
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../ui/tooltip.js";
5
5
  import { cn } from "../../../../lib/utils.js";
6
6
  import { FILTER_VARIANTS } from "../lib/constants.js";
@@ -76,8 +76,7 @@ function TableColumnSortOptions({ column, table: propTable, variant: propVariant
76
76
  column.toggleSorting(isDesc, canMultiSort ? isMulti : false);
77
77
  }
78
78
  };
79
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
80
- withSeparator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
79
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [withSeparator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
81
80
  /* @__PURE__ */ jsxs(DropdownMenuLabel, {
82
81
  className: "flex items-center justify-between text-xs font-normal text-muted-foreground",
83
82
  children: [/* @__PURE__ */ jsxs("div", {
@@ -122,7 +121,7 @@ function TableColumnSortOptions({ column, table: propTable, variant: propVariant
122
121
  onClick: () => column.clearSorting(),
123
122
  children: [/* @__PURE__ */ jsx(icons.unsorted, { className: "mr-2 size-4 text-muted-foreground/70" }), "Clear Sort"]
124
123
  })
125
- ] });
124
+ ] })] });
126
125
  }
127
126
  /**
128
127
  * Standalone dropdown menu for sorting.
@@ -16,7 +16,7 @@ const getCommonPinningStyles = (column, isHeader = false) => {
16
16
  maxWidth: columnSize,
17
17
  flexShrink: 0,
18
18
  zIndex: isHeader ? 20 : 10,
19
- backgroundColor: "var(--background)",
19
+ backgroundColor: "var(--card)",
20
20
  ...isLeft ? { boxShadow: "inset -2px 0 0 0 color-mix(in oklab, var(--foreground) 40%, transparent), 4px 0 8px -2px color-mix(in oklab, var(--foreground) 14%, transparent)" } : { boxShadow: "inset 2px 0 0 0 color-mix(in oklab, var(--foreground) 40%, transparent), -4px 0 8px -2px color-mix(in oklab, var(--foreground) 14%, transparent)" }
21
21
  };
22
22
  };
@@ -1,14 +1,23 @@
1
1
  //#region src/components/siyahi/treemap.d.ts
2
2
  export interface TreemapItem {
3
3
  name: string;
4
- /** Tile area, such as the holding's value. */
4
+ /** Tile area, such as the holding's value. Zero, negative and non-finite weights draw no tile. */
5
5
  weight: number;
6
- /** Signed change as a fraction; sets the heat. */
7
- change: number;
6
+ /** Signed change as a fraction; sets the heat. `null` when unknown: a muted tile marked "—". */
7
+ change: number | null;
8
+ /** Unique key when names repeat, such as one symbol held at two brokers. Defaults to `name`. */
9
+ key?: string;
10
+ /** Tiles of one group, such as a sector, sit together under the group's name, even a group of one. */
11
+ group?: string;
8
12
  }
9
- /** Holdings heat map: area by weight, colour by signed change, hand-drawn SVG. */
10
- export declare function Treemap({ items, height }: {
11
- items: TreemapItem[];
13
+ export interface TreemapProps<T extends TreemapItem> {
14
+ items: T[];
12
15
  height: number;
13
- }): import("react").JSX.Element;
16
+ /** ±range: the change, as a fraction, that takes the deepest heat step. Default 0.05 (±5%); a range that is not a positive number uses the default. */
17
+ range?: number;
18
+ /** Gets the item under the pointer, and null when the pointer moves onto a group's name or leaves the map. */
19
+ onHover?: (item: T | null) => void;
20
+ }
21
+ /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
22
+ export declare function Treemap<T extends TreemapItem>({ items, height, range, onHover }: TreemapProps<T>): import("react").JSX.Element;
14
23
  //#endregion
@@ -1,15 +1,10 @@
1
1
  import { useFormat } from "../../lib/format.js";
2
+ import { heat, layout } from "../../lib/treemap.js";
2
3
  import { useLayoutEffect, useRef, useState } from "react";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { hierarchy, treemap, treemapSquarify } from "d3-hierarchy";
5
5
  //#region src/components/siyahi/treemap.tsx
6
- function heat(change) {
7
- if (change === 0) return "var(--muted)";
8
- const mix = Math.min(5, Math.ceil(Math.abs(change) * 100)) * 12;
9
- return `color-mix(in oklab, var(${change > 0 ? "--positive" : "--negative"}) ${mix}%, var(--card))`;
10
- }
11
- /** Holdings heat map: area by weight, colour by signed change, hand-drawn SVG. */
12
- function Treemap({ items, height }) {
6
+ /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
7
+ function Treemap({ items, height, range, onHover }) {
13
8
  const ref = useRef(null);
14
9
  const [width, setWidth] = useState(0);
15
10
  const { pct } = useFormat();
@@ -18,39 +13,57 @@ function Treemap({ items, height }) {
18
13
  if (ref.current) observer.observe(ref.current);
19
14
  return () => observer.disconnect();
20
15
  }, []);
21
- const root = hierarchy({ children: items }).sum((d) => d.weight ?? 0);
22
- const leaves = treemap().size([width, height]).paddingInner(2).tile(treemapSquarify)(root).leaves();
16
+ const { groups, tiles } = layout(items, width, height);
17
+ const change = (value) => value === null ? "—" : `${value > 0 ? "▲ " : value < 0 ? "▼ " : ""}${pct(Math.abs(value))}`;
23
18
  return /* @__PURE__ */ jsx("div", {
24
19
  ref,
25
20
  "data-slot": "treemap",
26
21
  style: { height },
27
- children: /* @__PURE__ */ jsx("svg", {
22
+ children: /* @__PURE__ */ jsxs("svg", {
28
23
  width,
29
24
  height,
30
25
  role: "img",
31
26
  "aria-label": "Treemap of holdings by weight and change",
32
27
  className: "text-xs",
33
- children: leaves.map(({ data, x0, y0, x1, y1 }) => /* @__PURE__ */ jsxs("g", {
34
- transform: `translate(${x0},${y0})`,
28
+ onPointerLeave: () => onHover?.(null),
29
+ children: [groups.map(({ group, ...box }) => /* @__PURE__ */ jsxs("svg", {
30
+ ...box,
31
+ "data-slot": "treemap-group",
32
+ onPointerEnter: () => onHover?.(null),
33
+ children: [/* @__PURE__ */ jsx("rect", {
34
+ width: "100%",
35
+ height: "100%",
36
+ fill: "none",
37
+ pointerEvents: "all"
38
+ }), /* @__PURE__ */ jsx("text", {
39
+ x: 2,
40
+ y: 12,
41
+ className: "fill-muted-foreground font-medium",
42
+ children: group
43
+ })]
44
+ }, group)), tiles.map(({ item, ...box }) => /* @__PURE__ */ jsxs("svg", {
45
+ ...box,
46
+ "data-slot": "treemap-tile",
47
+ onPointerEnter: () => onHover?.(item),
35
48
  children: [/* @__PURE__ */ jsx("rect", {
36
- width: x1 - x0,
37
- height: y1 - y0,
49
+ width: "100%",
50
+ height: "100%",
38
51
  rx: 4,
39
- style: { fill: heat(data.change ?? 0) }
40
- }), x1 - x0 > 56 && y1 - y0 > 36 && /* @__PURE__ */ jsxs("text", {
52
+ style: { fill: heat(item.change, range) }
53
+ }), box.width > 56 && box.height > 36 && /* @__PURE__ */ jsxs("text", {
41
54
  x: 8,
42
55
  y: 18,
43
56
  className: "fill-foreground",
44
57
  children: [/* @__PURE__ */ jsx("tspan", {
45
58
  className: "font-medium",
46
- children: data.name
47
- }), /* @__PURE__ */ jsxs("tspan", {
59
+ children: item.name
60
+ }), /* @__PURE__ */ jsx("tspan", {
48
61
  x: 8,
49
62
  dy: 16,
50
- children: [(data.change ?? 0) > 0 ? "▲ " : (data.change ?? 0) < 0 ? "▼ " : "", pct(Math.abs(data.change ?? 0))]
63
+ children: change(item.change)
51
64
  })]
52
65
  })]
53
- }, data.name))
66
+ }, item.key ?? item.name))]
54
67
  })
55
68
  });
56
69
  }
package/dist/index.d.ts CHANGED
@@ -43,7 +43,7 @@ import { LocalTime, LocalTimeProps } from "./components/siyahi/local-time.js";
43
43
  import { PriceChart, PriceChartProps } from "./components/siyahi/price-chart.js";
44
44
  import { SiyahiLocale, defaultLocale } from "./lib/locale.js";
45
45
  import { SiyahiProvider, SiyahiProviderProps, useLocale } from "./components/siyahi/siyahi-provider.js";
46
- import { Treemap, TreemapItem } from "./components/siyahi/treemap.js";
46
+ import { Treemap, TreemapItem, TreemapProps } from "./components/siyahi/treemap.js";
47
47
  import { Alert, AlertAction, AlertDescription, AlertTitle } from "./components/ui/alert.js";
48
48
  import { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage } from "./components/ui/avatar.js";
49
49
  import { Badge, badgeVariants } from "./components/ui/badge.js";
@@ -70,4 +70,4 @@ import { Toggle, toggleVariants } from "./components/ui/toggle.js";
70
70
  import { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group.js";
71
71
  import { Formatters, TimeOptions, money, moneyCompact, pct, time, useFormat } from "./lib/format.js";
72
72
  import { Area, AreaChart, Bar, BarChart, CartesianGrid, Line, LineChart, ReferenceLine, XAxis, YAxis } from "./lib/recharts.js";
73
- export { ActiveCell, ActiveSelection, Activity, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AppSidebarProps, Area, AreaChart, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Bar, BarChart, BarList, BarListItem, BarListProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBlockProps, CalendarHeatmapBody, CalendarHeatmapBodyProps, CalendarHeatmapFooter, CalendarHeatmapFooterProps, CalendarHeatmapLegend, CalendarHeatmapLegendProps, CalendarHeatmapProps, CalendarHeatmapStat, CalendarHeatmapStatProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CartesianGrid, ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorConfig, ColumnResizeInfo, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DataTable, DataTableActions, DataTableBody, DataTableBodyProps, type DataTableCell, type DataTableColumn, DataTableColumnActions, type DataTableColumnDef, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderProps, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, type DataTableColumns, DataTableConfig, DataTableContainerProps, DataTableContext, DataTableContextState, DataTableEmptyActions, DataTableEmptyActionsProps, DataTableEmptyBody, DataTableEmptyBodyProps, DataTableEmptyDescription, DataTableEmptyDescriptionProps, DataTableEmptyFilteredMessage, DataTableEmptyFilteredMessageProps, DataTableEmptyIcon, DataTableEmptyIconProps, DataTableEmptyMessage, DataTableEmptyMessageProps, DataTableEmptyState, DataTableEmptyStateProps, DataTableEmptyTitle, DataTableEmptyTitleProps, DataTableErrorBoundary, DataTableErrorBoundaryProps, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableGroupRowContextValue, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableGroupedRowsProps, DataTableHeader, type DataTableHeader$1 as DataTableHeaderCell, DataTableHeaderProps, type DataTableInstance, DataTableLoading, DataTableLoadingMore, DataTableLoadingMoreProps, DataTableLoadingProps, DataTableProvider, DataTableRoot, type DataTableRow, DataTableRowContextMenu, DataTableRowContextMenuProps, DataTableRowContextMenuSlot, DataTableRowContextMenuSlotProps, DataTableRowMenuScope, DataTableSkeleton, DataTableSkeletonProps, DataTableToolbarSection, DataTableToolbarSectionProps, Delta, DeltaProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type ExtendedColumnFilter, type ExtendedColumnSort, type FilterOperator, type FilterOperatorValues, type FilterVariant, type FilterVariantValues, FlashCellRef, FlashCells, FlashRows, FlashRowsOptions, Formatters, type GlobalFilter, GroupedBodyRow, GroupedBodyRowProps, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, type JoinOperator, type JoinOperatorValues, Labels, Line, LineChart, LocalTime, LocalTimeProps, NavMain, NavProjects, NavUser, type Option, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, PriceChartProps, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, type QueryKeys, ReferenceLine, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScrollAlign, ScrollArea, ScrollBar, type ScrollEvent, ScrollRowIntoView, SectionCard, SectionCards, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiLocale, SiyahiProvider, SiyahiProviderProps, Skeleton, Slider, StatusActivity, StatusColorConfig, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBlockProps, StatusHeatmapBody, StatusHeatmapBodyProps, StatusHeatmapFooter, StatusHeatmapFooterProps, StatusHeatmapLabels, StatusHeatmapLegend, StatusHeatmapLegendProps, StatusHeatmapProps, StatusHeatmapStat, StatusHeatmapStatProps, StatusValue, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnActionsProps, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFacetedFilterProps, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TeamSwitcher, Textarea, TimeOptions, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapItem, type ValueOf, type ColorConfig$1 as WeekdayColorConfig, type WeekdayExtraColumnSpec, type WeekdayExtraRowSpec, WeekdayHeatmap, WeekdayHeatmapBlock, type WeekdayHeatmapBlockProps, WeekdayHeatmapBody, type WeekdayHeatmapBodyProps, WeekdayHeatmapFooter, type WeekdayHeatmapFooterProps, type WeekdayHeatmapLabels, WeekdayHeatmapLegend, type WeekdayHeatmapLegendProps, type WeekdayHeatmapProps, WeekdayHeatmapStat, type WeekdayHeatmapStatProps, type WeekdayHour, type WeekdayHourlyActivity, type WeekdayIndex, XAxis, YAxis, badgeVariants, buttonVariants, defaultLocale, flashCellKey, money, moneyCompact, pct, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, tabsListVariants, time, toggleVariants, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useTableFacetedFilter };
73
+ export { ActiveCell, ActiveSelection, Activity, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AppSidebarProps, Area, AreaChart, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Bar, BarChart, BarList, BarListItem, BarListProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBlockProps, CalendarHeatmapBody, CalendarHeatmapBodyProps, CalendarHeatmapFooter, CalendarHeatmapFooterProps, CalendarHeatmapLegend, CalendarHeatmapLegendProps, CalendarHeatmapProps, CalendarHeatmapStat, CalendarHeatmapStatProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CartesianGrid, ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorConfig, ColumnResizeInfo, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DataTable, DataTableActions, DataTableBody, DataTableBodyProps, type DataTableCell, type DataTableColumn, DataTableColumnActions, type DataTableColumnDef, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderProps, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, type DataTableColumns, DataTableConfig, DataTableContainerProps, DataTableContext, DataTableContextState, DataTableEmptyActions, DataTableEmptyActionsProps, DataTableEmptyBody, DataTableEmptyBodyProps, DataTableEmptyDescription, DataTableEmptyDescriptionProps, DataTableEmptyFilteredMessage, DataTableEmptyFilteredMessageProps, DataTableEmptyIcon, DataTableEmptyIconProps, DataTableEmptyMessage, DataTableEmptyMessageProps, DataTableEmptyState, DataTableEmptyStateProps, DataTableEmptyTitle, DataTableEmptyTitleProps, DataTableErrorBoundary, DataTableErrorBoundaryProps, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableGroupRowContextValue, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableGroupedRowsProps, DataTableHeader, type DataTableHeader$1 as DataTableHeaderCell, DataTableHeaderProps, type DataTableInstance, DataTableLoading, DataTableLoadingMore, DataTableLoadingMoreProps, DataTableLoadingProps, DataTableProvider, DataTableRoot, type DataTableRow, DataTableRowContextMenu, DataTableRowContextMenuProps, DataTableRowContextMenuSlot, DataTableRowContextMenuSlotProps, DataTableRowMenuScope, DataTableSkeleton, DataTableSkeletonProps, DataTableToolbarSection, DataTableToolbarSectionProps, Delta, DeltaProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type ExtendedColumnFilter, type ExtendedColumnSort, type FilterOperator, type FilterOperatorValues, type FilterVariant, type FilterVariantValues, FlashCellRef, FlashCells, FlashRows, FlashRowsOptions, Formatters, type GlobalFilter, GroupedBodyRow, GroupedBodyRowProps, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, type JoinOperator, type JoinOperatorValues, Labels, Line, LineChart, LocalTime, LocalTimeProps, NavMain, NavProjects, NavUser, type Option, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, PriceChartProps, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, type QueryKeys, ReferenceLine, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScrollAlign, ScrollArea, ScrollBar, type ScrollEvent, ScrollRowIntoView, SectionCard, SectionCards, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiLocale, SiyahiProvider, SiyahiProviderProps, Skeleton, Slider, StatusActivity, StatusColorConfig, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBlockProps, StatusHeatmapBody, StatusHeatmapBodyProps, StatusHeatmapFooter, StatusHeatmapFooterProps, StatusHeatmapLabels, StatusHeatmapLegend, StatusHeatmapLegendProps, StatusHeatmapProps, StatusHeatmapStat, StatusHeatmapStatProps, StatusValue, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnActionsProps, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFacetedFilterProps, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TeamSwitcher, Textarea, TimeOptions, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapItem, TreemapProps, type ValueOf, type ColorConfig$1 as WeekdayColorConfig, type WeekdayExtraColumnSpec, type WeekdayExtraRowSpec, WeekdayHeatmap, WeekdayHeatmapBlock, type WeekdayHeatmapBlockProps, WeekdayHeatmapBody, type WeekdayHeatmapBodyProps, WeekdayHeatmapFooter, type WeekdayHeatmapFooterProps, type WeekdayHeatmapLabels, WeekdayHeatmapLegend, type WeekdayHeatmapLegendProps, type WeekdayHeatmapProps, WeekdayHeatmapStat, type WeekdayHeatmapStatProps, type WeekdayHour, type WeekdayHourlyActivity, type WeekdayIndex, XAxis, YAxis, badgeVariants, buttonVariants, defaultLocale, flashCellKey, money, moneyCompact, pct, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, tabsListVariants, time, toggleVariants, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useTableFacetedFilter };
@@ -0,0 +1,23 @@
1
+ //#region src/lib/treemap.d.ts
2
+ interface Box {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface Layout<T> {
9
+ groups: (Box & {
10
+ group: string;
11
+ })[];
12
+ tiles: (Box & {
13
+ item: T;
14
+ })[];
15
+ }
16
+ /** Squarified layout: items without a group are tiles of their own, the rest sit together under their group's label. */
17
+ export declare function layout<T extends {
18
+ weight: number;
19
+ group?: string;
20
+ }>(items: T[], width: number, height: number): Layout<T>;
21
+ export declare function heat(change: number | null, range?: number): string;
22
+ //#endregion
23
+ export {};
@@ -0,0 +1,40 @@
1
+ import { hierarchy, treemap, treemapSquarify } from "d3-hierarchy";
2
+ //#region src/lib/treemap.ts
3
+ /** Height of a group's label row, above its tiles. */
4
+ const GROUP_LABEL = 18;
5
+ const box = ({ x0, y0, x1, y1 }) => ({
6
+ x: x0,
7
+ y: y0,
8
+ width: x1 - x0,
9
+ height: y1 - y0
10
+ });
11
+ const area = (weight) => weight !== void 0 && Number.isFinite(weight) && weight > 0 ? weight : 0;
12
+ /** Squarified layout: items without a group are tiles of their own, the rest sit together under their group's label. */
13
+ function layout(items, width, height) {
14
+ const nested = { children: [...Map.groupBy(items, (item) => item.group)].flatMap(([group, members]) => {
15
+ const tiles = members.map((item) => ({ item }));
16
+ return group === void 0 ? tiles : [{
17
+ group,
18
+ children: tiles
19
+ }];
20
+ }) };
21
+ const root = treemap().size([width, height]).paddingInner(2).paddingTop(({ data }) => data.group === void 0 ? 0 : GROUP_LABEL).tile(treemapSquarify)(hierarchy(nested).sum(({ item }) => area(item?.weight)));
22
+ return {
23
+ groups: (root.children ?? []).flatMap((node) => node.data.group === void 0 || !node.value ? [] : [{
24
+ ...box(node),
25
+ height: GROUP_LABEL,
26
+ group: node.data.group
27
+ }]),
28
+ tiles: root.leaves().flatMap((node) => node.data.item === void 0 || !node.value ? [] : [{
29
+ ...box(node),
30
+ item: node.data.item
31
+ }])
32
+ };
33
+ }
34
+ function heat(change, range = .05) {
35
+ if (!change) return "var(--muted)";
36
+ const mix = Math.min(5, Math.ceil(Math.abs(change) / (Number.isFinite(range) && range > 0 ? range : .05) * 5)) * 12;
37
+ return `color-mix(in oklab, var(${change > 0 ? "--positive" : "--negative"}) ${mix}%, var(--card))`;
38
+ }
39
+ //#endregion
40
+ export { heat, layout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siyahi/ui",
3
- "version": "0.1.1-next.4",
3
+ "version": "0.1.3-next.1",
4
4
  "description": "Stock shadcn/ui on Base UI and Tailwind v4, a finance theme, a few compositions and formatters.",
5
5
  "keywords": [
6
6
  "base-ui",
@@ -58,6 +58,7 @@
58
58
  "tw-animate-css": "^1.4.0"
59
59
  },
60
60
  "devDependencies": {
61
+ "@tailwindcss/node": "4.3.3",
61
62
  "@types/d3-hierarchy": "^3.1.7",
62
63
  "@types/node": "24.19.2",
63
64
  "@types/react": "~19.3.0",
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Generated by scripts/generate.ts. Do not edit.
3
+ * System fonts scaled to Geist's and Geist Mono's metrics. The theme names
4
+ * them right after Geist, so the text a browser draws while Geist loads takes
5
+ * the space Geist will, and nothing moves when Geist swaps in.
6
+ */
7
+ @font-face {
8
+ font-family: "Geist Fallback";
9
+ src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans"), local("Arimo Regular"), local("Arimo-Regular");
10
+ font-weight: 100 450;
11
+ size-adjust: 102.87%;
12
+ ascent-override: 97.7%;
13
+ descent-override: 28.68%;
14
+ line-gap-override: 0%;
15
+ }
16
+
17
+ @font-face {
18
+ font-family: "Geist Fallback";
19
+ src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans"), local("Arimo Regular"), local("Arimo-Regular");
20
+ font-weight: 451 550;
21
+ size-adjust: 105.29%;
22
+ ascent-override: 95.45%;
23
+ descent-override: 28.02%;
24
+ line-gap-override: 0%;
25
+ }
26
+
27
+ @font-face {
28
+ font-family: "Geist Fallback";
29
+ src: local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"), local("LiberationSans-Bold"), local("Arimo Bold"), local("Arimo-Bold");
30
+ font-weight: 551 650;
31
+ size-adjust: 100.05%;
32
+ ascent-override: 100.45%;
33
+ descent-override: 29.49%;
34
+ line-gap-override: 0%;
35
+ }
36
+
37
+ @font-face {
38
+ font-family: "Geist Fallback";
39
+ src: local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"), local("LiberationSans-Bold"), local("Arimo Bold"), local("Arimo-Bold");
40
+ font-weight: 651 900;
41
+ size-adjust: 102.09%;
42
+ ascent-override: 98.44%;
43
+ descent-override: 28.9%;
44
+ line-gap-override: 0%;
45
+ }
46
+
47
+ @font-face {
48
+ font-family: "Geist Mono Fallback";
49
+ src: local("Courier New"), local("CourierNewPSMT"), local("Liberation Mono"), local("LiberationMono"), local("Cousine Regular"), local("Cousine-Regular");
50
+ font-weight: 100 550;
51
+ size-adjust: 99.98%;
52
+ ascent-override: 100.52%;
53
+ descent-override: 29.5%;
54
+ line-gap-override: 0%;
55
+ }
56
+
57
+ @font-face {
58
+ font-family: "Geist Mono Fallback";
59
+ src: local("Courier New Bold"), local("CourierNewPS-BoldMT"), local("Liberation Mono Bold"), local("LiberationMono-Bold"), local("Cousine Bold"), local("Cousine-Bold");
60
+ font-weight: 551 900;
61
+ size-adjust: 99.98%;
62
+ ascent-override: 100.52%;
63
+ descent-override: 29.5%;
64
+ line-gap-override: 0%;
65
+ }
package/styles/siyahi.css CHANGED
@@ -9,6 +9,7 @@
9
9
  @import "./shadcn.css";
10
10
  @import "@fontsource-variable/geist";
11
11
  @import "@fontsource-variable/geist-mono";
12
+ @import "./fallback-fonts.css";
12
13
 
13
14
  /* Tailwind does not scan node_modules, so name the package's own classes. */
14
15
  @source "../dist";
@@ -21,9 +22,15 @@
21
22
  @custom-variant dark (@media (prefers-color-scheme: dark));
22
23
 
23
24
  @theme inline {
24
- --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
25
- --font-mono: "Geist Mono Variable", ui-monospace, monospace;
25
+ --font-sans: "Geist Variable", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
26
+ --font-mono: "Geist Mono Variable", "Geist Mono Fallback", ui-monospace, monospace;
26
27
  --font-heading: var(--font-sans);
28
+ /*
29
+ * Tailwind's 65ch, measured in Geist, whose "0" is 0.663em wide. A ch
30
+ * follows whichever font is drawing, so a plain 65ch is narrower while the
31
+ * fallback draws and every prose block rewraps when Geist arrives.
32
+ */
33
+ --max-width-prose: 43.1em;
27
34
  --color-background: var(--background);
28
35
  --color-foreground: var(--foreground);
29
36
  --color-card: var(--card);