react-scroll-indicators 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.
- package/README.md +81 -0
- package/dist/index.cjs +216 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +195 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# react-scroll-indicators
|
|
2
|
+
|
|
3
|
+
A small React library that provides **OverflowContainer** — a scrollable container with hover-activated gradient indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-scroll-indicators
|
|
9
|
+
# or
|
|
10
|
+
pnpm add react-scroll-indicators
|
|
11
|
+
# or
|
|
12
|
+
yarn add react-scroll-indicators
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Peer dependencies
|
|
16
|
+
|
|
17
|
+
- `react` (>=17)
|
|
18
|
+
- `react-dom` (>=17)
|
|
19
|
+
|
|
20
|
+
If you use Tailwind CSS, the default indicator styles use Tailwind utility classes. You can override them with `indicatorClassName` or your own CSS.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { OverflowContainer } from "react-scroll-indicators";
|
|
26
|
+
|
|
27
|
+
function MyComponent() {
|
|
28
|
+
return (
|
|
29
|
+
<OverflowContainer className="max-w-md h-48">
|
|
30
|
+
<div className="flex gap-4">
|
|
31
|
+
{items.map((item) => (
|
|
32
|
+
<Card key={item.id} {...item} />
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
</OverflowContainer>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### With vertical scroll
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<OverflowContainer
|
|
44
|
+
verticalScrollIndicators
|
|
45
|
+
horizontalScrollIndicators={false}
|
|
46
|
+
className="h-64 w-64"
|
|
47
|
+
>
|
|
48
|
+
<div className="flex flex-col gap-2">{/* tall content */}</div>
|
|
49
|
+
</OverflowContainer>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Description |
|
|
55
|
+
|------|------|---------|-------------|
|
|
56
|
+
| `children` | `ReactNode` | — | Content inside the scroll area |
|
|
57
|
+
| `className` | `string` | — | Outer wrapper class |
|
|
58
|
+
| `containerClassName` | `string` | — | Inner scroll container class |
|
|
59
|
+
| `scrollSpeed` | `number` | `10` | Scroll interval in ms (lower = faster) |
|
|
60
|
+
| `scrollDistance` | `number` | `10` | Pixels to scroll per tick |
|
|
61
|
+
| `scrollEndPadding` | `number` | `10` | Padding from end before stopping |
|
|
62
|
+
| `showScrollIndicators` | `boolean` | `true` | Show gradient indicators |
|
|
63
|
+
| `horizontalScrollIndicators` | `boolean` | `true` | Show left/right indicators |
|
|
64
|
+
| `verticalScrollIndicators` | `boolean` | `false` | Show top/bottom indicators |
|
|
65
|
+
| `indicatorClassName` | `string` | — | Override indicator styles |
|
|
66
|
+
| `scrollOnHover` | `boolean` | `true` | Scroll when hovering over indicators |
|
|
67
|
+
|
|
68
|
+
All standard `div` HTML attributes are also supported (e.g. `style`, `aria-*`).
|
|
69
|
+
|
|
70
|
+
## Build
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run build
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output is in `dist/` (ESM + CJS + types).
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
OverflowContainer: () => OverflowContainer_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/OverflowContainer.tsx
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
+
function cn(...classes) {
|
|
32
|
+
return classes.filter(Boolean).join(" ");
|
|
33
|
+
}
|
|
34
|
+
var OverflowContainer = (0, import_react.forwardRef)(
|
|
35
|
+
({
|
|
36
|
+
className,
|
|
37
|
+
children,
|
|
38
|
+
scrollSpeed = 10,
|
|
39
|
+
scrollDistance = 10,
|
|
40
|
+
scrollEndPadding = 10,
|
|
41
|
+
containerClassName,
|
|
42
|
+
showScrollIndicators = true,
|
|
43
|
+
horizontalScrollIndicators = true,
|
|
44
|
+
verticalScrollIndicators = false,
|
|
45
|
+
indicatorClassName,
|
|
46
|
+
scrollOnHover = true,
|
|
47
|
+
...props
|
|
48
|
+
}, forwardedRef) => {
|
|
49
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
50
|
+
const [canScrollLeft, setCanScrollLeft] = (0, import_react.useState)(false);
|
|
51
|
+
const [canScrollRight, setCanScrollRight] = (0, import_react.useState)(false);
|
|
52
|
+
const [canScrollUp, setCanScrollUp] = (0, import_react.useState)(false);
|
|
53
|
+
const [canScrollDown, setCanScrollDown] = (0, import_react.useState)(false);
|
|
54
|
+
const intervalRef = (0, import_react.useRef)(null);
|
|
55
|
+
(0, import_react.useEffect)(() => {
|
|
56
|
+
if (process.env?.NODE_ENV === "development") {
|
|
57
|
+
if (scrollSpeed < 0) {
|
|
58
|
+
console.warn(
|
|
59
|
+
"OverflowContainer: scrollSpeed should be a positive number"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (scrollDistance < 0) {
|
|
63
|
+
console.warn(
|
|
64
|
+
"OverflowContainer: scrollDistance should be a positive number"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (scrollEndPadding < 0) {
|
|
68
|
+
console.warn(
|
|
69
|
+
"OverflowContainer: scrollEndPadding should be a positive number"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, [scrollSpeed, scrollDistance, scrollEndPadding]);
|
|
74
|
+
const startScroll = (scrollDirection) => {
|
|
75
|
+
stopScroll();
|
|
76
|
+
const isHorizontal = scrollDirection === "left" || scrollDirection === "right";
|
|
77
|
+
const isBackward = scrollDirection === "left" || scrollDirection === "up";
|
|
78
|
+
const scrollAmount = isBackward ? -scrollDistance : scrollDistance;
|
|
79
|
+
const container = containerRef.current;
|
|
80
|
+
if (!container) return;
|
|
81
|
+
intervalRef.current = setInterval(() => {
|
|
82
|
+
if (!container) return;
|
|
83
|
+
const scrollPos = isHorizontal ? container.scrollLeft : container.scrollTop;
|
|
84
|
+
const scrollSize = isHorizontal ? container.scrollWidth : container.scrollHeight;
|
|
85
|
+
const clientSize = isHorizontal ? container.clientWidth : container.clientHeight;
|
|
86
|
+
const canScroll = isBackward ? scrollPos > 0 : scrollPos < scrollSize - clientSize;
|
|
87
|
+
if (!canScroll) {
|
|
88
|
+
stopScroll();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
updateScrollButtons();
|
|
92
|
+
container.scrollBy({
|
|
93
|
+
[isHorizontal ? "left" : "top"]: scrollAmount
|
|
94
|
+
});
|
|
95
|
+
}, scrollSpeed);
|
|
96
|
+
};
|
|
97
|
+
const updateScrollButtons = () => {
|
|
98
|
+
const container = containerRef.current;
|
|
99
|
+
if (!container) return;
|
|
100
|
+
const canScrollLeftVal = container.scrollLeft > scrollEndPadding;
|
|
101
|
+
const canScrollRightVal = container.scrollLeft < container.scrollWidth - container.clientWidth - scrollEndPadding;
|
|
102
|
+
const canScrollUpVal = container.scrollTop > scrollEndPadding;
|
|
103
|
+
const canScrollDownVal = container.scrollTop < container.scrollHeight - container.clientHeight - scrollEndPadding;
|
|
104
|
+
setCanScrollLeft(canScrollLeftVal);
|
|
105
|
+
setCanScrollRight(canScrollRightVal);
|
|
106
|
+
setCanScrollUp(canScrollUpVal);
|
|
107
|
+
setCanScrollDown(canScrollDownVal);
|
|
108
|
+
};
|
|
109
|
+
const stopScroll = () => {
|
|
110
|
+
if (intervalRef.current) {
|
|
111
|
+
clearInterval(intervalRef.current);
|
|
112
|
+
intervalRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
(0, import_react.useEffect)(() => {
|
|
116
|
+
if (!forwardedRef) return;
|
|
117
|
+
if (typeof forwardedRef === "function") {
|
|
118
|
+
forwardedRef(containerRef.current);
|
|
119
|
+
} else {
|
|
120
|
+
forwardedRef.current = containerRef.current;
|
|
121
|
+
}
|
|
122
|
+
}, [forwardedRef]);
|
|
123
|
+
(0, import_react.useEffect)(() => {
|
|
124
|
+
updateScrollButtons();
|
|
125
|
+
const container = containerRef.current;
|
|
126
|
+
if (!container) return;
|
|
127
|
+
const resizeObserver = new ResizeObserver(updateScrollButtons);
|
|
128
|
+
resizeObserver.observe(container);
|
|
129
|
+
return () => {
|
|
130
|
+
resizeObserver.disconnect();
|
|
131
|
+
stopScroll();
|
|
132
|
+
};
|
|
133
|
+
}, []);
|
|
134
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
135
|
+
"div",
|
|
136
|
+
{
|
|
137
|
+
role: "region",
|
|
138
|
+
"aria-label": "Scrollable content",
|
|
139
|
+
className: cn("flex relative overflow-hidden", className),
|
|
140
|
+
...props,
|
|
141
|
+
children: [
|
|
142
|
+
showScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "absolute left-0 top-0 flex w-full h-full z-50 bg-transparent pointer-events-none", children: [
|
|
143
|
+
canScrollLeft && horizontalScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
className: cn(
|
|
147
|
+
"absolute h-full w-4 left-0 top-0 bg-gradient-to-l from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
148
|
+
indicatorClassName
|
|
149
|
+
),
|
|
150
|
+
onMouseEnter: () => scrollOnHover && startScroll("left"),
|
|
151
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
canScrollRight && horizontalScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
155
|
+
"div",
|
|
156
|
+
{
|
|
157
|
+
className: cn(
|
|
158
|
+
"absolute h-full w-4 right-0 top-0 bg-gradient-to-r from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
159
|
+
indicatorClassName
|
|
160
|
+
),
|
|
161
|
+
onMouseEnter: () => scrollOnHover && startScroll("right"),
|
|
162
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
163
|
+
}
|
|
164
|
+
),
|
|
165
|
+
canScrollUp && verticalScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
166
|
+
"div",
|
|
167
|
+
{
|
|
168
|
+
className: cn(
|
|
169
|
+
"absolute w-full h-5 left-0 top-0 bg-gradient-to-t from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
170
|
+
indicatorClassName
|
|
171
|
+
),
|
|
172
|
+
onMouseEnter: () => scrollOnHover && startScroll("up"),
|
|
173
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
canScrollDown && verticalScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
className: cn(
|
|
180
|
+
"absolute w-full h-5 left-0 bottom-0 bg-gradient-to-b from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
181
|
+
indicatorClassName
|
|
182
|
+
),
|
|
183
|
+
onMouseEnter: () => scrollOnHover && startScroll("down"),
|
|
184
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
189
|
+
"div",
|
|
190
|
+
{
|
|
191
|
+
className: cn(
|
|
192
|
+
"flex relative overflow-auto w-full",
|
|
193
|
+
containerClassName
|
|
194
|
+
),
|
|
195
|
+
ref: containerRef,
|
|
196
|
+
onScroll: updateScrollButtons,
|
|
197
|
+
children
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
OverflowContainer.displayName = "OverflowContainer";
|
|
206
|
+
var OverflowContainer_default = OverflowContainer;
|
|
207
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
208
|
+
0 && (module.exports = {
|
|
209
|
+
OverflowContainer
|
|
210
|
+
});
|
|
211
|
+
/**
|
|
212
|
+
* OverflowContainer – React component for smooth scrolling with hover-activated
|
|
213
|
+
* indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
214
|
+
* @license MIT
|
|
215
|
+
*/
|
|
216
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/OverflowContainer.tsx"],"sourcesContent":["export { default as OverflowContainer } from \"./OverflowContainer\";\nexport type { OverflowContainerProps } from \"./OverflowContainer\";\n","/**\n * OverflowContainer – React component for smooth scrolling with hover-activated\n * indicators when content overflows. Supports horizontal and vertical scrolling.\n * @license MIT\n */\n\nimport {\n type ForwardedRef,\n forwardRef,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\ndeclare const process: { env?: { NODE_ENV?: string } };\n\nfunction cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n\nexport interface OverflowContainerProps\n extends React.HTMLAttributes<HTMLDivElement> {\n /** Custom className for the outer container */\n className?: string;\n\n /** Content to be rendered inside the scroll container */\n children: React.ReactNode;\n\n /**\n * Speed of the scroll animation in milliseconds. Lower = faster. Positive only.\n * @default 10\n */\n scrollSpeed?: number;\n\n /**\n * Distance to scroll in pixels per interval. Positive only.\n * @default 10\n */\n scrollDistance?: number;\n\n /**\n * Padding in pixels before the end to stop scrolling. Positive only.\n * @default 10\n */\n scrollEndPadding?: number;\n\n /** Custom className for the inner scrollable container */\n containerClassName?: string;\n\n /**\n * Whether to show scroll indicators when content overflows.\n * @default true\n */\n showScrollIndicators?: boolean;\n\n /**\n * Whether to show horizontal scroll indicators.\n * @default true\n */\n horizontalScrollIndicators?: boolean;\n\n /**\n * Whether to show vertical scroll indicators.\n * @default false\n */\n verticalScrollIndicators?: boolean;\n\n /** Custom className for scroll indicators (overrides default styles) */\n indicatorClassName?: string;\n\n /**\n * Whether to scroll when hovering over indicators.\n * @default true\n */\n scrollOnHover?: boolean;\n}\n\nconst OverflowContainer = forwardRef(\n (\n {\n className,\n children,\n scrollSpeed = 10,\n scrollDistance = 10,\n scrollEndPadding = 10,\n containerClassName,\n showScrollIndicators = true,\n horizontalScrollIndicators = true,\n verticalScrollIndicators = false,\n indicatorClassName,\n scrollOnHover = true,\n ...props\n }: OverflowContainerProps,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n const [canScrollUp, setCanScrollUp] = useState(false);\n const [canScrollDown, setCanScrollDown] = useState(false);\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n useEffect(() => {\n if (process.env?.NODE_ENV === \"development\") {\n if (scrollSpeed < 0) {\n console.warn(\n \"OverflowContainer: scrollSpeed should be a positive number\"\n );\n }\n if (scrollDistance < 0) {\n console.warn(\n \"OverflowContainer: scrollDistance should be a positive number\"\n );\n }\n if (scrollEndPadding < 0) {\n console.warn(\n \"OverflowContainer: scrollEndPadding should be a positive number\"\n );\n }\n }\n }, [scrollSpeed, scrollDistance, scrollEndPadding]);\n\n const startScroll = (scrollDirection: \"left\" | \"right\" | \"up\" | \"down\") => {\n stopScroll();\n const isHorizontal =\n scrollDirection === \"left\" || scrollDirection === \"right\";\n const isBackward = scrollDirection === \"left\" || scrollDirection === \"up\";\n const scrollAmount = isBackward ? -scrollDistance : scrollDistance;\n const container = containerRef.current;\n if (!container) return;\n\n intervalRef.current = setInterval(() => {\n if (!container) return;\n\n const scrollPos = isHorizontal\n ? container.scrollLeft\n : container.scrollTop;\n const scrollSize = isHorizontal\n ? container.scrollWidth\n : container.scrollHeight;\n const clientSize = isHorizontal\n ? container.clientWidth\n : container.clientHeight;\n\n const canScroll = isBackward\n ? scrollPos > 0\n : scrollPos < scrollSize - clientSize;\n\n if (!canScroll) {\n stopScroll();\n return;\n }\n updateScrollButtons();\n container.scrollBy({\n [isHorizontal ? \"left\" : \"top\"]: scrollAmount,\n });\n }, scrollSpeed);\n };\n\n const updateScrollButtons = () => {\n const container = containerRef.current;\n if (!container) return;\n\n const canScrollLeftVal =\n container.scrollLeft > scrollEndPadding;\n const canScrollRightVal =\n container.scrollLeft <\n container.scrollWidth - container.clientWidth - scrollEndPadding;\n const canScrollUpVal = container.scrollTop > scrollEndPadding;\n const canScrollDownVal =\n container.scrollTop <\n container.scrollHeight - container.clientHeight - scrollEndPadding;\n\n setCanScrollLeft(canScrollLeftVal);\n setCanScrollRight(canScrollRightVal);\n setCanScrollUp(canScrollUpVal);\n setCanScrollDown(canScrollDownVal);\n };\n\n const stopScroll = () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n\n useEffect(() => {\n if (!forwardedRef) return;\n if (typeof forwardedRef === \"function\") {\n forwardedRef(containerRef.current);\n } else {\n forwardedRef.current = containerRef.current;\n }\n }, [forwardedRef]);\n\n useEffect(() => {\n updateScrollButtons();\n const container = containerRef.current;\n if (!container) return;\n\n const resizeObserver = new ResizeObserver(updateScrollButtons);\n resizeObserver.observe(container);\n\n return () => {\n resizeObserver.disconnect();\n stopScroll();\n };\n }, []);\n\n return (\n <div\n role=\"region\"\n aria-label=\"Scrollable content\"\n className={cn(\"flex relative overflow-hidden\", className)}\n {...props}\n >\n {showScrollIndicators && (\n <div className=\"absolute left-0 top-0 flex w-full h-full z-50 bg-transparent pointer-events-none\">\n {canScrollLeft && horizontalScrollIndicators && (\n <div\n className={cn(\n \"absolute h-full w-4 left-0 top-0 bg-gradient-to-l from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"left\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollRight && horizontalScrollIndicators && (\n <div\n className={cn(\n \"absolute h-full w-4 right-0 top-0 bg-gradient-to-r from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"right\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollUp && verticalScrollIndicators && (\n <div\n className={cn(\n \"absolute w-full h-5 left-0 top-0 bg-gradient-to-t from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"up\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollDown && verticalScrollIndicators && (\n <div\n className={cn(\n \"absolute w-full h-5 left-0 bottom-0 bg-gradient-to-b from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"down\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n </div>\n )}\n <div\n className={cn(\n \"flex relative overflow-auto w-full\",\n containerClassName\n )}\n ref={containerRef}\n onScroll={updateScrollButtons}\n >\n {children}\n </div>\n </div>\n );\n }\n);\n\nOverflowContainer.displayName = \"OverflowContainer\";\n\nexport default OverflowContainer;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OverflowContainer – React component for smooth scrolling with hover-activated
|
|
5
|
+
* indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
interface OverflowContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
/** Custom className for the outer container */
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Content to be rendered inside the scroll container */
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Speed of the scroll animation in milliseconds. Lower = faster. Positive only.
|
|
15
|
+
* @default 10
|
|
16
|
+
*/
|
|
17
|
+
scrollSpeed?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Distance to scroll in pixels per interval. Positive only.
|
|
20
|
+
* @default 10
|
|
21
|
+
*/
|
|
22
|
+
scrollDistance?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Padding in pixels before the end to stop scrolling. Positive only.
|
|
25
|
+
* @default 10
|
|
26
|
+
*/
|
|
27
|
+
scrollEndPadding?: number;
|
|
28
|
+
/** Custom className for the inner scrollable container */
|
|
29
|
+
containerClassName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to show scroll indicators when content overflows.
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
showScrollIndicators?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to show horizontal scroll indicators.
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
horizontalScrollIndicators?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to show vertical scroll indicators.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
verticalScrollIndicators?: boolean;
|
|
45
|
+
/** Custom className for scroll indicators (overrides default styles) */
|
|
46
|
+
indicatorClassName?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Whether to scroll when hovering over indicators.
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
scrollOnHover?: boolean;
|
|
52
|
+
}
|
|
53
|
+
declare const OverflowContainer: react.ForwardRefExoticComponent<OverflowContainerProps & react.RefAttributes<HTMLDivElement>>;
|
|
54
|
+
|
|
55
|
+
export { OverflowContainer, type OverflowContainerProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OverflowContainer – React component for smooth scrolling with hover-activated
|
|
5
|
+
* indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
interface OverflowContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
/** Custom className for the outer container */
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Content to be rendered inside the scroll container */
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Speed of the scroll animation in milliseconds. Lower = faster. Positive only.
|
|
15
|
+
* @default 10
|
|
16
|
+
*/
|
|
17
|
+
scrollSpeed?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Distance to scroll in pixels per interval. Positive only.
|
|
20
|
+
* @default 10
|
|
21
|
+
*/
|
|
22
|
+
scrollDistance?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Padding in pixels before the end to stop scrolling. Positive only.
|
|
25
|
+
* @default 10
|
|
26
|
+
*/
|
|
27
|
+
scrollEndPadding?: number;
|
|
28
|
+
/** Custom className for the inner scrollable container */
|
|
29
|
+
containerClassName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to show scroll indicators when content overflows.
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
showScrollIndicators?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to show horizontal scroll indicators.
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
horizontalScrollIndicators?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to show vertical scroll indicators.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
verticalScrollIndicators?: boolean;
|
|
45
|
+
/** Custom className for scroll indicators (overrides default styles) */
|
|
46
|
+
indicatorClassName?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Whether to scroll when hovering over indicators.
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
scrollOnHover?: boolean;
|
|
52
|
+
}
|
|
53
|
+
declare const OverflowContainer: react.ForwardRefExoticComponent<OverflowContainerProps & react.RefAttributes<HTMLDivElement>>;
|
|
54
|
+
|
|
55
|
+
export { OverflowContainer, type OverflowContainerProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/OverflowContainer.tsx
|
|
4
|
+
import {
|
|
5
|
+
forwardRef,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
function cn(...classes) {
|
|
12
|
+
return classes.filter(Boolean).join(" ");
|
|
13
|
+
}
|
|
14
|
+
var OverflowContainer = forwardRef(
|
|
15
|
+
({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
scrollSpeed = 10,
|
|
19
|
+
scrollDistance = 10,
|
|
20
|
+
scrollEndPadding = 10,
|
|
21
|
+
containerClassName,
|
|
22
|
+
showScrollIndicators = true,
|
|
23
|
+
horizontalScrollIndicators = true,
|
|
24
|
+
verticalScrollIndicators = false,
|
|
25
|
+
indicatorClassName,
|
|
26
|
+
scrollOnHover = true,
|
|
27
|
+
...props
|
|
28
|
+
}, forwardedRef) => {
|
|
29
|
+
const containerRef = useRef(null);
|
|
30
|
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
31
|
+
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
32
|
+
const [canScrollUp, setCanScrollUp] = useState(false);
|
|
33
|
+
const [canScrollDown, setCanScrollDown] = useState(false);
|
|
34
|
+
const intervalRef = useRef(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (process.env?.NODE_ENV === "development") {
|
|
37
|
+
if (scrollSpeed < 0) {
|
|
38
|
+
console.warn(
|
|
39
|
+
"OverflowContainer: scrollSpeed should be a positive number"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (scrollDistance < 0) {
|
|
43
|
+
console.warn(
|
|
44
|
+
"OverflowContainer: scrollDistance should be a positive number"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (scrollEndPadding < 0) {
|
|
48
|
+
console.warn(
|
|
49
|
+
"OverflowContainer: scrollEndPadding should be a positive number"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, [scrollSpeed, scrollDistance, scrollEndPadding]);
|
|
54
|
+
const startScroll = (scrollDirection) => {
|
|
55
|
+
stopScroll();
|
|
56
|
+
const isHorizontal = scrollDirection === "left" || scrollDirection === "right";
|
|
57
|
+
const isBackward = scrollDirection === "left" || scrollDirection === "up";
|
|
58
|
+
const scrollAmount = isBackward ? -scrollDistance : scrollDistance;
|
|
59
|
+
const container = containerRef.current;
|
|
60
|
+
if (!container) return;
|
|
61
|
+
intervalRef.current = setInterval(() => {
|
|
62
|
+
if (!container) return;
|
|
63
|
+
const scrollPos = isHorizontal ? container.scrollLeft : container.scrollTop;
|
|
64
|
+
const scrollSize = isHorizontal ? container.scrollWidth : container.scrollHeight;
|
|
65
|
+
const clientSize = isHorizontal ? container.clientWidth : container.clientHeight;
|
|
66
|
+
const canScroll = isBackward ? scrollPos > 0 : scrollPos < scrollSize - clientSize;
|
|
67
|
+
if (!canScroll) {
|
|
68
|
+
stopScroll();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
updateScrollButtons();
|
|
72
|
+
container.scrollBy({
|
|
73
|
+
[isHorizontal ? "left" : "top"]: scrollAmount
|
|
74
|
+
});
|
|
75
|
+
}, scrollSpeed);
|
|
76
|
+
};
|
|
77
|
+
const updateScrollButtons = () => {
|
|
78
|
+
const container = containerRef.current;
|
|
79
|
+
if (!container) return;
|
|
80
|
+
const canScrollLeftVal = container.scrollLeft > scrollEndPadding;
|
|
81
|
+
const canScrollRightVal = container.scrollLeft < container.scrollWidth - container.clientWidth - scrollEndPadding;
|
|
82
|
+
const canScrollUpVal = container.scrollTop > scrollEndPadding;
|
|
83
|
+
const canScrollDownVal = container.scrollTop < container.scrollHeight - container.clientHeight - scrollEndPadding;
|
|
84
|
+
setCanScrollLeft(canScrollLeftVal);
|
|
85
|
+
setCanScrollRight(canScrollRightVal);
|
|
86
|
+
setCanScrollUp(canScrollUpVal);
|
|
87
|
+
setCanScrollDown(canScrollDownVal);
|
|
88
|
+
};
|
|
89
|
+
const stopScroll = () => {
|
|
90
|
+
if (intervalRef.current) {
|
|
91
|
+
clearInterval(intervalRef.current);
|
|
92
|
+
intervalRef.current = null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!forwardedRef) return;
|
|
97
|
+
if (typeof forwardedRef === "function") {
|
|
98
|
+
forwardedRef(containerRef.current);
|
|
99
|
+
} else {
|
|
100
|
+
forwardedRef.current = containerRef.current;
|
|
101
|
+
}
|
|
102
|
+
}, [forwardedRef]);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
updateScrollButtons();
|
|
105
|
+
const container = containerRef.current;
|
|
106
|
+
if (!container) return;
|
|
107
|
+
const resizeObserver = new ResizeObserver(updateScrollButtons);
|
|
108
|
+
resizeObserver.observe(container);
|
|
109
|
+
return () => {
|
|
110
|
+
resizeObserver.disconnect();
|
|
111
|
+
stopScroll();
|
|
112
|
+
};
|
|
113
|
+
}, []);
|
|
114
|
+
return /* @__PURE__ */ jsxs(
|
|
115
|
+
"div",
|
|
116
|
+
{
|
|
117
|
+
role: "region",
|
|
118
|
+
"aria-label": "Scrollable content",
|
|
119
|
+
className: cn("flex relative overflow-hidden", className),
|
|
120
|
+
...props,
|
|
121
|
+
children: [
|
|
122
|
+
showScrollIndicators && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-0 flex w-full h-full z-50 bg-transparent pointer-events-none", children: [
|
|
123
|
+
canScrollLeft && horizontalScrollIndicators && /* @__PURE__ */ jsx(
|
|
124
|
+
"div",
|
|
125
|
+
{
|
|
126
|
+
className: cn(
|
|
127
|
+
"absolute h-full w-4 left-0 top-0 bg-gradient-to-l from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
128
|
+
indicatorClassName
|
|
129
|
+
),
|
|
130
|
+
onMouseEnter: () => scrollOnHover && startScroll("left"),
|
|
131
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
canScrollRight && horizontalScrollIndicators && /* @__PURE__ */ jsx(
|
|
135
|
+
"div",
|
|
136
|
+
{
|
|
137
|
+
className: cn(
|
|
138
|
+
"absolute h-full w-4 right-0 top-0 bg-gradient-to-r from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
139
|
+
indicatorClassName
|
|
140
|
+
),
|
|
141
|
+
onMouseEnter: () => scrollOnHover && startScroll("right"),
|
|
142
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
canScrollUp && verticalScrollIndicators && /* @__PURE__ */ jsx(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
className: cn(
|
|
149
|
+
"absolute w-full h-5 left-0 top-0 bg-gradient-to-t from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
150
|
+
indicatorClassName
|
|
151
|
+
),
|
|
152
|
+
onMouseEnter: () => scrollOnHover && startScroll("up"),
|
|
153
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
canScrollDown && verticalScrollIndicators && /* @__PURE__ */ jsx(
|
|
157
|
+
"div",
|
|
158
|
+
{
|
|
159
|
+
className: cn(
|
|
160
|
+
"absolute w-full h-5 left-0 bottom-0 bg-gradient-to-b from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto",
|
|
161
|
+
indicatorClassName
|
|
162
|
+
),
|
|
163
|
+
onMouseEnter: () => scrollOnHover && startScroll("down"),
|
|
164
|
+
onMouseLeave: () => scrollOnHover && stopScroll()
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
] }),
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
"div",
|
|
170
|
+
{
|
|
171
|
+
className: cn(
|
|
172
|
+
"flex relative overflow-auto w-full",
|
|
173
|
+
containerClassName
|
|
174
|
+
),
|
|
175
|
+
ref: containerRef,
|
|
176
|
+
onScroll: updateScrollButtons,
|
|
177
|
+
children
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
OverflowContainer.displayName = "OverflowContainer";
|
|
186
|
+
var OverflowContainer_default = OverflowContainer;
|
|
187
|
+
export {
|
|
188
|
+
OverflowContainer_default as OverflowContainer
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* OverflowContainer – React component for smooth scrolling with hover-activated
|
|
192
|
+
* indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
193
|
+
* @license MIT
|
|
194
|
+
*/
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/OverflowContainer.tsx"],"sourcesContent":["/**\n * OverflowContainer – React component for smooth scrolling with hover-activated\n * indicators when content overflows. Supports horizontal and vertical scrolling.\n * @license MIT\n */\n\nimport {\n type ForwardedRef,\n forwardRef,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\ndeclare const process: { env?: { NODE_ENV?: string } };\n\nfunction cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n\nexport interface OverflowContainerProps\n extends React.HTMLAttributes<HTMLDivElement> {\n /** Custom className for the outer container */\n className?: string;\n\n /** Content to be rendered inside the scroll container */\n children: React.ReactNode;\n\n /**\n * Speed of the scroll animation in milliseconds. Lower = faster. Positive only.\n * @default 10\n */\n scrollSpeed?: number;\n\n /**\n * Distance to scroll in pixels per interval. Positive only.\n * @default 10\n */\n scrollDistance?: number;\n\n /**\n * Padding in pixels before the end to stop scrolling. Positive only.\n * @default 10\n */\n scrollEndPadding?: number;\n\n /** Custom className for the inner scrollable container */\n containerClassName?: string;\n\n /**\n * Whether to show scroll indicators when content overflows.\n * @default true\n */\n showScrollIndicators?: boolean;\n\n /**\n * Whether to show horizontal scroll indicators.\n * @default true\n */\n horizontalScrollIndicators?: boolean;\n\n /**\n * Whether to show vertical scroll indicators.\n * @default false\n */\n verticalScrollIndicators?: boolean;\n\n /** Custom className for scroll indicators (overrides default styles) */\n indicatorClassName?: string;\n\n /**\n * Whether to scroll when hovering over indicators.\n * @default true\n */\n scrollOnHover?: boolean;\n}\n\nconst OverflowContainer = forwardRef(\n (\n {\n className,\n children,\n scrollSpeed = 10,\n scrollDistance = 10,\n scrollEndPadding = 10,\n containerClassName,\n showScrollIndicators = true,\n horizontalScrollIndicators = true,\n verticalScrollIndicators = false,\n indicatorClassName,\n scrollOnHover = true,\n ...props\n }: OverflowContainerProps,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n const [canScrollUp, setCanScrollUp] = useState(false);\n const [canScrollDown, setCanScrollDown] = useState(false);\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n useEffect(() => {\n if (process.env?.NODE_ENV === \"development\") {\n if (scrollSpeed < 0) {\n console.warn(\n \"OverflowContainer: scrollSpeed should be a positive number\"\n );\n }\n if (scrollDistance < 0) {\n console.warn(\n \"OverflowContainer: scrollDistance should be a positive number\"\n );\n }\n if (scrollEndPadding < 0) {\n console.warn(\n \"OverflowContainer: scrollEndPadding should be a positive number\"\n );\n }\n }\n }, [scrollSpeed, scrollDistance, scrollEndPadding]);\n\n const startScroll = (scrollDirection: \"left\" | \"right\" | \"up\" | \"down\") => {\n stopScroll();\n const isHorizontal =\n scrollDirection === \"left\" || scrollDirection === \"right\";\n const isBackward = scrollDirection === \"left\" || scrollDirection === \"up\";\n const scrollAmount = isBackward ? -scrollDistance : scrollDistance;\n const container = containerRef.current;\n if (!container) return;\n\n intervalRef.current = setInterval(() => {\n if (!container) return;\n\n const scrollPos = isHorizontal\n ? container.scrollLeft\n : container.scrollTop;\n const scrollSize = isHorizontal\n ? container.scrollWidth\n : container.scrollHeight;\n const clientSize = isHorizontal\n ? container.clientWidth\n : container.clientHeight;\n\n const canScroll = isBackward\n ? scrollPos > 0\n : scrollPos < scrollSize - clientSize;\n\n if (!canScroll) {\n stopScroll();\n return;\n }\n updateScrollButtons();\n container.scrollBy({\n [isHorizontal ? \"left\" : \"top\"]: scrollAmount,\n });\n }, scrollSpeed);\n };\n\n const updateScrollButtons = () => {\n const container = containerRef.current;\n if (!container) return;\n\n const canScrollLeftVal =\n container.scrollLeft > scrollEndPadding;\n const canScrollRightVal =\n container.scrollLeft <\n container.scrollWidth - container.clientWidth - scrollEndPadding;\n const canScrollUpVal = container.scrollTop > scrollEndPadding;\n const canScrollDownVal =\n container.scrollTop <\n container.scrollHeight - container.clientHeight - scrollEndPadding;\n\n setCanScrollLeft(canScrollLeftVal);\n setCanScrollRight(canScrollRightVal);\n setCanScrollUp(canScrollUpVal);\n setCanScrollDown(canScrollDownVal);\n };\n\n const stopScroll = () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n\n useEffect(() => {\n if (!forwardedRef) return;\n if (typeof forwardedRef === \"function\") {\n forwardedRef(containerRef.current);\n } else {\n forwardedRef.current = containerRef.current;\n }\n }, [forwardedRef]);\n\n useEffect(() => {\n updateScrollButtons();\n const container = containerRef.current;\n if (!container) return;\n\n const resizeObserver = new ResizeObserver(updateScrollButtons);\n resizeObserver.observe(container);\n\n return () => {\n resizeObserver.disconnect();\n stopScroll();\n };\n }, []);\n\n return (\n <div\n role=\"region\"\n aria-label=\"Scrollable content\"\n className={cn(\"flex relative overflow-hidden\", className)}\n {...props}\n >\n {showScrollIndicators && (\n <div className=\"absolute left-0 top-0 flex w-full h-full z-50 bg-transparent pointer-events-none\">\n {canScrollLeft && horizontalScrollIndicators && (\n <div\n className={cn(\n \"absolute h-full w-4 left-0 top-0 bg-gradient-to-l from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"left\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollRight && horizontalScrollIndicators && (\n <div\n className={cn(\n \"absolute h-full w-4 right-0 top-0 bg-gradient-to-r from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"right\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollUp && verticalScrollIndicators && (\n <div\n className={cn(\n \"absolute w-full h-5 left-0 top-0 bg-gradient-to-t from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"up\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollDown && verticalScrollIndicators && (\n <div\n className={cn(\n \"absolute w-full h-5 left-0 bottom-0 bg-gradient-to-b from-black/0 to-black/10 cursor-pointer z-50 pointer-events-auto\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"down\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n </div>\n )}\n <div\n className={cn(\n \"flex relative overflow-auto w-full\",\n containerClassName\n )}\n ref={containerRef}\n onScroll={updateScrollButtons}\n >\n {children}\n </div>\n </div>\n );\n }\n);\n\nOverflowContainer.displayName = \"OverflowContainer\";\n\nexport default OverflowContainer;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-scroll-indicators",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React overflow container with hover-activated scroll indicators",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"example": "npm run dev --prefix example"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=17.0.0",
|
|
27
|
+
"react-dom": ">=17.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/react": "^18.3.12",
|
|
31
|
+
"@types/react-dom": "^18.3.1",
|
|
32
|
+
"react": "^18.3.1",
|
|
33
|
+
"react-dom": "^18.3.1",
|
|
34
|
+
"tsup": "^8.3.5",
|
|
35
|
+
"typescript": "^5.6.3"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"react",
|
|
39
|
+
"scroll",
|
|
40
|
+
"overflow",
|
|
41
|
+
"indicators",
|
|
42
|
+
"container"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/ankitc248/react-scroll-indicators.git"
|
|
48
|
+
}
|
|
49
|
+
}
|