Skip to content

Commit a1c7a1f

Browse files
authored
feat(DualScrollSync)!: adopt compound-components API alongside items prop (#15)
BREAKING CHANGE: DualScrollSync now supports a declarative compound-components API in addition to the existing `items` prop pattern. feat(DualScrollSync): add DualScrollSyncContent component feat(DualScrollSync): add DualScrollSyncContentSection component feat(DualScrollSync): add DualScrollSyncNav component feat(DualScrollSync): add DualScrollSyncNavItem component feat(DualScrollSync): add DualScrollSyncLabel component feat(hooks): add useDualScrollSyncContext for accessing scroll synchronization context feat(hooks): implement useValidateChildren to validate Nav and Content sections feat(types): introduce types for content, navigation, and labels perf(DualScrollSync): refactor component structure and improve test coverage style(DualScrollSync): add new SCSS modules for content, navigation, and labels refactor(hooks): simplify type definitions and improve key handling in IntersectionObserver
1 parent 0a9bb14 commit a1c7a1f

File tree

57 files changed

+1315
-464
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1315
-464
lines changed

.github/workflows/release-please.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ jobs:
1212
- uses: googleapis/release-please-action@v4
1313
with:
1414
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
15-
release-type: node
15+
config-file: release-please-config.json
16+
manifest-file: .release-please-manifest.json

CHANGELOG.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,36 @@
22

33
## [1.2.3](https://github.com/dorixdev/react-dual-scroll-sync/compare/v1.2.2...v1.2.3) (2025-08-27)
44

5+
### ⚡ Performance Improvements
56

6-
### Performance Improvements
7-
8-
* dual-scroll-styles ([#13](https://github.com/dorixdev/react-dual-scroll-sync/issues/13)) ([d3d4855](https://github.com/dorixdev/react-dual-scroll-sync/commit/d3d48551a0f07a9c4fe75a92838ea56a06c6d5f6))
7+
- dual-scroll-styles ([#13](https://github.com/dorixdev/react-dual-scroll-sync/issues/13)) ([d3d4855](https://github.com/dorixdev/react-dual-scroll-sync/commit/d3d48551a0f07a9c4fe75a92838ea56a06c6d5f6))
98

109
## [1.2.2](https://github.com/dorixdev/react-dual-scroll-sync/compare/v1.2.1...v1.2.2) (2025-08-27)
1110

11+
### 🐛 Bug Fixes
1212

13-
### Bug Fixes
14-
15-
* sort versions and add title ([#11](https://github.com/dorixdev/react-dual-scroll-sync/issues/11)) ([2b22b66](https://github.com/dorixdev/react-dual-scroll-sync/commit/2b22b66a08c4b0c372c03c5e6a4c5d3349667d15))
13+
- sort versions and add title ([#11](https://github.com/dorixdev/react-dual-scroll-sync/issues/11)) ([2b22b66](https://github.com/dorixdev/react-dual-scroll-sync/commit/2b22b66a08c4b0c372c03c5e6a4c5d3349667d15))
1614

1715
## [1.2.1](https://github.com/dorixdev/react-dual-scroll-sync/compare/v1.2.0...v1.2.1) (2025-08-27)
1816

19-
### Bug Fixes
17+
### 🐛 Bug Fixes
2018

2119
- remove --if-present flag ([#9](https://github.com/dorixdev/react-dual-scroll-sync/issues/9)) ([b6b9a10](https://github.com/dorixdev/react-dual-scroll-sync/commit/b6b9a101d28c4eb0dd0b7f34e02ce0efbb3a59c5))
2220

2321
## [1.2.0](https://github.com/dorixdev/react-dual-scroll-sync/compare/v1.1.2...v1.2.0) (2025-08-27)
2422

25-
### Features
23+
### Features
2624

2725
- add Storybook manager config and improve component previews ([#3](https://github.com/dorixdev/react-dual-scroll-sync/issues/3)) ([6a13f9f](https://github.com/dorixdev/react-dual-scroll-sync/commit/6a13f9f311c9eaff823569e24e99f6c758af5f94))
2826

29-
### Bug Fixes
27+
### 🐛 Bug Fixes
3028

3129
- **ci:** replace GITHUB_TOKEN to RELEASE_PLEASE_TOKEN ([#4](https://github.com/dorixdev/react-dual-scroll-sync/issues/4)) ([df8e4d1](https://github.com/dorixdev/react-dual-scroll-sync/commit/df8e4d1159c3be7f28bbb5196a0cfa7c773ab9c8))
3230
- **CI:** update release-please action ([#7](https://github.com/dorixdev/react-dual-scroll-sync/issues/7)) ([8e35746](https://github.com/dorixdev/react-dual-scroll-sync/commit/8e357464505bdf716115566dab70348205511045))
3331
- **DualScrollSync:** add title attribute for better accessibility ([6a13f9f](https://github.com/dorixdev/react-dual-scroll-sync/commit/6a13f9f311c9eaff823569e24e99f6c758af5f94))
3432
- **DualScrollSync:** refine nav item styles and active indicator ([6a13f9f](https://github.com/dorixdev/react-dual-scroll-sync/commit/6a13f9f311c9eaff823569e24e99f6c758af5f94))
3533

36-
### Performance Improvements
34+
### Performance Improvements
3735

3836
- **storybook:** update stories with new mock data (internal only) ([6a13f9f](https://github.com/dorixdev/react-dual-scroll-sync/commit/6a13f9f311c9eaff823569e24e99f6c758af5f94))
3937

README.md

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ A lightweight React library to synchronize a vertical navigation menu with scrol
88
[![bundle size](https://img.shields.io/bundlephobia/minzip/@dorixdev/react-dual-scroll-sync?label=size&logo=webpack)](https://bundlephobia.com/package/@dorixdev/react-dual-scroll-sync)
99
[![license](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
1010

11+
## 📑 Table of Contents
12+
13+
- [Features](#-features)
14+
- [Demo](#-demo)
15+
- [Installation](#-installation)
16+
- [Styles](#-styles)
17+
- [Quick Start](#-quick-start)
18+
- [Props Overview](#-props-overview)
19+
- [Customization](#-customization)
20+
- [Docs](#-documentation)
21+
1122
## 💡 Features
1223

1324
- 🔗 **Sync state** between a vertical nav and the currently visible content section
@@ -49,17 +60,55 @@ const items = [
4960
];
5061

5162
export default function Demo() {
52-
return (
53-
<DualScrollSync
54-
id="filters"
55-
items={items}
56-
maxVisibleItems={6}
57-
onItemClick={(key) => console.log('Selected', key)}
58-
/>
59-
);
63+
return <DualScrollSync items={items} />;
6064
}
6165
```
6266

67+
## 🧩 Usage Patterns
68+
69+
DualScrollSync can be used in two main ways: **data-driven** and **declarative**.
70+
71+
### Data-Driven
72+
73+
Define your sections in an array and let the component generate both nav items and content.
74+
75+
✅ Best for dynamic data (e.g. from CMS or API).
76+
77+
```tsx
78+
const items = [
79+
{ sectionKey: 'intro', label: 'Introduction', children: <p>...</p> },
80+
{ sectionKey: 'details', label: 'Details', children: <p>...</p> }
81+
];
82+
83+
<DualScrollSync items={items} onItemClick={(k) => console.log(k)} />;
84+
```
85+
86+
### Declarative
87+
88+
Write the structure directly in JSX using `DualScrollSync.NavItem` and `DualScrollSync.ContentSection`.
89+
90+
✅ Best for static pages where you want **full control**.
91+
92+
```tsx
93+
<DualScrollSync>
94+
<DualScrollSync.Nav>
95+
<DualScrollSync.NavItem sectionKey="a">Section A</DualScrollSync.NavItem>
96+
<DualScrollSync.NavItem sectionKey="b">Section B</DualScrollSync.NavItem>
97+
</DualScrollSync.Nav>
98+
99+
<DualScrollSync.Content>
100+
<DualScrollSync.ContentSection sectionKey="a">...</DualScrollSync.ContentSection>
101+
<DualScrollSync.ContentSection sectionKey="b">...</DualScrollSync.ContentSection>
102+
</DualScrollSync.Content>
103+
</DualScrollSync>
104+
```
105+
106+
## ⚖️ When to use
107+
108+
✅ Long scrollable pages with sticky nav
109+
✅ Catalog filters, docs sidebars, multi-section layouts
110+
❌ Very short pages (anchors may suffice)
111+
63112
## 📘 Documentation
64113

65114
Explore all props, variations, and usage guidelines in the [Storybook docs](https://react-dual-scroll-sync.vercel.app).

eslint.config.js

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,34 @@ import tseslint from 'typescript-eslint';
1111
import { globalIgnores } from 'eslint/config';
1212

1313
export default tseslint.config(
14-
[
15-
globalIgnores(['dist']),
16-
{
17-
files: ['**/*.{ts,tsx}'],
18-
plugins: {
19-
import: importPlugin,
20-
'simple-import-sort': simpleImportSort
21-
},
22-
extends: [
23-
js.configs.recommended,
24-
tseslint.configs.recommended,
25-
reactHooks.configs['recommended-latest'],
26-
reactRefresh.configs.vite
27-
],
28-
languageOptions: {
29-
ecmaVersion: 2020,
30-
globals: globals.browser
31-
},
32-
rules: {
33-
'import/extensions': 'off',
34-
'import/first': 'error',
35-
'import/newline-after-import': 'error',
36-
'import/no-duplicates': 'error',
37-
'simple-import-sort/exports': 'error',
38-
'simple-import-sort/imports': 'error'
39-
}
40-
}
41-
],
42-
storybook.configs['flat/recommended']
14+
[
15+
globalIgnores(['dist', 'storybook-static']),
16+
{
17+
files: ['**/*.{ts,tsx}'],
18+
plugins: {
19+
import: importPlugin,
20+
'simple-import-sort': simpleImportSort
21+
},
22+
extends: [
23+
js.configs.recommended,
24+
tseslint.configs.recommended,
25+
reactHooks.configs['recommended-latest'],
26+
reactRefresh.configs.vite
27+
],
28+
languageOptions: {
29+
ecmaVersion: 2020,
30+
globals: globals.browser
31+
},
32+
rules: {
33+
'import/extensions': 'off',
34+
'import/first': 'error',
35+
'import/newline-after-import': 'error',
36+
'import/no-duplicates': 'error',
37+
'simple-import-sort/exports': 'error',
38+
'simple-import-sort/imports': 'error',
39+
'@typescript-eslint/consistent-type-imports': 'error'
40+
}
41+
}
42+
],
43+
storybook.configs['flat/recommended']
4344
);
Lines changed: 1 addition & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@use '../../scss/tokens.scss' as *;
1+
@use '@/scss/tokens.scss' as *;
22

33
.scrollSync {
44
overflow: hidden;
@@ -8,116 +8,3 @@
88
border: 1px solid var(--dual-scroll-sync-border-color, #{$border-color});
99
border-radius: var(--dual-scroll-sync-border-radius, #{$border-radius});
1010
}
11-
12-
.scrollSyncNav {
13-
overflow-y: auto;
14-
width: 100%;
15-
max-width: var(--dual-scroll-sync-max-width-nav, #{$max-width-nav});
16-
17-
&::-webkit-scrollbar {
18-
display: none;
19-
}
20-
}
21-
22-
.scrollSyncNavItem {
23-
cursor: pointer;
24-
25-
display: flex;
26-
align-items: center;
27-
28-
width: 100%;
29-
height: calc(100% / var(--menu-nav-visible-count));
30-
padding: var(--dual-scroll-sync-space-m, #{$space-m}) var(--dual-scroll-sync-space-s, #{$space-s});
31-
border: none;
32-
border-bottom: 1px solid var(--dual-scroll-sync-border-color, #{$border-color});
33-
34-
text-align: left;
35-
36-
background-color: var(
37-
--dual-scroll-sync-inactive-background-color,
38-
#{$inactive-background-color}
39-
);
40-
41-
transition: var(--dual-scroll-sync-transition, #{$transition});
42-
43-
&:first-child {
44-
border-top-left-radius: var(--dual-scroll-sync-border-radius, #{$border-radius});
45-
}
46-
47-
&:last-child {
48-
border-bottom: none;
49-
border-bottom-left-radius: var(--dual-scroll-sync-border-radius, #{$border-radius});
50-
}
51-
}
52-
53-
.scrollSyncNavItemLabel,
54-
.scrollSyncContentSectionLabel {
55-
overflow: hidden;
56-
display: -webkit-box;
57-
-webkit-box-orient: vertical;
58-
-webkit-line-clamp: 2;
59-
line-clamp: 2;
60-
}
61-
62-
.scrollSyncNavItemLabel {
63-
margin: auto 0;
64-
padding: 0 var(--dual-scroll-sync-space-m, #{$space-m});
65-
font-size: smaller;
66-
}
67-
68-
.scrollSyncContentSectionLabel {
69-
padding: var(--dual-scroll-sync-space-s, #{$space-s}) 0;
70-
font-weight: bold;
71-
}
72-
73-
.scrollSyncNavItemActive {
74-
position: relative;
75-
background-color: var(
76-
--dual-scroll-sync-highlight-background-color,
77-
#{$highlight-background-color}
78-
);
79-
80-
span {
81-
font-weight: bold;
82-
color: var(--dual-scroll-sync-highlight-foreground-color, #{$highlight-foreground-color});
83-
84-
&::before {
85-
content: '';
86-
87-
position: absolute;
88-
top: 0;
89-
left: 0;
90-
91-
height: 100%;
92-
border-left: 4px solid
93-
var(--dual-scroll-sync-highlight-foreground-color, #{$highlight-foreground-color});
94-
}
95-
}
96-
}
97-
98-
.scrollSyncContent {
99-
overflow-x: hidden;
100-
overflow-y: auto;
101-
flex: 1;
102-
gap: var(--dual-scroll-sync-space-m, #{$space-m});
103-
104-
height: 100%;
105-
padding: var(--dual-scroll-sync-space-s, #{$space-s});
106-
107-
background-color: var(
108-
--dual-scroll-sync-highlight-background-color,
109-
#{$highlight-background-color}
110-
);
111-
}
112-
113-
.scrollSyncContentSection {
114-
display: flex;
115-
flex-direction: column;
116-
gap: var(--dual-scroll-sync-space-l, #{$space-l});
117-
118-
height: auto;
119-
padding: var(--dual-scroll-sync-space-m, #{$space-m});
120-
border-radius: var(--dual-scroll-sync-border-radius, #{$border-radius});
121-
122-
transition: background-color 0.3s ease-in-out;
123-
}

0 commit comments

Comments
 (0)