@stll/ui 0.50.0 → 0.50.2

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.
@@ -0,0 +1,19 @@
1
+ import { ComponentProps } from "react";
2
+ //#region src/components/horizontal-scroller.d.ts
3
+ type ScrollDirection = "ltr" | "rtl";
4
+ type HorizontalScrollerProps = ComponentProps<"div"> & {
5
+ nextLabel: string;
6
+ previousLabel: string;
7
+ /** Lays out the items: the row that may grow wider than the viewport. */
8
+ contentClassName?: string;
9
+ viewportClassName?: string;
10
+ };
11
+ type ScrollState = {
12
+ canScrollEnd: boolean;
13
+ canScrollStart: boolean;
14
+ };
15
+ export declare const HorizontalScroller: ({ children, className, nextLabel, previousLabel, contentClassName, viewportClassName, ...props }: HorizontalScrollerProps) => import("react").JSX.Element;
16
+ export declare const scrollState: (scrollLeft: number, scrollWidth: number, clientWidth: number, direction: ScrollDirection) => ScrollState;
17
+ /** Wheel deltas arrive in pixels, lines or pages; scrollBy takes pixels. */
18
+ export declare const wheelDeltaPixels: (event: Pick<WheelEvent, "deltaMode" | "deltaY">, node: Pick<HTMLElement, "clientWidth">) => number;
19
+ //#endregion
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { ChevronLeftIcon, ChevronRightIcon } from "../icons.js";
3
+ import { cn } from "../lib/utils.js";
4
+ import { containedEventHandler } from "../hooks/use-contained-handler.js";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { useCallback, useState } from "react";
7
+ //#region src/components/horizontal-scroller.tsx
8
+ const CONTROL_CLASS = "bg-background/90 text-foreground absolute top-1/2 -translate-y-1/2 rounded-full border p-1 shadow-sm";
9
+ const HorizontalScroller = ({ children, className, nextLabel, previousLabel, contentClassName, viewportClassName, ...props }) => {
10
+ const [viewport, setViewport] = useState(null);
11
+ const [state, setState] = useState({
12
+ canScrollEnd: false,
13
+ canScrollStart: false
14
+ });
15
+ const measure = useCallback((node) => {
16
+ setState(scrollState(node.scrollLeft, node.scrollWidth, node.clientWidth, getComputedStyle(node).direction === "rtl" ? "rtl" : "ltr"));
17
+ }, []);
18
+ const viewportRef = useCallback((node) => {
19
+ setViewport(node);
20
+ if (!node) return;
21
+ const observer = new ResizeObserver(() => measure(node));
22
+ observer.observe(node);
23
+ if (node.firstElementChild !== null) observer.observe(node.firstElementChild);
24
+ measure(node);
25
+ const onWheel = (event) => {
26
+ if (event.ctrlKey || event.deltaX !== 0 || event.deltaY === 0 || node.scrollWidth <= node.clientWidth) return;
27
+ const rtl = getComputedStyle(node).direction === "rtl";
28
+ const { canScrollEnd, canScrollStart } = scrollState(node.scrollLeft, node.scrollWidth, node.clientWidth, rtl ? "rtl" : "ltr");
29
+ if (event.deltaY > 0 ? !canScrollEnd : !canScrollStart) return;
30
+ event.preventDefault();
31
+ const delta = wheelDeltaPixels(event, node);
32
+ node.scrollBy({ left: rtl ? -delta : delta });
33
+ };
34
+ node.addEventListener("wheel", onWheel, { passive: false });
35
+ return () => {
36
+ observer.disconnect();
37
+ node.removeEventListener("wheel", onWheel);
38
+ };
39
+ }, [measure]);
40
+ const scroll = (inlineDirection) => {
41
+ if (!viewport) return;
42
+ const rtl = getComputedStyle(viewport).direction === "rtl";
43
+ viewport.scrollBy({
44
+ behavior: "smooth",
45
+ left: viewport.clientWidth * .8 * inlineDirection * (rtl ? -1 : 1)
46
+ });
47
+ };
48
+ const onScroll = (event) => measure(event.currentTarget);
49
+ return /* @__PURE__ */ jsxs("div", {
50
+ className: cn("relative min-w-0", className),
51
+ onFocus: containedEventHandler(({ target }) => {
52
+ if (target instanceof HTMLElement && target !== viewport) target.scrollIntoView({
53
+ block: "nearest",
54
+ inline: "nearest"
55
+ });
56
+ }),
57
+ ...props,
58
+ children: [
59
+ /* @__PURE__ */ jsx("div", {
60
+ className: cn("scrollbar-subtle overflow-x-auto overscroll-x-contain", viewportClassName),
61
+ onScroll,
62
+ ref: viewportRef,
63
+ children: /* @__PURE__ */ jsx("div", {
64
+ className: cn("w-max min-w-full", contentClassName),
65
+ children
66
+ })
67
+ }),
68
+ state.canScrollStart && /* @__PURE__ */ jsx("div", { className: "from-background pointer-events-none absolute inset-y-0 start-0 w-10 bg-linear-to-r to-transparent rtl:bg-linear-to-l" }),
69
+ state.canScrollEnd && /* @__PURE__ */ jsx("div", { className: "from-background pointer-events-none absolute inset-y-0 end-0 w-10 bg-linear-to-l to-transparent rtl:bg-linear-to-r" }),
70
+ (state.canScrollStart || state.canScrollEnd) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
71
+ "aria-disabled": !state.canScrollStart,
72
+ tabIndex: state.canScrollStart ? void 0 : -1,
73
+ "aria-label": previousLabel,
74
+ className: cn(CONTROL_CLASS, "start-1", !state.canScrollStart && "pointer-events-none opacity-0 focus-visible:opacity-100"),
75
+ onClick: () => {
76
+ if (state.canScrollStart) scroll(-1);
77
+ },
78
+ type: "button",
79
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-4 rtl:rotate-180" })
80
+ }), /* @__PURE__ */ jsx("button", {
81
+ "aria-disabled": !state.canScrollEnd,
82
+ tabIndex: state.canScrollEnd ? void 0 : -1,
83
+ "aria-label": nextLabel,
84
+ className: cn(CONTROL_CLASS, "end-1", !state.canScrollEnd && "pointer-events-none opacity-0 focus-visible:opacity-100"),
85
+ onClick: () => {
86
+ if (state.canScrollEnd) scroll(1);
87
+ },
88
+ type: "button",
89
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4 rtl:rotate-180" })
90
+ })] })
91
+ ]
92
+ });
93
+ };
94
+ const scrollState = (scrollLeft, scrollWidth, clientWidth, direction) => {
95
+ const maximum = Math.max(0, scrollWidth - clientWidth);
96
+ const position = direction === "rtl" ? Math.abs(scrollLeft) : scrollLeft;
97
+ return {
98
+ canScrollStart: maximum > 1 && position > 1,
99
+ canScrollEnd: maximum > 1 && position < maximum - 1
100
+ };
101
+ };
102
+ const LINE_HEIGHT_PX = 16;
103
+ /** Wheel deltas arrive in pixels, lines or pages; scrollBy takes pixels. */
104
+ const wheelDeltaPixels = (event, node) => {
105
+ switch (event.deltaMode) {
106
+ case WheelEvent.DOM_DELTA_LINE: return event.deltaY * LINE_HEIGHT_PX;
107
+ case WheelEvent.DOM_DELTA_PAGE: return event.deltaY * node.clientWidth;
108
+ default: return event.deltaY;
109
+ }
110
+ };
111
+ //#endregion
112
+ export { HorizontalScroller, scrollState, wheelDeltaPixels };
@@ -91,7 +91,7 @@ const LandingItemText = ({ icon, iconTone = "muted", meta, title }) => /* @__PUR
91
91
  })]
92
92
  });
93
93
  const LandingRowIcon = ({ children, tone = "muted" }) => /* @__PURE__ */ jsx("span", {
94
- className: cn("mt-0.5 flex size-4 shrink-0 items-center justify-center", tone === "muted" && "text-foreground-muted group-hover:text-muted-foreground"),
94
+ className: cn("mt-0.5 flex min-h-4 min-w-4 shrink-0 items-center justify-center", tone === "muted" && "text-foreground-muted group-hover:text-muted-foreground"),
95
95
  children
96
96
  });
97
97
  const LandingEmpty = ({ children }) => /* @__PURE__ */ jsx("div", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/ui",
3
- "version": "0.50.0",
3
+ "version": "0.50.2",
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",
@@ -321,6 +321,10 @@
321
321
  "types": "./dist/components/scroll-area.d.ts",
322
322
  "import": "./dist/components/scroll-area.js"
323
323
  },
324
+ "./horizontal-scroller": {
325
+ "types": "./dist/components/horizontal-scroller.d.ts",
326
+ "import": "./dist/components/horizontal-scroller.js"
327
+ },
324
328
  "./scroll-to-top": {
325
329
  "types": "./dist/components/scroll-to-top.d.ts",
326
330
  "import": "./dist/components/scroll-to-top.js"
@@ -606,6 +610,10 @@
606
610
  "types": "./dist/components/scroll-area.d.ts",
607
611
  "import": "./dist/components/scroll-area.js"
608
612
  },
613
+ "./components/horizontal-scroller": {
614
+ "types": "./dist/components/horizontal-scroller.d.ts",
615
+ "import": "./dist/components/horizontal-scroller.js"
616
+ },
609
617
  "./components/scroll-to-top": {
610
618
  "types": "./dist/components/scroll-to-top.d.ts",
611
619
  "import": "./dist/components/scroll-to-top.js"