@stll/ui 0.19.0 → 0.20.0

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.
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { useKanbanDropTarget } from "./sortable-interactions.js";
3
+ import { resolveOptionColor } from "../lib/option-color.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { useId, useRef } from "react";
5
6
  import { useDndContext } from "@dnd-kit/core";
@@ -9,6 +10,18 @@ import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
9
10
  const DEFAULT_ESTIMATE_SIZE_PX = 128;
10
11
  const DEFAULT_OVERSCAN = 8;
11
12
  const DEFAULT_LOAD_MORE_THRESHOLD_PX = 200;
13
+ /** The CSS custom property every accent tint and the active accent ring are
14
+ * derived from, so both stay in lockstep with the resolved colour token. */
15
+ const KANBAN_CELL_ACCENT_VAR = "--kanban-cell-accent";
16
+ /** Faint resting wash: matches the alpha `option-color` already uses for its
17
+ * own subtle background token, so a tinted cell reads at the same weight as
18
+ * the swatch and badges that carry the same colour. */
19
+ const KANBAN_CELL_ACCENT_RESTING_ALPHA = 12;
20
+ /** Stronger wash while a card is dragged over an accented cell. */
21
+ const KANBAN_CELL_ACCENT_ACTIVE_ALPHA = 22;
22
+ /** Ring alpha for the active accent frame, well above the wash so the frame
23
+ * still reads as the drag-over affordance rather than more background tint. */
24
+ const KANBAN_CELL_ACCENT_ACTIVE_RING_ALPHA = 55;
12
25
  const retainActiveSortableIndex = (range, activeIndex) => {
13
26
  const indexes = defaultRangeExtractor(range);
14
27
  if (activeIndex < 0 || indexes.includes(activeIndex)) return indexes;
@@ -21,7 +34,7 @@ const KANBAN_VIRTUAL_CELL_PAGINATION = {
21
34
  CURSOR: "cursor"
22
35
  };
23
36
  /** Bounded, virtualized Kanban cell with cursor-page request deduplication. */
24
- const KanbanVirtualCell = ({ rows, getRowKey, renderRow, pagination, sortable, containerRef, active = false, backgroundColor, footer, estimateSize = DEFAULT_ESTIMATE_SIZE_PX, overscan = DEFAULT_OVERSCAN, loadMoreThreshold = DEFAULT_LOAD_MORE_THRESHOLD_PX, className }) => {
37
+ const KanbanVirtualCell = ({ rows, getRowKey, renderRow, pagination, sortable, containerRef, active = false, backgroundColor, accent, footer, estimateSize = DEFAULT_ESTIMATE_SIZE_PX, overscan = DEFAULT_OVERSCAN, loadMoreThreshold = DEFAULT_LOAD_MORE_THRESHOLD_PX, className }) => {
25
38
  const internalRef = useRef(null);
26
39
  const fallbackDropTargetId = useId();
27
40
  const scrollRef = internalRef;
@@ -69,12 +82,21 @@ const KanbanVirtualCell = ({ rows, getRowKey, renderRow, pagination, sortable, c
69
82
  if (containerRef) containerRef.current = element;
70
83
  dropTarget.setNodeRef(element);
71
84
  };
85
+ const accentVariants = accent === void 0 ? void 0 : resolveOptionColor(accent);
86
+ const accentBackground = accentVariants === void 0 ? void 0 : `color-mix(in srgb, var(${KANBAN_CELL_ACCENT_VAR}) ${active ? KANBAN_CELL_ACCENT_ACTIVE_ALPHA : KANBAN_CELL_ACCENT_RESTING_ALPHA}%, var(--background))`;
87
+ const activeAccentRing = active && accentVariants !== void 0 ? `0 0 0 2px color-mix(in srgb, var(${KANBAN_CELL_ACCENT_VAR}) ${KANBAN_CELL_ACCENT_ACTIVE_RING_ALPHA}%, transparent)` : void 0;
88
+ const style = backgroundColor === void 0 && accentVariants === void 0 ? void 0 : {
89
+ backgroundColor: backgroundColor ?? accentBackground,
90
+ ...activeAccentRing === void 0 ? void 0 : { boxShadow: activeAccentRing },
91
+ ...accentVariants === void 0 ? void 0 : { [KANBAN_CELL_ACCENT_VAR]: accentVariants.color }
92
+ };
72
93
  const content = /* @__PURE__ */ jsxs("div", {
73
- className: cn("bg-muted/20 max-h-[min(60vh,40rem)] min-h-20 overflow-y-auto overscroll-y-contain rounded-xl p-2 transition-[background-color,outline-color]", active && "bg-primary/5 ring-primary/50 ring-2", className),
94
+ className: cn("bg-muted/20 max-h-[min(60vh,40rem)] min-h-20 overflow-y-auto overscroll-y-contain rounded-xl p-2 transition-[background-color,outline-color]", active && accentVariants === void 0 && "bg-primary/5 ring-primary/50 ring-2", className),
74
95
  "data-kanban-cell": sortable?.dropTarget.id,
96
+ "data-kanban-cell-accent": accentVariants === void 0 ? void 0 : "true",
75
97
  onScroll: handleScroll,
76
98
  ref: setScrollElement,
77
- style: backgroundColor ? { backgroundColor } : void 0,
99
+ style,
78
100
  children: [/* @__PURE__ */ jsx("div", {
79
101
  className: "relative",
80
102
  style: { height: virtualizer.getTotalSize() },
@@ -5,6 +5,6 @@ declare const CONTROL_SIZE: Readonly<{
5
5
  readonly lg: "lg";
6
6
  }>;
7
7
  type ControlSize = (typeof CONTROL_SIZE)[keyof typeof CONTROL_SIZE];
8
- declare const CONTROL_SIZES: readonly ("default" | "lg" | "sm")[];
8
+ declare const CONTROL_SIZES: readonly ("sm" | "default" | "lg")[];
9
9
  //#endregion
10
10
  export { CONTROL_SIZE, CONTROL_SIZES, type ControlSize };
@@ -0,0 +1,28 @@
1
+ //#region src/lib/slot.d.ts
2
+ /**
3
+ * Minimal replacement for `@radix-ui/react-slot`.
4
+ *
5
+ * Renders its single child element with the parent's props
6
+ * (className, style, event handlers, ref, data-* attributes)
7
+ * merged onto it. Used by the sidebar shell for the `asChild`
8
+ * pattern.
9
+ *
10
+ * Ref composition logic adapted from the MIT-licensed
11
+ * `@radix-ui/react-compose-refs` (v1.1.2).
12
+ *
13
+ * This module intentionally uses `Children.only` and
14
+ * `cloneElement`: it is a low-level composition primitive
15
+ * that merges props onto a single child, which is exactly
16
+ * the use case React documents for these APIs.
17
+ *
18
+ * Several lint suppressions are necessary because prop
19
+ * merging is inherently dynamic; the types of child props
20
+ * are not statically known.
21
+ */
22
+ type SlotProps = React.HTMLAttributes<HTMLElement> & {
23
+ children?: React.ReactNode;
24
+ ref?: React.Ref<HTMLElement> | undefined;
25
+ };
26
+ declare const Slot: ({ ref: parentRef, children, className, style, ...rest }: SlotProps) => React.ReactNode;
27
+ //#endregion
28
+ export { Slot };
@@ -0,0 +1,64 @@
1
+ import { cn } from "./utils.js";
2
+ import { Children, Fragment, cloneElement, isValidElement } from "react";
3
+ //#region src/lib/slot.tsx
4
+ /**
5
+ * Minimal replacement for `@radix-ui/react-slot`.
6
+ *
7
+ * Renders its single child element with the parent's props
8
+ * (className, style, event handlers, ref, data-* attributes)
9
+ * merged onto it. Used by the sidebar shell for the `asChild`
10
+ * pattern.
11
+ *
12
+ * Ref composition logic adapted from the MIT-licensed
13
+ * `@radix-ui/react-compose-refs` (v1.1.2).
14
+ *
15
+ * This module intentionally uses `Children.only` and
16
+ * `cloneElement`: it is a low-level composition primitive
17
+ * that merges props onto a single child, which is exactly
18
+ * the use case React documents for these APIs.
19
+ *
20
+ * Several lint suppressions are necessary because prop
21
+ * merging is inherently dynamic; the types of child props
22
+ * are not statically known.
23
+ */
24
+ const Slot = ({ ref: parentRef, children, className, style, ...rest }) => {
25
+ const child = Children.only(children);
26
+ if (!isValidElement(child)) return child;
27
+ if (child.type === Fragment) return child;
28
+ const childProps = child.props;
29
+ const mergedProps = {
30
+ ...rest,
31
+ ...childProps
32
+ };
33
+ const childRef = childProps["ref"];
34
+ if (parentRef !== void 0 || childRef !== void 0) mergedProps["ref"] = composeSlotRefs(parentRef, childRef);
35
+ const childClassName = childProps["className"];
36
+ if (className || typeof childClassName === "string") mergedProps["className"] = cn(className, typeof childClassName === "string" ? childClassName : void 0);
37
+ const childStyle = childProps["style"];
38
+ if (style || typeof childStyle === "object" && childStyle !== null) mergedProps["style"] = {
39
+ ...style,
40
+ ...typeof childStyle === "object" && childStyle !== null ? childStyle : {}
41
+ };
42
+ const restEntries = Object.entries(rest);
43
+ for (const [key, parentValue] of restEntries) if (key.startsWith("on") && typeof parentValue === "function") {
44
+ const childValue = childProps[key];
45
+ if (typeof childValue === "function") mergedProps[key] = (...args) => {
46
+ Reflect.apply(childValue, void 0, args);
47
+ Reflect.apply(parentValue, void 0, args);
48
+ };
49
+ }
50
+ return cloneElement(child, mergedProps);
51
+ };
52
+ const composeSlotRefs = (parentRef, childRef) => (node) => {
53
+ setSlotRef(parentRef, node);
54
+ setSlotRef(childRef, node);
55
+ };
56
+ const setSlotRef = (ref, node) => {
57
+ if (typeof ref === "function") {
58
+ Reflect.apply(ref, void 0, [node]);
59
+ return;
60
+ }
61
+ if (typeof ref === "object" && ref !== null && "current" in ref) ref.current = node;
62
+ };
63
+ //#endregion
64
+ export { Slot };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/ui",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Stella's design system: bidi-aware React primitives built on Base UI, the dockable inspector pane, and the Tailwind v4 theme they are styled with.",
5
5
  "keywords": [
6
6
  "base-ui",
@@ -257,6 +257,10 @@
257
257
  "types": "./dist/components/sheet.d.ts",
258
258
  "import": "./dist/components/sheet.js"
259
259
  },
260
+ "./sidebar": {
261
+ "types": "./dist/components/sidebar.d.ts",
262
+ "import": "./dist/components/sidebar.js"
263
+ },
260
264
  "./loader": {
261
265
  "types": "./dist/components/loader.d.ts",
262
266
  "import": "./dist/components/loader.js"
@@ -494,6 +498,10 @@
494
498
  "types": "./dist/components/sheet.d.ts",
495
499
  "import": "./dist/components/sheet.js"
496
500
  },
501
+ "./components/sidebar": {
502
+ "types": "./dist/components/sidebar.d.ts",
503
+ "import": "./dist/components/sidebar.js"
504
+ },
497
505
  "./components/skeleton": {
498
506
  "types": "./dist/components/skeleton.d.ts",
499
507
  "import": "./dist/components/skeleton.js"