@synerise/ds-scrollbar 1.5.1 → 2.0.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/CHANGELOG.md +8 -0
- package/CLAUDE.md +135 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [2.0.0](https://github.com/Synerise/synerise-design/compare/@synerise/ds-scrollbar@1.5.2...@synerise/ds-scrollbar@2.0.0) (2026-08-26)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @synerise/ds-scrollbar
|
|
9
|
+
|
|
10
|
+
## [1.5.2](https://github.com/Synerise/synerise-design/compare/@synerise/ds-scrollbar@1.5.1...@synerise/ds-scrollbar@1.5.2) (2026-07-23)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @synerise/ds-scrollbar
|
|
13
|
+
|
|
6
14
|
## [1.5.1](https://github.com/Synerise/synerise-design/compare/@synerise/ds-scrollbar@1.5.0...@synerise/ds-scrollbar@1.5.1) (2026-06-17)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @synerise/ds-scrollbar
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Scrollbar (`@synerise/ds-scrollbar`)
|
|
2
|
+
|
|
3
|
+
> Custom scrollbar component with two rendering strategies: a PerfectScrollbar-based virtual mode and a fully custom drag-and-drop mode.
|
|
4
|
+
|
|
5
|
+
## Package structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
Scrollbar.tsx — main component (forwardRef, delegates to DnDScrollbar or VirtualScrollbar)
|
|
10
|
+
Scrollbar.types.ts — ScrollbarAdditionalProps, ScrollbarProps, VirtualScrollbarProps
|
|
11
|
+
Scrollbar.styles.tsx — ScrollbarContainer, LoaderWrapper, Loader
|
|
12
|
+
DnDScrollbar/
|
|
13
|
+
DnDScrollbar.tsx — custom scrollbar with mouse-drag thumb support
|
|
14
|
+
DnDScrollbar.styles.tsx — styled components for DnD variant
|
|
15
|
+
index.ts
|
|
16
|
+
VirtualScrollbar/
|
|
17
|
+
VirtualScrollbar.tsx — PerfectScrollbar wrapper with resize/animation awareness
|
|
18
|
+
VirtualScrollbar.styles.tsx
|
|
19
|
+
index.ts
|
|
20
|
+
__specs__/
|
|
21
|
+
Scrollbar.spec.tsx — Vitest/RTL tests
|
|
22
|
+
style/
|
|
23
|
+
index.less — imports ds-core variables, perfect-scrollbar CSS, mixin
|
|
24
|
+
scrollbar.mixin.less — overrides for .ps__rail-* and .ps__thumb-*, large-size variant
|
|
25
|
+
modules.d.ts — imports @testing-library/jest-dom
|
|
26
|
+
index.ts — public exports
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Public exports
|
|
30
|
+
|
|
31
|
+
### `Scrollbar` (default export)
|
|
32
|
+
|
|
33
|
+
The unified entry point. Renders `DnDScrollbar` when `withDnd={true}`, otherwise renders `VirtualScrollbar`. Accepts a `forwardRef` to the underlying scroll container (`HTMLElement`).
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Default | Description |
|
|
36
|
+
|------|------|---------|-------------|
|
|
37
|
+
| `absolute` | `boolean` | `false` | Removes padding offsets from the inner wrapper (scrollbar overlaps content) |
|
|
38
|
+
| `children` | `ReactNode` | — | Scrollable content |
|
|
39
|
+
| `classes` | `string` | — | Extra CSS class(es) applied to the inner content element |
|
|
40
|
+
| `className` | `string` | — | Class applied to the outermost `ScrollbarContainer` div |
|
|
41
|
+
| `confineScroll` | `boolean` | `false` | Prevents wheel events from propagating to parent scrollable areas |
|
|
42
|
+
| `fetchData` | `() => void` | — | Called when scroll reaches the Y end and `hasMore` is `true` and `loading` is falsy |
|
|
43
|
+
| `hasMore` | `boolean` | — | Gates whether `fetchData` is invoked on Y-end |
|
|
44
|
+
| `largeSize` | `boolean` | `false` | Renders a wider (16 px) thumb with blue tokens instead of the default 3 px grey thumb |
|
|
45
|
+
| `loading` | `boolean` | — | Overlays a semi-transparent mask and animated spinner; also dims inner content to `0.2` opacity |
|
|
46
|
+
| `maxHeight` | `string \| number` | — | CSS `max-height` on the scroll content element |
|
|
47
|
+
| `onScroll` | `(event: UIEvent) => void` | — | Fired on scroll |
|
|
48
|
+
| `onYReachEnd` | `() => void` | — | Called independently of `fetchData` whenever the Y end is reached |
|
|
49
|
+
| `overscrollBehavior` | `'auto' \| 'contain' \| 'none'` | `'contain'` | Sets CSS `overscroll-behavior` on the scroll container. Honored by both variants: virtual applies it to the inner `.ps` element, DnD applies it to its own `ScrollbarContainer` |
|
|
50
|
+
| `style` | `CSSProperties` | — | Inline styles for the inner wrapper div |
|
|
51
|
+
| `withDnd` | `boolean` | `false` | Use `DnDScrollbar` (custom thumb, drag support) instead of `VirtualScrollbar` |
|
|
52
|
+
|
|
53
|
+
### `VirtualScrollbarProps`
|
|
54
|
+
|
|
55
|
+
Extends `ScrollbarProps` with one additional prop:
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Description |
|
|
58
|
+
|------|------|---------|-------------|
|
|
59
|
+
| `scrollbarOptions` | `ScrollBarProps['options']` | — | Options forwarded to `@ofsajd/react-perfect-scrollbar`; `minScrollbarLength` is always forced to `48` |
|
|
60
|
+
|
|
61
|
+
### Named type exports
|
|
62
|
+
|
|
63
|
+
- `ScrollbarAdditionalProps` — all props except `children`
|
|
64
|
+
- `ScrollbarProps` — `ScrollbarAdditionalProps & { children?: ReactNode }`
|
|
65
|
+
- `VirtualScrollbarProps` — `ScrollbarProps & { scrollbarOptions?: ... }`
|
|
66
|
+
- `OverscrollBehavior` — `'auto' | 'contain' | 'none'`, value of the `overscrollBehavior` prop
|
|
67
|
+
|
|
68
|
+
## Usage patterns
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import Scrollbar from '@synerise/ds-scrollbar';
|
|
72
|
+
|
|
73
|
+
// Default virtual scrollbar
|
|
74
|
+
<Scrollbar maxHeight={400}>
|
|
75
|
+
{content}
|
|
76
|
+
</Scrollbar>
|
|
77
|
+
|
|
78
|
+
// With infinite scroll
|
|
79
|
+
<Scrollbar
|
|
80
|
+
maxHeight={400}
|
|
81
|
+
hasMore={hasMore}
|
|
82
|
+
loading={isLoading}
|
|
83
|
+
fetchData={loadNextPage}
|
|
84
|
+
>
|
|
85
|
+
{items}
|
|
86
|
+
</Scrollbar>
|
|
87
|
+
|
|
88
|
+
// Drag-and-drop custom scrollbar
|
|
89
|
+
<Scrollbar withDnd maxHeight={400}>
|
|
90
|
+
{content}
|
|
91
|
+
</Scrollbar>
|
|
92
|
+
|
|
93
|
+
// Large variant (accessible-style thumb)
|
|
94
|
+
<Scrollbar largeSize maxHeight={400}>
|
|
95
|
+
{content}
|
|
96
|
+
</Scrollbar>
|
|
97
|
+
|
|
98
|
+
// Allow scroll chaining to parent (default is 'contain')
|
|
99
|
+
<Scrollbar maxHeight={400} overscrollBehavior="auto">
|
|
100
|
+
{content}
|
|
101
|
+
</Scrollbar>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Styling
|
|
105
|
+
|
|
106
|
+
Two layers of styling:
|
|
107
|
+
|
|
108
|
+
1. **styled-components** — `ScrollbarContainer` (relative, `height: 100%`) wraps both mode variants. It owns the `.ps { overscroll-behavior: <overscrollBehavior>; }` rule, which is picked up by the virtual variant (perfect-scrollbar's `.ps` element). The DnD variant ships its own `ScrollbarContainer` that applies `overscroll-behavior` directly on itself. The prop defaults to `'contain'`, matching the behavior previously hard-coded in `scrollbar.mixin.less`. `LoaderWrapper` (absolute fill, `rgba(255,255,255,0.6)`) and a spinning `Loader` are rendered on top when `loading` is true.
|
|
109
|
+
2. **Less overrides** (`style/scrollbar.mixin.less`) — patches PerfectScrollbar's `.ps__rail-*` and `.ps__thumb-*` classes. The `.large-size` class (applied via `classnames` in `VirtualScrollbar`) activates the wide blue thumb variant.
|
|
110
|
+
|
|
111
|
+
**Token usage:**
|
|
112
|
+
- Default thumb: `grey-300` / `grey-500` (hover)
|
|
113
|
+
- Large thumb: `blue-050` / `blue-100` (hover), `grey-300` border / `grey-400` border (hover)
|
|
114
|
+
- Loading spinner in `Scrollbar.styles`: `grey-600`; in `DnDScrollbar.styles`: `blue-600`
|
|
115
|
+
|
|
116
|
+
The track in `DnDScrollbar` is hidden (`opacity: 0`) and revealed on parent hover via a styled-components selector chain.
|
|
117
|
+
|
|
118
|
+
## Key dependencies
|
|
119
|
+
|
|
120
|
+
- `@ofsajd/react-perfect-scrollbar` `1.0.0` — used only by `VirtualScrollbar`; this is a fork/mirror of `react-perfect-scrollbar`
|
|
121
|
+
- `@synerise/ds-utils` — `useCombinedRefs`, `useResizeObserver` (used in `VirtualScrollbar`)
|
|
122
|
+
- `@synerise/ds-icon` + `SpinnerM` — loading spinner icon
|
|
123
|
+
- `@synerise/ds-core` — `useTheme` for palette tokens; peer dependency
|
|
124
|
+
- `classnames` — conditional class composition in `VirtualScrollbar`
|
|
125
|
+
|
|
126
|
+
## Implementation notes
|
|
127
|
+
|
|
128
|
+
- `VirtualScrollbar` registers `transitionend`/`animationend` listeners on `document.body` (no cleanup deps array — runs after every render) to retrigger PerfectScrollbar geometry recalculation when the scrollbar becomes visible after a CSS animation.
|
|
129
|
+
- `DnDScrollbar` implements its own thumb-drag via `mousemove`/`mouseup`/`mouseleave` listeners on `document` and a `ResizeObserver` on the wrapper. Minimum thumb height is `48 px`; if `scrollHeight === clientHeight` (no overflow), `thumbHeight` is set to `0` (thumb hidden).
|
|
130
|
+
- `DnDScrollbar` does not accept `scrollbarOptions` — the prop is silently ignored because it is part of `VirtualScrollbarProps` only.
|
|
131
|
+
- `overscrollBehavior` is forwarded from the top-level `Scrollbar` down to whichever inner component is active. Virtual picks it up via the outer wrapper's `.ps` nested rule; DnD applies it on its own `ScrollbarContainer` directly.
|
|
132
|
+
- `confineScroll` behaviour differs between modes: `VirtualScrollbar` calls `event.preventDefault()` on `wheel` (blocks scroll propagation); `DnDScrollbar` calls `event.stopPropagation()` only.
|
|
133
|
+
- The `forwardRef` type is `HTMLElement` (broad); callers may need to cast to `HTMLDivElement` for precise access.
|
|
134
|
+
- The test runner is **Jest** (not Vitest) — `package.json` uses `"test": "jest"` and `jest.config.js` is present.
|
|
135
|
+
- `modules.d.ts` is named misleadingly — it only augments the test environment with `@testing-library/jest-dom` typings.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synerise/ds-scrollbar",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Scrollbar UI Component for the Synerise Design System",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"repository": "Synerise/synerise-design",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"/dist",
|
|
19
19
|
"CHANGELOG.md",
|
|
20
|
+
"CLAUDE.md",
|
|
20
21
|
"README.md",
|
|
21
22
|
"package.json",
|
|
22
23
|
"LICENSE.md"
|
|
@@ -42,8 +43,8 @@
|
|
|
42
43
|
"types": "dist/index.d.ts",
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"@ofsajd/react-perfect-scrollbar": "1.0.0",
|
|
45
|
-
"@synerise/ds-icon": "^
|
|
46
|
-
"@synerise/ds-utils": "^
|
|
46
|
+
"@synerise/ds-icon": "^2.0.0",
|
|
47
|
+
"@synerise/ds-utils": "^2.0.0",
|
|
47
48
|
"classnames": "^2.3.2"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"styled-components": "^5.3.3",
|
|
53
54
|
"vitest": "4"
|
|
54
55
|
},
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "033854a18f038079e9a16b0ff250f8401a2287c2"
|
|
56
57
|
}
|