@siyahi/ui 0.1.5-next.1 → 0.1.6-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
@@ -74,8 +74,12 @@ Every merge to `main` also attaches both tarballs to a GitHub release
74
74
  bar list, the `SectionCards` stat tile and the `sidebar-07` app shell.
75
75
  - **Siyahi components**: `Delta` (a signed figure with ▲ ▼), `PriceChart`
76
76
  (TradingView Lightweight Charts in theme colours), `Treemap` (holdings heat
77
- map), `LocalTime` (a time in the configured zone with the viewer's own clock
77
+ map; hover or tap a tile for its figures), `LocalTime` (a time in the configured zone with the viewer's own clock
78
78
  in a tooltip) and `SiyahiProvider`.
79
+ - **Cards and badges as installed**: every `Card`, stat tiles included, keeps
80
+ luma's `rounded-4xl`, `shadow-md` and faint ring. A category (a sector, a
81
+ broker, a unit's kind) is an outline `Badge`: categories stay neutral, and
82
+ colour is left to the four meaning colours and the charts.
79
83
  - **Formatters**: `money`, `moneyCompact`, `pct` and `time`, or `useFormat()`
80
84
  for the same bound to the provider's locale.
81
85
  `useFormat().time(value, { withDate: true })` (or
@@ -17,7 +17,15 @@ export interface TreemapProps<T extends TreemapItem> {
17
17
  range?: number;
18
18
  /** Gets the item under the pointer, and null when the pointer moves onto a group's name or leaves the map. */
19
19
  onHover?: (item: T | null) => void;
20
+ /** The selected item, matched by key, whose tile carries a ring. Pass back what onSelect gave. */
21
+ selected?: T | null;
22
+ /**
23
+ * Gets the item whose tile is tapped or clicked, and null when the selected
24
+ * tile is tapped again or a tap lands anywhere else. On a touch screen, which
25
+ * has no hover, this is how a reader gets a tile's figures.
26
+ */
27
+ onSelect?: (item: T | null) => void;
20
28
  }
21
29
  /** 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;
30
+ export declare function Treemap<T extends TreemapItem>({ items, height, range, onHover, selected, onSelect }: TreemapProps<T>): import("react").JSX.Element;
23
31
  //#endregion
@@ -1,18 +1,29 @@
1
1
  import { useFormat } from "../../lib/format.js";
2
2
  import { heat, layout } from "../../lib/treemap.js";
3
- import { useLayoutEffect, useRef, useState } from "react";
3
+ import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/components/siyahi/treemap.tsx
6
6
  /** Holdings heat map: area by weight, colour by signed change, optionally grouped; hand-drawn SVG. */
7
- function Treemap({ items, height, range, onHover }) {
7
+ function Treemap({ items, height, range, onHover, selected, onSelect }) {
8
8
  const ref = useRef(null);
9
9
  const [width, setWidth] = useState(0);
10
+ const selectedKey = selected ? selected.key ?? selected.name : null;
10
11
  const { pct } = useFormat();
11
12
  useLayoutEffect(() => {
12
13
  const observer = new ResizeObserver(([entry]) => setWidth(entry?.contentRect.width ?? 0));
13
14
  if (ref.current) observer.observe(ref.current);
14
15
  return () => observer.disconnect();
15
16
  }, []);
17
+ const clearOutside = useEffectEvent((event) => {
18
+ const tile = event.target instanceof Element ? event.target.closest("[data-slot=\"treemap-tile\"]") : null;
19
+ if (!ref.current?.contains(tile)) onSelect?.(null);
20
+ });
21
+ useEffect(() => {
22
+ if (selectedKey === null) return;
23
+ const onPointerDown = (event) => clearOutside(event);
24
+ document.addEventListener("pointerdown", onPointerDown);
25
+ return () => document.removeEventListener("pointerdown", onPointerDown);
26
+ }, [selectedKey]);
16
27
  const { groups, tiles } = layout(items, width, height);
17
28
  const change = (value) => value === null ? "—" : `${value > 0 ? "▲ " : value < 0 ? "▼ " : ""}${pct(Math.abs(value))}`;
18
29
  return /* @__PURE__ */ jsx("div", {
@@ -41,29 +52,47 @@ function Treemap({ items, height, range, onHover }) {
41
52
  className: "fill-muted-foreground font-medium",
42
53
  children: group
43
54
  })]
44
- }, group)), tiles.map(({ item, ...box }) => /* @__PURE__ */ jsxs("svg", {
45
- ...box,
46
- "data-slot": "treemap-tile",
47
- onPointerEnter: () => onHover?.(item),
48
- children: [/* @__PURE__ */ jsx("rect", {
49
- width: "100%",
50
- height: "100%",
51
- rx: 4,
52
- style: { fill: heat(item.change, range) }
53
- }), box.width > 56 && box.height > 36 && /* @__PURE__ */ jsxs("text", {
54
- x: 8,
55
- y: 18,
56
- className: "fill-foreground",
57
- children: [/* @__PURE__ */ jsx("tspan", {
58
- className: "font-medium",
59
- children: item.name
60
- }), /* @__PURE__ */ jsx("tspan", {
61
- x: 8,
62
- dy: 16,
63
- children: change(item.change)
64
- })]
65
- })]
66
- }, item.key ?? item.name))]
55
+ }, group)), tiles.map(({ item, ...box }) => {
56
+ const key = item.key ?? item.name;
57
+ return /* @__PURE__ */ jsxs("svg", {
58
+ ...box,
59
+ "data-slot": "treemap-tile",
60
+ "data-selected": key === selectedKey || void 0,
61
+ onPointerEnter: () => onHover?.(item),
62
+ onClick: () => onSelect?.(key === selectedKey ? null : item),
63
+ children: [
64
+ /* @__PURE__ */ jsx("rect", {
65
+ width: "100%",
66
+ height: "100%",
67
+ rx: 4,
68
+ style: { fill: heat(item.change, range) }
69
+ }),
70
+ key === selectedKey && /* @__PURE__ */ jsx("rect", {
71
+ x: 1,
72
+ y: 1,
73
+ width: box.width - 2,
74
+ height: box.height - 2,
75
+ rx: 3,
76
+ fill: "none",
77
+ strokeWidth: 2,
78
+ className: "stroke-foreground"
79
+ }),
80
+ box.width > 56 && box.height > 36 && /* @__PURE__ */ jsxs("text", {
81
+ x: 8,
82
+ y: 18,
83
+ className: "fill-foreground",
84
+ children: [/* @__PURE__ */ jsx("tspan", {
85
+ className: "font-medium",
86
+ children: item.name
87
+ }), /* @__PURE__ */ jsx("tspan", {
88
+ x: 8,
89
+ dy: 16,
90
+ children: change(item.change)
91
+ })]
92
+ })
93
+ ]
94
+ }, key);
95
+ })]
67
96
  })
68
97
  });
69
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siyahi/ui",
3
- "version": "0.1.5-next.1",
3
+ "version": "0.1.6-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",