@present-day/scroll-area 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/mergeRefs.ts","../src/core/props.ts","../src/core/useScrollEdges.ts","../src/radix/parts.tsx","../src/radix/ScrollArea.tsx"],"sourcesContent":["// The default entry is the Radix adapter; Base UI lives at `./base-ui`.\nexport * from './radix'\n","import type { Ref, RefCallback } from 'react'\n\n/** Combines several refs into one callback ref. */\nexport function mergeRefs<T>(\n ...refs: Array<Ref<T> | undefined>\n): RefCallback<T> {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as { current: T | null }).current = node\n }\n }\n}\n","import type { CSSProperties, ReactNode, Ref, UIEventHandler } from 'react'\n\nimport type { ScrollAxes } from './useScrollEdges'\n\n/*\n * Each option set is an `as const` object in SCREAMING_SNAKE_CASE plus a\n * PascalCase union type derived from it, so `axis={SCROLL_AREA_AXIS.BOTH}` and\n * `axis=\"both\"` are equivalent.\n */\n\n/** Which directions scroll. */\nexport const SCROLL_AREA_AXIS = {\n VERTICAL: 'vertical',\n HORIZONTAL: 'horizontal',\n BOTH: 'both',\n} as const\nexport type ScrollAreaAxis =\n (typeof SCROLL_AREA_AXIS)[keyof typeof SCROLL_AREA_AXIS]\n\n/** How edges with more content beyond them are signaled. */\nexport const SCROLL_AREA_EDGE_EFFECT = {\n /** Fades the content itself toward the edge (color-independent). */\n FADE: 'fade',\n /** Draws `--scroll-area-shadow-color` over the edge. */\n SHADOW: 'shadow',\n /** Draws nothing; `data-overflow-*` attributes are still set. */\n NONE: 'none',\n} as const\nexport type ScrollAreaEdgeEffect =\n (typeof SCROLL_AREA_EDGE_EFFECT)[keyof typeof SCROLL_AREA_EDGE_EFFECT]\n\n/** When scrollbars are visible. */\nexport const SCROLL_AREA_SCROLLBARS = {\n /** While scrolling or hovering the area. */\n HOVER: 'hover',\n /** While scrolling. */\n SCROLL: 'scroll',\n /** While the content overflows. */\n AUTO: 'auto',\n /** Even when the content doesn't overflow. */\n ALWAYS: 'always',\n /** Never; the area still scrolls by wheel, touch, and keyboard. */\n HIDDEN: 'hidden',\n} as const\nexport type ScrollAreaScrollbars =\n (typeof SCROLL_AREA_SCROLLBARS)[keyof typeof SCROLL_AREA_SCROLLBARS]\n\nexport type ScrollAreaClassNames = {\n root?: string\n viewport?: string\n scrollbar?: string\n thumb?: string\n}\n\n/** Props every adapter's `ScrollArea` accepts, with the same meaning. */\nexport interface ScrollAreaSharedProps {\n children?: ReactNode\n /** Which directions scroll. Defaults to `\"vertical\"`. */\n axis?: ScrollAreaAxis\n /** How edges with more content beyond them are signaled. Defaults to `\"fade\"`. */\n edgeEffect?: ScrollAreaEdgeEffect\n /** Depth of the edge effect: a number of pixels or any CSS length. */\n edgeSize?: number | string\n /** When scrollbars are visible. Defaults to `\"hover\"`. */\n scrollbars?: ScrollAreaScrollbars\n /** Reading direction; flips horizontal scrolling and the scrollbar side. */\n dir?: 'ltr' | 'rtl'\n className?: string\n style?: CSSProperties\n /** Classes for the internal parts. */\n classNames?: ScrollAreaClassNames\n /** Ref to the scrolling viewport element. */\n viewportRef?: Ref<HTMLDivElement>\n onScroll?: UIEventHandler<HTMLDivElement>\n}\n\ntype StyleWithVars = CSSProperties & Record<`--${string}`, string>\n\nexport function axesFor(axis: ScrollAreaAxis): ScrollAxes {\n return {\n horizontal: axis !== 'vertical',\n vertical: axis !== 'horizontal',\n }\n}\n\n/** The root's inline style, with `edgeSize` applied as the size token. */\nexport function rootStyle(\n style: CSSProperties | undefined,\n edgeSize: number | string | undefined,\n): StyleWithVars {\n const result: StyleWithVars = { ...style }\n if (edgeSize !== undefined) {\n result['--scroll-area-edge-size'] =\n typeof edgeSize === 'number' ? `${edgeSize}px` : edgeSize\n }\n return result\n}\n","import { type RefObject, useLayoutEffect, useState } from 'react'\n\nexport type ScrollAxes = { horizontal: boolean; vertical: boolean }\nexport type EdgeSide = 'top' | 'right' | 'bottom' | 'left'\nexport type Edges = Record<EdgeSide, boolean>\n\nexport const EDGE_SIDES: readonly EdgeSide[] = [\n 'top',\n 'right',\n 'bottom',\n 'left',\n]\n\n/** Absorbs fractional scroll positions from zoom and high-DPI displays. */\nconst TOLERANCE = 1\n\n/** Which physical edges of `el` have more content beyond them. */\nexport function measureEdges(el: HTMLElement, axes: ScrollAxes): Edges {\n const { scrollTop, scrollHeight, clientHeight, scrollWidth, clientWidth } = el\n const maxLeft = scrollWidth - clientWidth\n // In RTL, scrollLeft is 0 at the right edge and goes negative toward the left.\n const rtl = getComputedStyle(el).direction === 'rtl'\n const fromLeft = rtl ? maxLeft + el.scrollLeft : el.scrollLeft\n\n return {\n top: axes.vertical && scrollTop > TOLERANCE,\n right: axes.horizontal && maxLeft - fromLeft > TOLERANCE,\n bottom:\n axes.vertical && scrollHeight - clientHeight - scrollTop > TOLERANCE,\n left: axes.horizontal && fromLeft > TOLERANCE,\n }\n}\n\n/**\n * Tracks which edges of a scrolling element have hidden content and mirrors\n * them as `data-overflow-{side}` attributes on `targetRef`. Writes go straight\n * to the DOM, so scrolling never re-renders React.\n *\n * Returns a callback ref for the scrolling element.\n */\nexport function useScrollEdges<T extends HTMLElement = HTMLDivElement>(\n targetRef: RefObject<HTMLElement | null>,\n { horizontal, vertical }: ScrollAxes,\n): (node: T | null) => void {\n const [viewport, setViewport] = useState<T | null>(null)\n\n useLayoutEffect(() => {\n if (!viewport) return\n\n let frame = 0\n const apply = () => {\n frame = 0\n const target = targetRef.current\n if (!target) return\n const edges = measureEdges(viewport, { horizontal, vertical })\n for (const side of EDGE_SIDES) {\n target.toggleAttribute(`data-overflow-${side}`, edges[side])\n }\n }\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(apply)\n }\n\n apply()\n viewport.addEventListener('scroll', schedule, { passive: true })\n const observer = new ResizeObserver(schedule)\n observer.observe(viewport)\n if (viewport.firstElementChild) observer.observe(viewport.firstElementChild)\n\n return () => {\n cancelAnimationFrame(frame)\n viewport.removeEventListener('scroll', schedule)\n observer.disconnect()\n }\n }, [viewport, targetRef, horizontal, vertical])\n\n return setViewport\n}\n","import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\nimport clsx from 'clsx'\nimport * as React from 'react'\n\nexport const Root = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaProps\n>(function Root({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Root\n ref={ref}\n data-primitive=\"radix\"\n className={clsx('sa-root', className)}\n {...props}\n />\n )\n})\n\nexport const Viewport = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaViewportProps\n>(function Viewport({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Viewport\n ref={ref}\n className={clsx('sa-viewport', className)}\n {...props}\n />\n )\n})\n\nexport const Scrollbar = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaScrollbarProps\n>(function Scrollbar({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Scrollbar\n ref={ref}\n className={clsx('sa-scrollbar', className)}\n {...props}\n />\n )\n})\n\nexport const Thumb = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaThumbProps\n>(function Thumb({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Thumb\n ref={ref}\n className={clsx('sa-thumb', className)}\n {...props}\n />\n )\n})\n\nexport const Corner = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaCornerProps\n>(function Corner({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Corner\n ref={ref}\n className={clsx('sa-corner', className)}\n {...props}\n />\n )\n})\n","import type * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\nimport clsx from 'clsx'\nimport * as React from 'react'\n\nimport { mergeRefs } from '../core/mergeRefs'\nimport { axesFor, rootStyle, type ScrollAreaSharedProps } from '../core/props'\nimport { useScrollEdges } from '../core/useScrollEdges'\nimport { Corner, Root, Scrollbar, Thumb, Viewport } from './parts'\n\nexport interface ScrollAreaProps\n extends ScrollAreaSharedProps,\n Omit<\n ScrollAreaPrimitive.ScrollAreaProps,\n keyof ScrollAreaSharedProps | 'type'\n > {}\n\nexport const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(\n function ScrollArea(\n {\n children,\n axis = 'vertical',\n edgeEffect = 'fade',\n edgeSize,\n scrollbars = 'hover',\n classNames = {},\n className,\n style,\n viewportRef,\n onScroll,\n ...rootProps\n },\n ref,\n ) {\n const rootRef = React.useRef<HTMLDivElement>(null)\n const { horizontal, vertical } = axesFor(axis)\n const edgesRef = useScrollEdges<HTMLDivElement>(rootRef, {\n horizontal,\n vertical,\n })\n // Memoized so React doesn't detach and reattach the refs on every render.\n const mergedRootRef = React.useMemo(() => mergeRefs(ref, rootRef), [ref])\n const mergedViewportRef = React.useMemo(\n () => mergeRefs(viewportRef, edgesRef),\n [viewportRef, edgesRef],\n )\n\n return (\n <Root\n {...rootProps}\n ref={mergedRootRef}\n // Radix only lets the viewport scroll while a scrollbar is mounted, so\n // \"hidden\" keeps them mounted and the stylesheet hides them.\n type={scrollbars === 'hidden' ? 'scroll' : scrollbars}\n data-edge-effect={edgeEffect}\n data-scrollbars={scrollbars}\n className={clsx(className, classNames.root)}\n style={rootStyle(style, edgeSize)}\n >\n <Viewport\n ref={mergedViewportRef}\n className={classNames.viewport}\n onScroll={onScroll}\n >\n {children}\n </Viewport>\n {edgeEffect === 'shadow' ? (\n <div className=\"sa-edge-shadow\" aria-hidden=\"true\" />\n ) : null}\n {vertical ? (\n <Scrollbar orientation=\"vertical\" className={classNames.scrollbar}>\n <Thumb className={classNames.thumb} />\n </Scrollbar>\n ) : null}\n {horizontal ? (\n <Scrollbar orientation=\"horizontal\" className={classNames.scrollbar}>\n <Thumb className={classNames.thumb} />\n </Scrollbar>\n ) : null}\n {vertical && horizontal ? <Corner /> : null}\n </Root>\n )\n },\n)\n"],"mappings":"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","names":["Corner","Root","Scrollbar","Thumb","Viewport","Root","clsx","Viewport","Scrollbar","Thumb","Corner","import_clsx","React","import_jsx_runtime","ScrollArea","Root","clsx","Viewport","Scrollbar","Thumb","Corner"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,145 @@
1
+ "use client";
2
+ import "./styles.css";
3
+ import {
4
+ EDGE_SIDES,
5
+ SCROLL_AREA_AXIS,
6
+ SCROLL_AREA_EDGE_EFFECT,
7
+ SCROLL_AREA_SCROLLBARS,
8
+ axesFor,
9
+ measureEdges,
10
+ mergeRefs,
11
+ rootStyle,
12
+ useScrollEdges
13
+ } from "./chunk-GSHKDU53.mjs";
14
+
15
+ // src/radix/parts.tsx
16
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
17
+ import clsx from "clsx";
18
+ import * as React from "react";
19
+ import { jsx } from "react/jsx-runtime";
20
+ var Root2 = React.forwardRef(function Root3({ className, ...props }, ref) {
21
+ return /* @__PURE__ */ jsx(
22
+ ScrollAreaPrimitive.Root,
23
+ {
24
+ ref,
25
+ "data-primitive": "radix",
26
+ className: clsx("sa-root", className),
27
+ ...props
28
+ }
29
+ );
30
+ });
31
+ var Viewport2 = React.forwardRef(function Viewport3({ className, ...props }, ref) {
32
+ return /* @__PURE__ */ jsx(
33
+ ScrollAreaPrimitive.Viewport,
34
+ {
35
+ ref,
36
+ className: clsx("sa-viewport", className),
37
+ ...props
38
+ }
39
+ );
40
+ });
41
+ var Scrollbar2 = React.forwardRef(function Scrollbar3({ className, ...props }, ref) {
42
+ return /* @__PURE__ */ jsx(
43
+ ScrollAreaPrimitive.Scrollbar,
44
+ {
45
+ ref,
46
+ className: clsx("sa-scrollbar", className),
47
+ ...props
48
+ }
49
+ );
50
+ });
51
+ var Thumb2 = React.forwardRef(function Thumb3({ className, ...props }, ref) {
52
+ return /* @__PURE__ */ jsx(
53
+ ScrollAreaPrimitive.Thumb,
54
+ {
55
+ ref,
56
+ className: clsx("sa-thumb", className),
57
+ ...props
58
+ }
59
+ );
60
+ });
61
+ var Corner2 = React.forwardRef(function Corner3({ className, ...props }, ref) {
62
+ return /* @__PURE__ */ jsx(
63
+ ScrollAreaPrimitive.Corner,
64
+ {
65
+ ref,
66
+ className: clsx("sa-corner", className),
67
+ ...props
68
+ }
69
+ );
70
+ });
71
+
72
+ // src/radix/ScrollArea.tsx
73
+ import clsx2 from "clsx";
74
+ import * as React2 from "react";
75
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
76
+ var ScrollArea = React2.forwardRef(
77
+ function ScrollArea2({
78
+ children,
79
+ axis = "vertical",
80
+ edgeEffect = "fade",
81
+ edgeSize,
82
+ scrollbars = "hover",
83
+ classNames = {},
84
+ className,
85
+ style,
86
+ viewportRef,
87
+ onScroll,
88
+ ...rootProps
89
+ }, ref) {
90
+ const rootRef = React2.useRef(null);
91
+ const { horizontal, vertical } = axesFor(axis);
92
+ const edgesRef = useScrollEdges(rootRef, {
93
+ horizontal,
94
+ vertical
95
+ });
96
+ const mergedRootRef = React2.useMemo(() => mergeRefs(ref, rootRef), [ref]);
97
+ const mergedViewportRef = React2.useMemo(
98
+ () => mergeRefs(viewportRef, edgesRef),
99
+ [viewportRef, edgesRef]
100
+ );
101
+ return /* @__PURE__ */ jsxs(
102
+ Root2,
103
+ {
104
+ ...rootProps,
105
+ ref: mergedRootRef,
106
+ type: scrollbars === "hidden" ? "scroll" : scrollbars,
107
+ "data-edge-effect": edgeEffect,
108
+ "data-scrollbars": scrollbars,
109
+ className: clsx2(className, classNames.root),
110
+ style: rootStyle(style, edgeSize),
111
+ children: [
112
+ /* @__PURE__ */ jsx2(
113
+ Viewport2,
114
+ {
115
+ ref: mergedViewportRef,
116
+ className: classNames.viewport,
117
+ onScroll,
118
+ children
119
+ }
120
+ ),
121
+ edgeEffect === "shadow" ? /* @__PURE__ */ jsx2("div", { className: "sa-edge-shadow", "aria-hidden": "true" }) : null,
122
+ vertical ? /* @__PURE__ */ jsx2(Scrollbar2, { orientation: "vertical", className: classNames.scrollbar, children: /* @__PURE__ */ jsx2(Thumb2, { className: classNames.thumb }) }) : null,
123
+ horizontal ? /* @__PURE__ */ jsx2(Scrollbar2, { orientation: "horizontal", className: classNames.scrollbar, children: /* @__PURE__ */ jsx2(Thumb2, { className: classNames.thumb }) }) : null,
124
+ vertical && horizontal ? /* @__PURE__ */ jsx2(Corner2, {}) : null
125
+ ]
126
+ }
127
+ );
128
+ }
129
+ );
130
+ export {
131
+ Corner2 as Corner,
132
+ EDGE_SIDES,
133
+ Root2 as Root,
134
+ SCROLL_AREA_AXIS,
135
+ SCROLL_AREA_EDGE_EFFECT,
136
+ SCROLL_AREA_SCROLLBARS,
137
+ ScrollArea,
138
+ Scrollbar2 as Scrollbar,
139
+ Thumb2 as Thumb,
140
+ Viewport2 as Viewport,
141
+ measureEdges,
142
+ mergeRefs,
143
+ useScrollEdges
144
+ };
145
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/radix/parts.tsx","../src/radix/ScrollArea.tsx"],"sourcesContent":["import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\nimport clsx from 'clsx'\nimport * as React from 'react'\n\nexport const Root = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaProps\n>(function Root({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Root\n ref={ref}\n data-primitive=\"radix\"\n className={clsx('sa-root', className)}\n {...props}\n />\n )\n})\n\nexport const Viewport = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaViewportProps\n>(function Viewport({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Viewport\n ref={ref}\n className={clsx('sa-viewport', className)}\n {...props}\n />\n )\n})\n\nexport const Scrollbar = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaScrollbarProps\n>(function Scrollbar({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Scrollbar\n ref={ref}\n className={clsx('sa-scrollbar', className)}\n {...props}\n />\n )\n})\n\nexport const Thumb = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaThumbProps\n>(function Thumb({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Thumb\n ref={ref}\n className={clsx('sa-thumb', className)}\n {...props}\n />\n )\n})\n\nexport const Corner = React.forwardRef<\n HTMLDivElement,\n ScrollAreaPrimitive.ScrollAreaCornerProps\n>(function Corner({ className, ...props }, ref) {\n return (\n <ScrollAreaPrimitive.Corner\n ref={ref}\n className={clsx('sa-corner', className)}\n {...props}\n />\n )\n})\n","import type * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\nimport clsx from 'clsx'\nimport * as React from 'react'\n\nimport { mergeRefs } from '../core/mergeRefs'\nimport { axesFor, rootStyle, type ScrollAreaSharedProps } from '../core/props'\nimport { useScrollEdges } from '../core/useScrollEdges'\nimport { Corner, Root, Scrollbar, Thumb, Viewport } from './parts'\n\nexport interface ScrollAreaProps\n extends ScrollAreaSharedProps,\n Omit<\n ScrollAreaPrimitive.ScrollAreaProps,\n keyof ScrollAreaSharedProps | 'type'\n > {}\n\nexport const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(\n function ScrollArea(\n {\n children,\n axis = 'vertical',\n edgeEffect = 'fade',\n edgeSize,\n scrollbars = 'hover',\n classNames = {},\n className,\n style,\n viewportRef,\n onScroll,\n ...rootProps\n },\n ref,\n ) {\n const rootRef = React.useRef<HTMLDivElement>(null)\n const { horizontal, vertical } = axesFor(axis)\n const edgesRef = useScrollEdges<HTMLDivElement>(rootRef, {\n horizontal,\n vertical,\n })\n // Memoized so React doesn't detach and reattach the refs on every render.\n const mergedRootRef = React.useMemo(() => mergeRefs(ref, rootRef), [ref])\n const mergedViewportRef = React.useMemo(\n () => mergeRefs(viewportRef, edgesRef),\n [viewportRef, edgesRef],\n )\n\n return (\n <Root\n {...rootProps}\n ref={mergedRootRef}\n // Radix only lets the viewport scroll while a scrollbar is mounted, so\n // \"hidden\" keeps them mounted and the stylesheet hides them.\n type={scrollbars === 'hidden' ? 'scroll' : scrollbars}\n data-edge-effect={edgeEffect}\n data-scrollbars={scrollbars}\n className={clsx(className, classNames.root)}\n style={rootStyle(style, edgeSize)}\n >\n <Viewport\n ref={mergedViewportRef}\n className={classNames.viewport}\n onScroll={onScroll}\n >\n {children}\n </Viewport>\n {edgeEffect === 'shadow' ? (\n <div className=\"sa-edge-shadow\" aria-hidden=\"true\" />\n ) : null}\n {vertical ? (\n <Scrollbar orientation=\"vertical\" className={classNames.scrollbar}>\n <Thumb className={classNames.thumb} />\n </Scrollbar>\n ) : null}\n {horizontal ? (\n <Scrollbar orientation=\"horizontal\" className={classNames.scrollbar}>\n <Thumb className={classNames.thumb} />\n </Scrollbar>\n ) : null}\n {vertical && horizontal ? <Corner /> : null}\n </Root>\n )\n },\n)\n"],"mappings":"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","names":["Root","Viewport","Scrollbar","Thumb","Corner","clsx","React","jsx","ScrollArea","Root","clsx","Viewport","Scrollbar","Thumb","Corner"]}
@@ -0,0 +1,83 @@
1
+ import { Ref, RefCallback, RefObject, ReactNode, CSSProperties, UIEventHandler } from 'react';
2
+
3
+ /** Combines several refs into one callback ref. */
4
+ declare function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): RefCallback<T>;
5
+
6
+ type ScrollAxes = {
7
+ horizontal: boolean;
8
+ vertical: boolean;
9
+ };
10
+ type EdgeSide = 'top' | 'right' | 'bottom' | 'left';
11
+ type Edges = Record<EdgeSide, boolean>;
12
+ declare const EDGE_SIDES: readonly EdgeSide[];
13
+ /** Which physical edges of `el` have more content beyond them. */
14
+ declare function measureEdges(el: HTMLElement, axes: ScrollAxes): Edges;
15
+ /**
16
+ * Tracks which edges of a scrolling element have hidden content and mirrors
17
+ * them as `data-overflow-{side}` attributes on `targetRef`. Writes go straight
18
+ * to the DOM, so scrolling never re-renders React.
19
+ *
20
+ * Returns a callback ref for the scrolling element.
21
+ */
22
+ declare function useScrollEdges<T extends HTMLElement = HTMLDivElement>(targetRef: RefObject<HTMLElement | null>, { horizontal, vertical }: ScrollAxes): (node: T | null) => void;
23
+
24
+ /** Which directions scroll. */
25
+ declare const SCROLL_AREA_AXIS: {
26
+ readonly VERTICAL: "vertical";
27
+ readonly HORIZONTAL: "horizontal";
28
+ readonly BOTH: "both";
29
+ };
30
+ type ScrollAreaAxis = (typeof SCROLL_AREA_AXIS)[keyof typeof SCROLL_AREA_AXIS];
31
+ /** How edges with more content beyond them are signaled. */
32
+ declare const SCROLL_AREA_EDGE_EFFECT: {
33
+ /** Fades the content itself toward the edge (color-independent). */
34
+ readonly FADE: "fade";
35
+ /** Draws `--scroll-area-shadow-color` over the edge. */
36
+ readonly SHADOW: "shadow";
37
+ /** Draws nothing; `data-overflow-*` attributes are still set. */
38
+ readonly NONE: "none";
39
+ };
40
+ type ScrollAreaEdgeEffect = (typeof SCROLL_AREA_EDGE_EFFECT)[keyof typeof SCROLL_AREA_EDGE_EFFECT];
41
+ /** When scrollbars are visible. */
42
+ declare const SCROLL_AREA_SCROLLBARS: {
43
+ /** While scrolling or hovering the area. */
44
+ readonly HOVER: "hover";
45
+ /** While scrolling. */
46
+ readonly SCROLL: "scroll";
47
+ /** While the content overflows. */
48
+ readonly AUTO: "auto";
49
+ /** Even when the content doesn't overflow. */
50
+ readonly ALWAYS: "always";
51
+ /** Never; the area still scrolls by wheel, touch, and keyboard. */
52
+ readonly HIDDEN: "hidden";
53
+ };
54
+ type ScrollAreaScrollbars = (typeof SCROLL_AREA_SCROLLBARS)[keyof typeof SCROLL_AREA_SCROLLBARS];
55
+ type ScrollAreaClassNames = {
56
+ root?: string;
57
+ viewport?: string;
58
+ scrollbar?: string;
59
+ thumb?: string;
60
+ };
61
+ /** Props every adapter's `ScrollArea` accepts, with the same meaning. */
62
+ interface ScrollAreaSharedProps {
63
+ children?: ReactNode;
64
+ /** Which directions scroll. Defaults to `"vertical"`. */
65
+ axis?: ScrollAreaAxis;
66
+ /** How edges with more content beyond them are signaled. Defaults to `"fade"`. */
67
+ edgeEffect?: ScrollAreaEdgeEffect;
68
+ /** Depth of the edge effect: a number of pixels or any CSS length. */
69
+ edgeSize?: number | string;
70
+ /** When scrollbars are visible. Defaults to `"hover"`. */
71
+ scrollbars?: ScrollAreaScrollbars;
72
+ /** Reading direction; flips horizontal scrolling and the scrollbar side. */
73
+ dir?: 'ltr' | 'rtl';
74
+ className?: string;
75
+ style?: CSSProperties;
76
+ /** Classes for the internal parts. */
77
+ classNames?: ScrollAreaClassNames;
78
+ /** Ref to the scrolling viewport element. */
79
+ viewportRef?: Ref<HTMLDivElement>;
80
+ onScroll?: UIEventHandler<HTMLDivElement>;
81
+ }
82
+
83
+ export { EDGE_SIDES as E, type ScrollAreaClassNames as S, type ScrollAreaSharedProps as a, type EdgeSide as b, type Edges as c, SCROLL_AREA_AXIS as d, SCROLL_AREA_EDGE_EFFECT as e, SCROLL_AREA_SCROLLBARS as f, type ScrollAreaAxis as g, type ScrollAreaEdgeEffect as h, type ScrollAreaScrollbars as i, type ScrollAxes as j, mergeRefs as k, measureEdges as m, useScrollEdges as u };
@@ -0,0 +1,83 @@
1
+ import { Ref, RefCallback, RefObject, ReactNode, CSSProperties, UIEventHandler } from 'react';
2
+
3
+ /** Combines several refs into one callback ref. */
4
+ declare function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): RefCallback<T>;
5
+
6
+ type ScrollAxes = {
7
+ horizontal: boolean;
8
+ vertical: boolean;
9
+ };
10
+ type EdgeSide = 'top' | 'right' | 'bottom' | 'left';
11
+ type Edges = Record<EdgeSide, boolean>;
12
+ declare const EDGE_SIDES: readonly EdgeSide[];
13
+ /** Which physical edges of `el` have more content beyond them. */
14
+ declare function measureEdges(el: HTMLElement, axes: ScrollAxes): Edges;
15
+ /**
16
+ * Tracks which edges of a scrolling element have hidden content and mirrors
17
+ * them as `data-overflow-{side}` attributes on `targetRef`. Writes go straight
18
+ * to the DOM, so scrolling never re-renders React.
19
+ *
20
+ * Returns a callback ref for the scrolling element.
21
+ */
22
+ declare function useScrollEdges<T extends HTMLElement = HTMLDivElement>(targetRef: RefObject<HTMLElement | null>, { horizontal, vertical }: ScrollAxes): (node: T | null) => void;
23
+
24
+ /** Which directions scroll. */
25
+ declare const SCROLL_AREA_AXIS: {
26
+ readonly VERTICAL: "vertical";
27
+ readonly HORIZONTAL: "horizontal";
28
+ readonly BOTH: "both";
29
+ };
30
+ type ScrollAreaAxis = (typeof SCROLL_AREA_AXIS)[keyof typeof SCROLL_AREA_AXIS];
31
+ /** How edges with more content beyond them are signaled. */
32
+ declare const SCROLL_AREA_EDGE_EFFECT: {
33
+ /** Fades the content itself toward the edge (color-independent). */
34
+ readonly FADE: "fade";
35
+ /** Draws `--scroll-area-shadow-color` over the edge. */
36
+ readonly SHADOW: "shadow";
37
+ /** Draws nothing; `data-overflow-*` attributes are still set. */
38
+ readonly NONE: "none";
39
+ };
40
+ type ScrollAreaEdgeEffect = (typeof SCROLL_AREA_EDGE_EFFECT)[keyof typeof SCROLL_AREA_EDGE_EFFECT];
41
+ /** When scrollbars are visible. */
42
+ declare const SCROLL_AREA_SCROLLBARS: {
43
+ /** While scrolling or hovering the area. */
44
+ readonly HOVER: "hover";
45
+ /** While scrolling. */
46
+ readonly SCROLL: "scroll";
47
+ /** While the content overflows. */
48
+ readonly AUTO: "auto";
49
+ /** Even when the content doesn't overflow. */
50
+ readonly ALWAYS: "always";
51
+ /** Never; the area still scrolls by wheel, touch, and keyboard. */
52
+ readonly HIDDEN: "hidden";
53
+ };
54
+ type ScrollAreaScrollbars = (typeof SCROLL_AREA_SCROLLBARS)[keyof typeof SCROLL_AREA_SCROLLBARS];
55
+ type ScrollAreaClassNames = {
56
+ root?: string;
57
+ viewport?: string;
58
+ scrollbar?: string;
59
+ thumb?: string;
60
+ };
61
+ /** Props every adapter's `ScrollArea` accepts, with the same meaning. */
62
+ interface ScrollAreaSharedProps {
63
+ children?: ReactNode;
64
+ /** Which directions scroll. Defaults to `"vertical"`. */
65
+ axis?: ScrollAreaAxis;
66
+ /** How edges with more content beyond them are signaled. Defaults to `"fade"`. */
67
+ edgeEffect?: ScrollAreaEdgeEffect;
68
+ /** Depth of the edge effect: a number of pixels or any CSS length. */
69
+ edgeSize?: number | string;
70
+ /** When scrollbars are visible. Defaults to `"hover"`. */
71
+ scrollbars?: ScrollAreaScrollbars;
72
+ /** Reading direction; flips horizontal scrolling and the scrollbar side. */
73
+ dir?: 'ltr' | 'rtl';
74
+ className?: string;
75
+ style?: CSSProperties;
76
+ /** Classes for the internal parts. */
77
+ classNames?: ScrollAreaClassNames;
78
+ /** Ref to the scrolling viewport element. */
79
+ viewportRef?: Ref<HTMLDivElement>;
80
+ onScroll?: UIEventHandler<HTMLDivElement>;
81
+ }
82
+
83
+ export { EDGE_SIDES as E, type ScrollAreaClassNames as S, type ScrollAreaSharedProps as a, type EdgeSide as b, type Edges as c, SCROLL_AREA_AXIS as d, SCROLL_AREA_EDGE_EFFECT as e, SCROLL_AREA_SCROLLBARS as f, type ScrollAreaAxis as g, type ScrollAreaEdgeEffect as h, type ScrollAreaScrollbars as i, type ScrollAxes as j, mergeRefs as k, measureEdges as m, useScrollEdges as u };
@@ -0,0 +1,157 @@
1
+ /* src/core/styles.css */
2
+ @property --_sa-t { syntax: "<length>"; inherits: true; initial-value: 0px; }
3
+ @property --_sa-r { syntax: "<length>"; inherits: true; initial-value: 0px; }
4
+ @property --_sa-b { syntax: "<length>"; inherits: true; initial-value: 0px; }
5
+ @property --_sa-l { syntax: "<length>"; inherits: true; initial-value: 0px; }
6
+ :where(.sa-root) {
7
+ block-size: 100%;
8
+ inline-size: 100%;
9
+ }
10
+ .sa-root {
11
+ --_sa-size: var(--scroll-area-edge-size, 24px);
12
+ --_sa-t: 0px;
13
+ --_sa-r: 0px;
14
+ --_sa-b: 0px;
15
+ --_sa-l: 0px;
16
+ position: relative;
17
+ box-sizing: border-box;
18
+ overflow: hidden;
19
+ transition:
20
+ --_sa-t 150ms ease-out,
21
+ --_sa-r 150ms ease-out,
22
+ --_sa-b 150ms ease-out,
23
+ --_sa-l 150ms ease-out;
24
+ }
25
+ .sa-root[data-overflow-top] {
26
+ --_sa-t: var(--_sa-size);
27
+ }
28
+ .sa-root[data-overflow-right] {
29
+ --_sa-r: var(--_sa-size);
30
+ }
31
+ .sa-root[data-overflow-bottom] {
32
+ --_sa-b: var(--_sa-size);
33
+ }
34
+ .sa-root[data-overflow-left] {
35
+ --_sa-l: var(--_sa-size);
36
+ }
37
+ @media (prefers-reduced-motion: reduce) {
38
+ .sa-root {
39
+ transition: none;
40
+ }
41
+ }
42
+ :where(.sa-viewport) {
43
+ block-size: 100%;
44
+ inline-size: 100%;
45
+ }
46
+ .sa-viewport {
47
+ box-sizing: border-box;
48
+ border-radius: inherit;
49
+ }
50
+ .sa-viewport:focus-visible {
51
+ outline: 2px solid Highlight;
52
+ outline-offset: -2px;
53
+ }
54
+ .sa-viewport[data-radix-scroll-area-viewport] > div {
55
+ display: var(--scroll-area-content-display, table) !important;
56
+ }
57
+ .sa-content {
58
+ display: var(--scroll-area-content-display, block);
59
+ }
60
+ .sa-root[data-edge-effect=fade] > .sa-viewport {
61
+ --_sa-min: var(--scroll-area-edge-fade-min, transparent);
62
+ mask-image:
63
+ linear-gradient(
64
+ to bottom,
65
+ var(--_sa-min),
66
+ #000 var(--_sa-t)),
67
+ linear-gradient(
68
+ to left,
69
+ var(--_sa-min),
70
+ #000 var(--_sa-r)),
71
+ linear-gradient(
72
+ to top,
73
+ var(--_sa-min),
74
+ #000 var(--_sa-b)),
75
+ linear-gradient(
76
+ to right,
77
+ var(--_sa-min),
78
+ #000 var(--_sa-l));
79
+ mask-composite: intersect;
80
+ }
81
+ .sa-edge-shadow {
82
+ --_sa-c: var( --scroll-area-shadow-color, light-dark(rgb(0 0 0 / 0.16), rgb(0 0 0 / 0.6)) );
83
+ position: absolute;
84
+ inset: 0;
85
+ pointer-events: none;
86
+ border-radius: inherit;
87
+ background:
88
+ linear-gradient(
89
+ to bottom,
90
+ var(--_sa-c),
91
+ transparent) top / 100% var(--_sa-t) no-repeat,
92
+ linear-gradient(
93
+ to left,
94
+ var(--_sa-c),
95
+ transparent) right / var(--_sa-r) 100% no-repeat,
96
+ linear-gradient(
97
+ to top,
98
+ var(--_sa-c),
99
+ transparent) bottom / 100% var(--_sa-b) no-repeat,
100
+ linear-gradient(
101
+ to right,
102
+ var(--_sa-c),
103
+ transparent) left / var(--_sa-l) 100% no-repeat;
104
+ }
105
+ .sa-scrollbar {
106
+ --_sa-gutter: 2px;
107
+ display: flex;
108
+ box-sizing: border-box;
109
+ padding: var(--_sa-gutter);
110
+ user-select: none;
111
+ touch-action: none;
112
+ }
113
+ .sa-root[data-scrollbars=hidden] > .sa-scrollbar {
114
+ display: none;
115
+ }
116
+ .sa-root[data-primitive=base-ui] > .sa-scrollbar {
117
+ transition: opacity 150ms;
118
+ }
119
+ .sa-root[data-primitive=base-ui][data-scrollbars=hover] > .sa-scrollbar:not([data-hovering], [data-scrolling]),
120
+ .sa-root[data-primitive=base-ui][data-scrollbars=scroll] > .sa-scrollbar:not([data-scrolling]) {
121
+ opacity: 0;
122
+ pointer-events: none;
123
+ }
124
+ @media (prefers-reduced-motion: reduce) {
125
+ .sa-root[data-primitive=base-ui] > .sa-scrollbar {
126
+ transition: none;
127
+ }
128
+ }
129
+ .sa-scrollbar[data-orientation=vertical] {
130
+ inline-size: calc(var(--scroll-area-thumb-size, 6px) + var(--_sa-gutter) * 2);
131
+ }
132
+ .sa-scrollbar[data-orientation=horizontal] {
133
+ flex-direction: column;
134
+ block-size: calc(var(--scroll-area-thumb-size, 6px) + var(--_sa-gutter) * 2);
135
+ }
136
+ .sa-thumb {
137
+ position: relative;
138
+ flex: 1;
139
+ border-radius: 9999px;
140
+ background: var( --scroll-area-thumb-color, light-dark(rgb(0 0 0 / 0.28), rgb(255 255 255 / 0.28)) );
141
+ transition: background-color 150ms;
142
+ }
143
+ .sa-thumb:hover {
144
+ background: var( --scroll-area-thumb-color-hover, light-dark(rgb(0 0 0 / 0.45), rgb(255 255 255 / 0.45)) );
145
+ }
146
+ .sa-thumb::before {
147
+ content: "";
148
+ position: absolute;
149
+ top: 50%;
150
+ left: 50%;
151
+ translate: -50% -50%;
152
+ inline-size: 100%;
153
+ block-size: 100%;
154
+ min-inline-size: var(--scroll-area-touch-target, 24px);
155
+ min-block-size: var(--scroll-area-touch-target, 24px);
156
+ }
157
+ /*# sourceMappingURL=styles.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/styles.css"],"sourcesContent":["/*\n * Public tokens (set on any ancestor to override). Defaults are var()\n * fallbacks so nothing here shadows an inherited value:\n * --scroll-area-edge-size depth of the fade/shadow 24px\n * --scroll-area-edge-fade-min opacity at the very edge transparent\n * --scroll-area-shadow-color shadow tint light-dark()\n * --scroll-area-thumb-color scrollbar thumb light-dark()\n * --scroll-area-thumb-color-hover\n * --scroll-area-thumb-size thumb thickness 6px\n * --scroll-area-touch-target minimum thumb hit area 24px\n * --scroll-area-content-display content wrapper display table (Radix) / block (Base UI)\n *\n * light-dark() resolves against the inherited `color-scheme`, which this\n * package never sets.\n */\n\n/* Per-edge depth, registered so it can transition. */\n@property --_sa-t {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n}\n@property --_sa-r {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n}\n@property --_sa-b {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n}\n@property --_sa-l {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n}\n\n/* :where() keeps sizing easy to override. */\n:where(.sa-root) {\n block-size: 100%;\n inline-size: 100%;\n}\n\n.sa-root {\n --_sa-size: var(--scroll-area-edge-size, 24px);\n /* Reset explicitly so a nested scroll area doesn't inherit its parent's edges. */\n --_sa-t: 0px;\n --_sa-r: 0px;\n --_sa-b: 0px;\n --_sa-l: 0px;\n position: relative;\n box-sizing: border-box;\n overflow: hidden;\n transition:\n --_sa-t 150ms ease-out,\n --_sa-r 150ms ease-out,\n --_sa-b 150ms ease-out,\n --_sa-l 150ms ease-out;\n}\n\n.sa-root[data-overflow-top] {\n --_sa-t: var(--_sa-size);\n}\n.sa-root[data-overflow-right] {\n --_sa-r: var(--_sa-size);\n}\n.sa-root[data-overflow-bottom] {\n --_sa-b: var(--_sa-size);\n}\n.sa-root[data-overflow-left] {\n --_sa-l: var(--_sa-size);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sa-root {\n transition: none;\n }\n}\n\n:where(.sa-viewport) {\n block-size: 100%;\n inline-size: 100%;\n}\n\n.sa-viewport {\n box-sizing: border-box;\n border-radius: inherit;\n}\n\n.sa-viewport:focus-visible {\n /* Base UI makes an overflowing viewport focusable; the root clips outer outlines. */\n outline: 2px solid Highlight;\n outline-offset: -2px;\n}\n\n.sa-viewport[data-radix-scroll-area-viewport] > div {\n /* Overrides the inline `display: table` Radix sets on the content wrapper. */\n /* biome-ignore lint/complexity/noImportantStyles: only way to beat an inline style */\n display: var(--scroll-area-content-display, table) !important;\n}\n\n.sa-content {\n display: var(--scroll-area-content-display, block);\n}\n\n/*\n * Fade: one mask layer per edge, intersected. A 0px edge is fully opaque, so\n * any combination of edges works without combination rules.\n */\n.sa-root[data-edge-effect=\"fade\"] > .sa-viewport {\n --_sa-min: var(--scroll-area-edge-fade-min, transparent);\n mask-image:\n linear-gradient(to bottom, var(--_sa-min), #000 var(--_sa-t)),\n linear-gradient(to left, var(--_sa-min), #000 var(--_sa-r)),\n linear-gradient(to top, var(--_sa-min), #000 var(--_sa-b)),\n linear-gradient(to right, var(--_sa-min), #000 var(--_sa-l));\n mask-composite: intersect;\n}\n\n/* Shadow: an overlay above the content and below the scrollbars. */\n.sa-edge-shadow {\n --_sa-c: var(\n --scroll-area-shadow-color,\n light-dark(rgb(0 0 0 / 0.16), rgb(0 0 0 / 0.6))\n );\n position: absolute;\n inset: 0;\n pointer-events: none;\n border-radius: inherit;\n background:\n linear-gradient(to bottom, var(--_sa-c), transparent) top / 100%\n var(--_sa-t) no-repeat,\n linear-gradient(to left, var(--_sa-c), transparent) right / var(--_sa-r)\n 100% no-repeat,\n linear-gradient(to top, var(--_sa-c), transparent) bottom / 100%\n var(--_sa-b) no-repeat,\n linear-gradient(to right, var(--_sa-c), transparent) left / var(--_sa-l)\n 100% no-repeat;\n}\n\n.sa-scrollbar {\n --_sa-gutter: 2px;\n display: flex;\n box-sizing: border-box;\n padding: var(--_sa-gutter);\n user-select: none;\n /* Disables browser panning and zooming gestures on the track. */\n touch-action: none;\n}\n\n/* Mounted (so the viewport can scroll) but never shown. */\n.sa-root[data-scrollbars=\"hidden\"] > .sa-scrollbar {\n display: none;\n}\n\n/*\n * Base UI has no visibility prop, so \"hover\" and \"scroll\" key off the\n * hover/scroll state its scrollbars carry. Radix handles these itself.\n */\n.sa-root[data-primitive=\"base-ui\"] > .sa-scrollbar {\n transition: opacity 150ms;\n}\n\n.sa-root[data-primitive=\"base-ui\"][data-scrollbars=\"hover\"]\n > .sa-scrollbar:not([data-hovering], [data-scrolling]),\n.sa-root[data-primitive=\"base-ui\"][data-scrollbars=\"scroll\"]\n > .sa-scrollbar:not([data-scrolling]) {\n opacity: 0;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sa-root[data-primitive=\"base-ui\"] > .sa-scrollbar {\n transition: none;\n }\n}\n\n.sa-scrollbar[data-orientation=\"vertical\"] {\n inline-size: calc(var(--scroll-area-thumb-size, 6px) + var(--_sa-gutter) * 2);\n}\n\n.sa-scrollbar[data-orientation=\"horizontal\"] {\n flex-direction: column;\n block-size: calc(var(--scroll-area-thumb-size, 6px) + var(--_sa-gutter) * 2);\n}\n\n.sa-thumb {\n position: relative;\n flex: 1;\n border-radius: 9999px;\n background: var(\n --scroll-area-thumb-color,\n light-dark(rgb(0 0 0 / 0.28), rgb(255 255 255 / 0.28))\n );\n transition: background-color 150ms;\n}\n\n.sa-thumb:hover {\n background: var(\n --scroll-area-thumb-color-hover,\n light-dark(rgb(0 0 0 / 0.45), rgb(255 255 255 / 0.45))\n );\n}\n\n/* Larger hit area for touch: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html */\n.sa-thumb::before {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n translate: -50% -50%;\n inline-size: 100%;\n block-size: 100%;\n min-inline-size: var(--scroll-area-touch-target, 24px);\n min-block-size: var(--scroll-area-touch-target, 24px);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,118 @@
1
+ {
2
+ "name": "@present-day/scroll-area",
3
+ "version": "0.1.0",
4
+ "description": "A Radix or Base UI scroll area with theme-aware edge fades and shadows",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "./radix": {
20
+ "import": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "./base-ui": {
30
+ "import": {
31
+ "types": "./dist/base-ui.d.mts",
32
+ "default": "./dist/base-ui.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./dist/base-ui.d.ts",
36
+ "default": "./dist/base-ui.js"
37
+ }
38
+ },
39
+ "./styles.css": "./dist/styles.css"
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "sideEffects": [
45
+ "**/*.css"
46
+ ],
47
+ "packageManager": "bun@1.3.13",
48
+ "scripts": {
49
+ "storybook": "storybook dev -p 6006",
50
+ "storybook:build": "storybook build",
51
+ "build": "tsup",
52
+ "dev": "tsup --watch",
53
+ "prepublishOnly": "bun run build",
54
+ "lint": "biome check .",
55
+ "format": "biome check --write .",
56
+ "type-check": "tsc --noEmit",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest"
59
+ },
60
+ "keywords": [
61
+ "react",
62
+ "scroll-area",
63
+ "radix",
64
+ "base-ui",
65
+ "scroll-shadow",
66
+ "fade"
67
+ ],
68
+ "author": "Present Day",
69
+ "license": "MIT",
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/present-day/scroll-area.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/present-day/scroll-area/issues"
76
+ },
77
+ "homepage": "https://github.com/present-day/scroll-area#readme",
78
+ "publishConfig": {
79
+ "access": "public"
80
+ },
81
+ "peerDependencies": {
82
+ "@base-ui/react": "^1.0.0",
83
+ "@radix-ui/react-scroll-area": "^1.2.0",
84
+ "react": ">=18.0.0",
85
+ "react-dom": ">=18.0.0"
86
+ },
87
+ "dependencies": {
88
+ "clsx": "^2.1.1"
89
+ },
90
+ "devDependencies": {
91
+ "@base-ui/react": "^1.8.0",
92
+ "@biomejs/biome": "^2.4.12",
93
+ "@radix-ui/react-scroll-area": "^1.2.18",
94
+ "@storybook/addon-a11y": "^10.6.0",
95
+ "@storybook/react-vite": "^10.6.0",
96
+ "@testing-library/jest-dom": "^6.9.1",
97
+ "@testing-library/react": "^16.3.2",
98
+ "@types/node": "^26.6.2",
99
+ "@types/react": "^19.3.0",
100
+ "@types/react-dom": "^19.3.0",
101
+ "happy-dom": "^20.9.0",
102
+ "react": "^19.3.0",
103
+ "react-dom": "^19.3.0",
104
+ "storybook": "^10.6.0",
105
+ "tsup": "^8.5.1",
106
+ "typescript": "^6.0.3",
107
+ "vite": "^8.3.0",
108
+ "vitest": "^4.1.6"
109
+ },
110
+ "peerDependenciesMeta": {
111
+ "@base-ui/react": {
112
+ "optional": true
113
+ },
114
+ "@radix-ui/react-scroll-area": {
115
+ "optional": true
116
+ }
117
+ }
118
+ }