react-scroll-indicators 0.1.0 → 0.2.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 +45 -5
- package/dist/index.cjs +7 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -10
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
- package/src/OverflowContainer.tsx +275 -0
- package/src/index.ts +2 -0
- package/src/overflow-container.css +68 -0
package/README.md
CHANGED
|
@@ -17,17 +17,25 @@ yarn add react-scroll-indicators
|
|
|
17
17
|
- `react` (>=17)
|
|
18
18
|
- `react-dom` (>=17)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
No Tailwind or other CSS framework is required. The component uses plain CSS with BEM-style class names (e.g. `overflow-container`, `overflow-container__inner`, `overflow-container__indicator--left`). Import the default styles once in your app, or override with your own CSS via `className` / `containerClassName` / `indicatorClassName`.
|
|
21
21
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
24
|
+
### 1. Import the default styles (once)
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import "react-scroll-indicators/styles";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Use the component
|
|
31
|
+
|
|
24
32
|
```tsx
|
|
25
33
|
import { OverflowContainer } from "react-scroll-indicators";
|
|
26
34
|
|
|
27
35
|
function MyComponent() {
|
|
28
36
|
return (
|
|
29
|
-
<OverflowContainer className="max-w-md h-48">
|
|
30
|
-
<div
|
|
37
|
+
<OverflowContainer className="max-w-md h-48" style={{ maxWidth: "28rem", height: "12rem" }}>
|
|
38
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
31
39
|
{items.map((item) => (
|
|
32
40
|
<Card key={item.id} {...item} />
|
|
33
41
|
))}
|
|
@@ -37,15 +45,32 @@ function MyComponent() {
|
|
|
37
45
|
}
|
|
38
46
|
```
|
|
39
47
|
|
|
48
|
+
### As a copyable component (shadcn-style)
|
|
49
|
+
|
|
50
|
+
You can use the component file directly so it lives in your codebase and you can edit it:
|
|
51
|
+
|
|
52
|
+
**Option A – Import the source file** (your bundler compiles it):
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import OverflowContainer from "react-scroll-indicators/OverflowContainer";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Option B – Copy the file into your project** (e.g. `components/ui/OverflowContainer.tsx`):
|
|
59
|
+
|
|
60
|
+
1. Copy from: `node_modules/react-scroll-indicators/src/OverflowContainer.tsx`
|
|
61
|
+
2. Paste into your project (e.g. `components/ui/OverflowContainer.tsx`).
|
|
62
|
+
3. Import from that path. The component only depends on `react`; you can keep or replace the small `cn()` helper.
|
|
63
|
+
|
|
40
64
|
### With vertical scroll
|
|
41
65
|
|
|
42
66
|
```tsx
|
|
43
67
|
<OverflowContainer
|
|
44
68
|
verticalScrollIndicators
|
|
45
69
|
horizontalScrollIndicators={false}
|
|
46
|
-
className="
|
|
70
|
+
className="my-container"
|
|
71
|
+
style={{ height: "16rem", width: "16rem" }}
|
|
47
72
|
>
|
|
48
|
-
<div
|
|
73
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>{/* tall content */}</div>
|
|
49
74
|
</OverflowContainer>
|
|
50
75
|
```
|
|
51
76
|
|
|
@@ -67,6 +92,21 @@ function MyComponent() {
|
|
|
67
92
|
|
|
68
93
|
All standard `div` HTML attributes are also supported (e.g. `style`, `aria-*`).
|
|
69
94
|
|
|
95
|
+
### Class names (for custom CSS)
|
|
96
|
+
|
|
97
|
+
If you skip the default styles and style the component yourself, use these class names:
|
|
98
|
+
|
|
99
|
+
| Class | Element |
|
|
100
|
+
|-------|--------|
|
|
101
|
+
| `overflow-container` | Outer wrapper |
|
|
102
|
+
| `overflow-container__inner` | Scrollable content area |
|
|
103
|
+
| `overflow-container__indicators` | Layer that holds the indicator overlays |
|
|
104
|
+
| `overflow-container__indicator` | Base class for each indicator |
|
|
105
|
+
| `overflow-container__indicator--left` | Left edge indicator |
|
|
106
|
+
| `overflow-container__indicator--right` | Right edge indicator |
|
|
107
|
+
| `overflow-container__indicator--up` | Top edge indicator |
|
|
108
|
+
| `overflow-container__indicator--down` | Bottom edge indicator |
|
|
109
|
+
|
|
70
110
|
## Build
|
|
71
111
|
|
|
72
112
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -136,15 +136,15 @@ var OverflowContainer = (0, import_react.forwardRef)(
|
|
|
136
136
|
{
|
|
137
137
|
role: "region",
|
|
138
138
|
"aria-label": "Scrollable content",
|
|
139
|
-
className: cn("
|
|
139
|
+
className: cn("overflow-container", className),
|
|
140
140
|
...props,
|
|
141
141
|
children: [
|
|
142
|
-
showScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "
|
|
142
|
+
showScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "overflow-container__indicators", children: [
|
|
143
143
|
canScrollLeft && horizontalScrollIndicators && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
144
144
|
"div",
|
|
145
145
|
{
|
|
146
146
|
className: cn(
|
|
147
|
-
"
|
|
147
|
+
"overflow-container__indicator overflow-container__indicator--left",
|
|
148
148
|
indicatorClassName
|
|
149
149
|
),
|
|
150
150
|
onMouseEnter: () => scrollOnHover && startScroll("left"),
|
|
@@ -155,7 +155,7 @@ var OverflowContainer = (0, import_react.forwardRef)(
|
|
|
155
155
|
"div",
|
|
156
156
|
{
|
|
157
157
|
className: cn(
|
|
158
|
-
"
|
|
158
|
+
"overflow-container__indicator overflow-container__indicator--right",
|
|
159
159
|
indicatorClassName
|
|
160
160
|
),
|
|
161
161
|
onMouseEnter: () => scrollOnHover && startScroll("right"),
|
|
@@ -166,7 +166,7 @@ var OverflowContainer = (0, import_react.forwardRef)(
|
|
|
166
166
|
"div",
|
|
167
167
|
{
|
|
168
168
|
className: cn(
|
|
169
|
-
"
|
|
169
|
+
"overflow-container__indicator overflow-container__indicator--up",
|
|
170
170
|
indicatorClassName
|
|
171
171
|
),
|
|
172
172
|
onMouseEnter: () => scrollOnHover && startScroll("up"),
|
|
@@ -177,7 +177,7 @@ var OverflowContainer = (0, import_react.forwardRef)(
|
|
|
177
177
|
"div",
|
|
178
178
|
{
|
|
179
179
|
className: cn(
|
|
180
|
-
"
|
|
180
|
+
"overflow-container__indicator overflow-container__indicator--down",
|
|
181
181
|
indicatorClassName
|
|
182
182
|
),
|
|
183
183
|
onMouseEnter: () => scrollOnHover && startScroll("down"),
|
|
@@ -188,10 +188,7 @@ var OverflowContainer = (0, import_react.forwardRef)(
|
|
|
188
188
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
189
189
|
"div",
|
|
190
190
|
{
|
|
191
|
-
className: cn(
|
|
192
|
-
"flex relative overflow-auto w-full",
|
|
193
|
-
containerClassName
|
|
194
|
-
),
|
|
191
|
+
className: cn("overflow-container__inner", containerClassName),
|
|
195
192
|
ref: containerRef,
|
|
196
193
|
onScroll: updateScrollButtons,
|
|
197
194
|
children
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
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(\"overflow-container\", className)}\n {...props}\n >\n {showScrollIndicators && (\n <div className=\"overflow-container__indicators\">\n {canScrollLeft && horizontalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--left\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"left\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollRight && horizontalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--right\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"right\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollUp && verticalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--up\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"up\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollDown && verticalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--down\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"down\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n </div>\n )}\n <div\n className={cn(\"overflow-container__inner\", containerClassName)}\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.js
CHANGED
|
@@ -116,15 +116,15 @@ var OverflowContainer = forwardRef(
|
|
|
116
116
|
{
|
|
117
117
|
role: "region",
|
|
118
118
|
"aria-label": "Scrollable content",
|
|
119
|
-
className: cn("
|
|
119
|
+
className: cn("overflow-container", className),
|
|
120
120
|
...props,
|
|
121
121
|
children: [
|
|
122
|
-
showScrollIndicators && /* @__PURE__ */ jsxs("div", { className: "
|
|
122
|
+
showScrollIndicators && /* @__PURE__ */ jsxs("div", { className: "overflow-container__indicators", children: [
|
|
123
123
|
canScrollLeft && horizontalScrollIndicators && /* @__PURE__ */ jsx(
|
|
124
124
|
"div",
|
|
125
125
|
{
|
|
126
126
|
className: cn(
|
|
127
|
-
"
|
|
127
|
+
"overflow-container__indicator overflow-container__indicator--left",
|
|
128
128
|
indicatorClassName
|
|
129
129
|
),
|
|
130
130
|
onMouseEnter: () => scrollOnHover && startScroll("left"),
|
|
@@ -135,7 +135,7 @@ var OverflowContainer = forwardRef(
|
|
|
135
135
|
"div",
|
|
136
136
|
{
|
|
137
137
|
className: cn(
|
|
138
|
-
"
|
|
138
|
+
"overflow-container__indicator overflow-container__indicator--right",
|
|
139
139
|
indicatorClassName
|
|
140
140
|
),
|
|
141
141
|
onMouseEnter: () => scrollOnHover && startScroll("right"),
|
|
@@ -146,7 +146,7 @@ var OverflowContainer = forwardRef(
|
|
|
146
146
|
"div",
|
|
147
147
|
{
|
|
148
148
|
className: cn(
|
|
149
|
-
"
|
|
149
|
+
"overflow-container__indicator overflow-container__indicator--up",
|
|
150
150
|
indicatorClassName
|
|
151
151
|
),
|
|
152
152
|
onMouseEnter: () => scrollOnHover && startScroll("up"),
|
|
@@ -157,7 +157,7 @@ var OverflowContainer = forwardRef(
|
|
|
157
157
|
"div",
|
|
158
158
|
{
|
|
159
159
|
className: cn(
|
|
160
|
-
"
|
|
160
|
+
"overflow-container__indicator overflow-container__indicator--down",
|
|
161
161
|
indicatorClassName
|
|
162
162
|
),
|
|
163
163
|
onMouseEnter: () => scrollOnHover && startScroll("down"),
|
|
@@ -168,10 +168,7 @@ var OverflowContainer = forwardRef(
|
|
|
168
168
|
/* @__PURE__ */ jsx(
|
|
169
169
|
"div",
|
|
170
170
|
{
|
|
171
|
-
className: cn(
|
|
172
|
-
"flex relative overflow-auto w-full",
|
|
173
|
-
containerClassName
|
|
174
|
-
),
|
|
171
|
+
className: cn("overflow-container__inner", containerClassName),
|
|
175
172
|
ref: containerRef,
|
|
176
173
|
onScroll: updateScrollButtons,
|
|
177
174
|
children
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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":[]}
|
|
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(\"overflow-container\", className)}\n {...props}\n >\n {showScrollIndicators && (\n <div className=\"overflow-container__indicators\">\n {canScrollLeft && horizontalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--left\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"left\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollRight && horizontalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--right\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"right\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollUp && verticalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--up\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"up\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n {canScrollDown && verticalScrollIndicators && (\n <div\n className={cn(\n \"overflow-container__indicator overflow-container__indicator--down\",\n indicatorClassName\n )}\n onMouseEnter={() => scrollOnHover && startScroll(\"down\")}\n onMouseLeave={() => scrollOnHover && stopScroll()}\n />\n )}\n </div>\n )}\n <div\n className={cn(\"overflow-container__inner\", containerClassName)}\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-scroll-indicators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React overflow container with hover-activated scroll indicators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -11,11 +11,21 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./OverflowContainer": {
|
|
16
|
+
"types": "./src/OverflowContainer.tsx",
|
|
17
|
+
"import": "./src/OverflowContainer.tsx",
|
|
18
|
+
"default": "./src/OverflowContainer.tsx"
|
|
19
|
+
},
|
|
20
|
+
"./styles": {
|
|
21
|
+
"import": "./src/overflow-container.css",
|
|
22
|
+
"default": "./src/overflow-container.css"
|
|
14
23
|
}
|
|
15
24
|
},
|
|
16
25
|
"files": [
|
|
17
26
|
"dist",
|
|
18
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"src"
|
|
19
29
|
],
|
|
20
30
|
"scripts": {
|
|
21
31
|
"build": "tsup",
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OverflowContainer – React component for smooth scrolling with hover-activated
|
|
3
|
+
* indicators when content overflows. Supports horizontal and vertical scrolling.
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type ForwardedRef,
|
|
9
|
+
forwardRef,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from "react";
|
|
14
|
+
|
|
15
|
+
declare const process: { env?: { NODE_ENV?: string } };
|
|
16
|
+
|
|
17
|
+
function cn(...classes: (string | undefined | null | false)[]): string {
|
|
18
|
+
return classes.filter(Boolean).join(" ");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OverflowContainerProps
|
|
22
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
/** Custom className for the outer container */
|
|
24
|
+
className?: string;
|
|
25
|
+
|
|
26
|
+
/** Content to be rendered inside the scroll container */
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Speed of the scroll animation in milliseconds. Lower = faster. Positive only.
|
|
31
|
+
* @default 10
|
|
32
|
+
*/
|
|
33
|
+
scrollSpeed?: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Distance to scroll in pixels per interval. Positive only.
|
|
37
|
+
* @default 10
|
|
38
|
+
*/
|
|
39
|
+
scrollDistance?: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Padding in pixels before the end to stop scrolling. Positive only.
|
|
43
|
+
* @default 10
|
|
44
|
+
*/
|
|
45
|
+
scrollEndPadding?: number;
|
|
46
|
+
|
|
47
|
+
/** Custom className for the inner scrollable container */
|
|
48
|
+
containerClassName?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether to show scroll indicators when content overflows.
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
showScrollIndicators?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether to show horizontal scroll indicators.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
horizontalScrollIndicators?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether to show vertical scroll indicators.
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
verticalScrollIndicators?: boolean;
|
|
67
|
+
|
|
68
|
+
/** Custom className for scroll indicators (overrides default styles) */
|
|
69
|
+
indicatorClassName?: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether to scroll when hovering over indicators.
|
|
73
|
+
* @default true
|
|
74
|
+
*/
|
|
75
|
+
scrollOnHover?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const OverflowContainer = forwardRef(
|
|
79
|
+
(
|
|
80
|
+
{
|
|
81
|
+
className,
|
|
82
|
+
children,
|
|
83
|
+
scrollSpeed = 10,
|
|
84
|
+
scrollDistance = 10,
|
|
85
|
+
scrollEndPadding = 10,
|
|
86
|
+
containerClassName,
|
|
87
|
+
showScrollIndicators = true,
|
|
88
|
+
horizontalScrollIndicators = true,
|
|
89
|
+
verticalScrollIndicators = false,
|
|
90
|
+
indicatorClassName,
|
|
91
|
+
scrollOnHover = true,
|
|
92
|
+
...props
|
|
93
|
+
}: OverflowContainerProps,
|
|
94
|
+
forwardedRef: ForwardedRef<HTMLDivElement>
|
|
95
|
+
) => {
|
|
96
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
97
|
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
98
|
+
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
99
|
+
const [canScrollUp, setCanScrollUp] = useState(false);
|
|
100
|
+
const [canScrollDown, setCanScrollDown] = useState(false);
|
|
101
|
+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (process.env?.NODE_ENV === "development") {
|
|
105
|
+
if (scrollSpeed < 0) {
|
|
106
|
+
console.warn(
|
|
107
|
+
"OverflowContainer: scrollSpeed should be a positive number"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (scrollDistance < 0) {
|
|
111
|
+
console.warn(
|
|
112
|
+
"OverflowContainer: scrollDistance should be a positive number"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (scrollEndPadding < 0) {
|
|
116
|
+
console.warn(
|
|
117
|
+
"OverflowContainer: scrollEndPadding should be a positive number"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}, [scrollSpeed, scrollDistance, scrollEndPadding]);
|
|
122
|
+
|
|
123
|
+
const startScroll = (scrollDirection: "left" | "right" | "up" | "down") => {
|
|
124
|
+
stopScroll();
|
|
125
|
+
const isHorizontal =
|
|
126
|
+
scrollDirection === "left" || scrollDirection === "right";
|
|
127
|
+
const isBackward = scrollDirection === "left" || scrollDirection === "up";
|
|
128
|
+
const scrollAmount = isBackward ? -scrollDistance : scrollDistance;
|
|
129
|
+
const container = containerRef.current;
|
|
130
|
+
if (!container) return;
|
|
131
|
+
|
|
132
|
+
intervalRef.current = setInterval(() => {
|
|
133
|
+
if (!container) return;
|
|
134
|
+
|
|
135
|
+
const scrollPos = isHorizontal
|
|
136
|
+
? container.scrollLeft
|
|
137
|
+
: container.scrollTop;
|
|
138
|
+
const scrollSize = isHorizontal
|
|
139
|
+
? container.scrollWidth
|
|
140
|
+
: container.scrollHeight;
|
|
141
|
+
const clientSize = isHorizontal
|
|
142
|
+
? container.clientWidth
|
|
143
|
+
: container.clientHeight;
|
|
144
|
+
|
|
145
|
+
const canScroll = isBackward
|
|
146
|
+
? scrollPos > 0
|
|
147
|
+
: scrollPos < scrollSize - clientSize;
|
|
148
|
+
|
|
149
|
+
if (!canScroll) {
|
|
150
|
+
stopScroll();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
updateScrollButtons();
|
|
154
|
+
container.scrollBy({
|
|
155
|
+
[isHorizontal ? "left" : "top"]: scrollAmount,
|
|
156
|
+
});
|
|
157
|
+
}, scrollSpeed);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const updateScrollButtons = () => {
|
|
161
|
+
const container = containerRef.current;
|
|
162
|
+
if (!container) return;
|
|
163
|
+
|
|
164
|
+
const canScrollLeftVal =
|
|
165
|
+
container.scrollLeft > scrollEndPadding;
|
|
166
|
+
const canScrollRightVal =
|
|
167
|
+
container.scrollLeft <
|
|
168
|
+
container.scrollWidth - container.clientWidth - scrollEndPadding;
|
|
169
|
+
const canScrollUpVal = container.scrollTop > scrollEndPadding;
|
|
170
|
+
const canScrollDownVal =
|
|
171
|
+
container.scrollTop <
|
|
172
|
+
container.scrollHeight - container.clientHeight - scrollEndPadding;
|
|
173
|
+
|
|
174
|
+
setCanScrollLeft(canScrollLeftVal);
|
|
175
|
+
setCanScrollRight(canScrollRightVal);
|
|
176
|
+
setCanScrollUp(canScrollUpVal);
|
|
177
|
+
setCanScrollDown(canScrollDownVal);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const stopScroll = () => {
|
|
181
|
+
if (intervalRef.current) {
|
|
182
|
+
clearInterval(intervalRef.current);
|
|
183
|
+
intervalRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!forwardedRef) return;
|
|
189
|
+
if (typeof forwardedRef === "function") {
|
|
190
|
+
forwardedRef(containerRef.current);
|
|
191
|
+
} else {
|
|
192
|
+
forwardedRef.current = containerRef.current;
|
|
193
|
+
}
|
|
194
|
+
}, [forwardedRef]);
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
updateScrollButtons();
|
|
198
|
+
const container = containerRef.current;
|
|
199
|
+
if (!container) return;
|
|
200
|
+
|
|
201
|
+
const resizeObserver = new ResizeObserver(updateScrollButtons);
|
|
202
|
+
resizeObserver.observe(container);
|
|
203
|
+
|
|
204
|
+
return () => {
|
|
205
|
+
resizeObserver.disconnect();
|
|
206
|
+
stopScroll();
|
|
207
|
+
};
|
|
208
|
+
}, []);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div
|
|
212
|
+
role="region"
|
|
213
|
+
aria-label="Scrollable content"
|
|
214
|
+
className={cn("overflow-container", className)}
|
|
215
|
+
{...props}
|
|
216
|
+
>
|
|
217
|
+
{showScrollIndicators && (
|
|
218
|
+
<div className="overflow-container__indicators">
|
|
219
|
+
{canScrollLeft && horizontalScrollIndicators && (
|
|
220
|
+
<div
|
|
221
|
+
className={cn(
|
|
222
|
+
"overflow-container__indicator overflow-container__indicator--left",
|
|
223
|
+
indicatorClassName
|
|
224
|
+
)}
|
|
225
|
+
onMouseEnter={() => scrollOnHover && startScroll("left")}
|
|
226
|
+
onMouseLeave={() => scrollOnHover && stopScroll()}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
{canScrollRight && horizontalScrollIndicators && (
|
|
230
|
+
<div
|
|
231
|
+
className={cn(
|
|
232
|
+
"overflow-container__indicator overflow-container__indicator--right",
|
|
233
|
+
indicatorClassName
|
|
234
|
+
)}
|
|
235
|
+
onMouseEnter={() => scrollOnHover && startScroll("right")}
|
|
236
|
+
onMouseLeave={() => scrollOnHover && stopScroll()}
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
{canScrollUp && verticalScrollIndicators && (
|
|
240
|
+
<div
|
|
241
|
+
className={cn(
|
|
242
|
+
"overflow-container__indicator overflow-container__indicator--up",
|
|
243
|
+
indicatorClassName
|
|
244
|
+
)}
|
|
245
|
+
onMouseEnter={() => scrollOnHover && startScroll("up")}
|
|
246
|
+
onMouseLeave={() => scrollOnHover && stopScroll()}
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
{canScrollDown && verticalScrollIndicators && (
|
|
250
|
+
<div
|
|
251
|
+
className={cn(
|
|
252
|
+
"overflow-container__indicator overflow-container__indicator--down",
|
|
253
|
+
indicatorClassName
|
|
254
|
+
)}
|
|
255
|
+
onMouseEnter={() => scrollOnHover && startScroll("down")}
|
|
256
|
+
onMouseLeave={() => scrollOnHover && stopScroll()}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
<div
|
|
262
|
+
className={cn("overflow-container__inner", containerClassName)}
|
|
263
|
+
ref={containerRef}
|
|
264
|
+
onScroll={updateScrollButtons}
|
|
265
|
+
>
|
|
266
|
+
{children}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
OverflowContainer.displayName = "OverflowContainer";
|
|
274
|
+
|
|
275
|
+
export default OverflowContainer;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-scroll-indicators – default styles for OverflowContainer
|
|
3
|
+
* Import once in your app: import "react-scroll-indicators/styles";
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.overflow-container {
|
|
7
|
+
display: flex;
|
|
8
|
+
position: relative;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.overflow-container__inner {
|
|
13
|
+
display: flex;
|
|
14
|
+
position: relative;
|
|
15
|
+
overflow: auto;
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.overflow-container__indicators {
|
|
20
|
+
position: absolute;
|
|
21
|
+
left: 0;
|
|
22
|
+
top: 0;
|
|
23
|
+
display: flex;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
z-index: 50;
|
|
27
|
+
background: transparent;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.overflow-container__indicator {
|
|
32
|
+
position: absolute;
|
|
33
|
+
z-index: 50;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
pointer-events: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.overflow-container__indicator--left {
|
|
39
|
+
left: 0;
|
|
40
|
+
top: 0;
|
|
41
|
+
width: 16px;
|
|
42
|
+
height: 100%;
|
|
43
|
+
background: linear-gradient(to left, transparent, rgba(0, 0, 0, 0.1));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.overflow-container__indicator--right {
|
|
47
|
+
right: 0;
|
|
48
|
+
top: 0;
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 100%;
|
|
51
|
+
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.1));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.overflow-container__indicator--up {
|
|
55
|
+
left: 0;
|
|
56
|
+
top: 0;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 20px;
|
|
59
|
+
background: linear-gradient(to top, transparent, rgba(0, 0, 0, 0.1));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.overflow-container__indicator--down {
|
|
63
|
+
left: 0;
|
|
64
|
+
bottom: 0;
|
|
65
|
+
width: 100%;
|
|
66
|
+
height: 20px;
|
|
67
|
+
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));
|
|
68
|
+
}
|