morphing-toc 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Antoine Pirard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # morphing-toc
2
+
3
+ A React table of contents component that displays minimal vertical lines and morphs into a full navigation menu on hover.
4
+
5
+ ## Features
6
+
7
+ - Minimal visual footprint with vertical lines
8
+ - Smooth morph animation on hover
9
+ - Auto-extracts headings from DOM
10
+ - Generates unique IDs for headings
11
+ - Smooth scrolling with configurable offset
12
+ - Fully customizable colors and sizes
13
+ - TypeScript support
14
+ - Works with any React framework
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install morphing-toc
20
+ ```
21
+
22
+ ## Peer Dependencies
23
+
24
+ This package requires the following peer dependencies:
25
+
26
+ - `react` >= 18.0.0
27
+ - `react-dom` >= 18.0.0
28
+ - `motion` >= 11.0.0
29
+
30
+ ## Quick Start
31
+
32
+ ```tsx
33
+ import { MorphingToc } from 'morphing-toc';
34
+
35
+ function BlogPost() {
36
+ return (
37
+ <div>
38
+ <MorphingToc />
39
+ <article>
40
+ <h1>My Blog Post</h1>
41
+ <h2>Introduction</h2>
42
+ <p>...</p>
43
+ <h2>Main Content</h2>
44
+ <h3>Subsection</h3>
45
+ <p>...</p>
46
+ </article>
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ## Props
53
+
54
+ | Prop | Type | Default | Description |
55
+ |------|------|---------|-------------|
56
+ | `className` | `string` | `''` | Custom CSS class for the container |
57
+ | `scrollOffset` | `number` | `80` | Offset from top when scrolling (px) |
58
+ | `headingLevels` | `number[]` | `[2, 3, 4]` | Which heading levels to include |
59
+ | `skipFirstH1` | `boolean` | `true` | Skip the first h1 (page title) |
60
+ | `containerSelector` | `string` | `undefined` | CSS selector to scope heading search |
61
+ | `colors` | `MorphingTocColors` | See below | Custom color configuration |
62
+ | `sizes` | `MorphingTocSizes` | See below | Custom size configuration |
63
+
64
+ ## Customization
65
+
66
+ ### Custom Colors
67
+
68
+ ```tsx
69
+ <MorphingToc
70
+ colors={{
71
+ line: {
72
+ h1: '#1e40af',
73
+ h2: '#3b82f6',
74
+ h3: '#93c5fd',
75
+ },
76
+ menu: {
77
+ background: 'rgba(30, 64, 175, 0.95)',
78
+ border: 'rgba(59, 130, 246, 0.3)',
79
+ text: '#ffffff',
80
+ textHover: '#bfdbfe',
81
+ itemHover: 'rgba(59, 130, 246, 0.2)',
82
+ },
83
+ }}
84
+ />
85
+ ```
86
+
87
+ ### Custom Sizes
88
+
89
+ ```tsx
90
+ <MorphingToc
91
+ sizes={{
92
+ lineWidth: {
93
+ h1: '2rem',
94
+ h2: '1.5rem',
95
+ h3: '1rem',
96
+ },
97
+ lineHeight: '2px',
98
+ lineGap: '0.75rem',
99
+ menuWidth: '20rem',
100
+ }}
101
+ />
102
+ ```
103
+
104
+ ### Scoped to Container
105
+
106
+ ```tsx
107
+ <MorphingToc
108
+ containerSelector="#article-content"
109
+ headingLevels={[2, 3]}
110
+ />
111
+ ```
112
+
113
+ ## Exports
114
+
115
+ ```tsx
116
+ import {
117
+ MorphingToc, // Main component
118
+ useTocItems, // Hook for heading extraction
119
+ scrollToSection, // Scroll utility
120
+ } from 'morphing-toc';
121
+
122
+ // Type exports
123
+ import type {
124
+ MorphingTocProps,
125
+ MorphingTocColors,
126
+ MorphingTocSizes,
127
+ TocItem,
128
+ UseTocItemsOptions,
129
+ } from 'morphing-toc';
130
+ ```
131
+
132
+ ## useTocItems Hook
133
+
134
+ For custom implementations, you can use the `useTocItems` hook directly:
135
+
136
+ ```tsx
137
+ import { useTocItems, scrollToSection } from 'morphing-toc';
138
+
139
+ function CustomToc() {
140
+ const items = useTocItems({
141
+ headingLevels: [2, 3],
142
+ skipFirstH1: true,
143
+ containerSelector: '#content',
144
+ });
145
+
146
+ return (
147
+ <nav>
148
+ {items.map((item) => (
149
+ <button
150
+ key={item.id}
151
+ onClick={() => scrollToSection(item.id, 80)}
152
+ >
153
+ {item.title}
154
+ </button>
155
+ ))}
156
+ </nav>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ## Browser Support
162
+
163
+ - Chrome (latest)
164
+ - Firefox (latest)
165
+ - Safari (latest)
166
+ - Edge (latest)
167
+
168
+ ## License
169
+
170
+ MIT
171
+
172
+ ## Author
173
+
174
+ [Antoine Pirard](https://antoinepirard.com)
@@ -0,0 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { M as MorphingTocProps } from './types-Dol3SnRo.mjs';
3
+
4
+ declare function MorphingToc({ className, scrollOffset, headingLevels, skipFirstH1, containerSelector, colors, sizes, }: MorphingTocProps): react_jsx_runtime.JSX.Element | null;
5
+
6
+ export { MorphingToc };
@@ -0,0 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { M as MorphingTocProps } from './types-Dol3SnRo.js';
3
+
4
+ declare function MorphingToc({ className, scrollOffset, headingLevels, skipFirstH1, containerSelector, colors, sizes, }: MorphingTocProps): react_jsx_runtime.JSX.Element | null;
5
+
6
+ export { MorphingToc };
@@ -0,0 +1,229 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var react$1 = require('motion/react');
7
+ var useTocItems = require('./useTocItems');
8
+ var scrollToSection = require('./scrollToSection');
9
+
10
+ function MenuItem({ item, indent, colors, onClick }) {
11
+ const [isHovered, setIsHovered] = react.useState(false);
12
+ return /* @__PURE__ */ jsxRuntime.jsx(
13
+ "button",
14
+ {
15
+ onClick,
16
+ onMouseEnter: () => setIsHovered(true),
17
+ onMouseLeave: () => setIsHovered(false),
18
+ className: "block w-full text-left px-2 py-1 rounded text-sm cursor-pointer",
19
+ style: {
20
+ paddingLeft: `${8 + indent}px`,
21
+ color: isHovered ? colors.textHover : colors.text,
22
+ backgroundColor: isHovered ? colors.itemHover : "transparent"
23
+ },
24
+ tabIndex: -1,
25
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: item.title })
26
+ }
27
+ );
28
+ }
29
+ const defaultColors = {
30
+ line: {
31
+ h1: "#475569",
32
+ h2: "#94a3b8",
33
+ h3: "#cbd5e1",
34
+ h4: "#cbd5e1",
35
+ default: "#cbd5e1"
36
+ },
37
+ menu: {
38
+ background: "rgba(255, 255, 255, 0.95)",
39
+ border: "rgba(203, 213, 225, 0.3)",
40
+ text: "#475569",
41
+ textHover: "#0f172a",
42
+ itemHover: "#f8fafc"
43
+ }
44
+ };
45
+ const defaultSizes = {
46
+ lineWidth: {
47
+ h1: "1.5rem",
48
+ h2: "1rem",
49
+ h3: "0.75rem",
50
+ h4: "0.5rem",
51
+ default: "0.5rem"
52
+ },
53
+ lineHeight: "1px",
54
+ lineGap: "0.5rem",
55
+ menuWidth: "16rem"
56
+ };
57
+ function getLineColor(level, colors = {}) {
58
+ const merged = { ...defaultColors.line, ...colors };
59
+ switch (level) {
60
+ case 1:
61
+ return merged.h1;
62
+ case 2:
63
+ return merged.h2;
64
+ case 3:
65
+ return merged.h3;
66
+ case 4:
67
+ return merged.h4;
68
+ default:
69
+ return merged.default;
70
+ }
71
+ }
72
+ function getLineWidth(level, sizes = {}) {
73
+ const merged = { ...defaultSizes.lineWidth, ...sizes };
74
+ switch (level) {
75
+ case 1:
76
+ return merged.h1;
77
+ case 2:
78
+ return merged.h2;
79
+ case 3:
80
+ return merged.h3;
81
+ case 4:
82
+ return merged.h4;
83
+ default:
84
+ return merged.default;
85
+ }
86
+ }
87
+ function MorphingToc({
88
+ className = "",
89
+ scrollOffset = 80,
90
+ headingLevels = [2, 3, 4],
91
+ skipFirstH1 = true,
92
+ containerSelector,
93
+ colors = {},
94
+ sizes = {}
95
+ }) {
96
+ const [isHovered, setIsHovered] = react.useState(false);
97
+ const tocItems = useTocItems.useTocItems({
98
+ headingLevels,
99
+ skipFirstH1,
100
+ containerSelector
101
+ });
102
+ const mergedColors = {
103
+ line: { ...defaultColors.line, ...colors.line },
104
+ menu: { ...defaultColors.menu, ...colors.menu }
105
+ };
106
+ const mergedSizes = {
107
+ lineWidth: { ...defaultSizes.lineWidth, ...sizes.lineWidth },
108
+ lineHeight: sizes.lineHeight ?? defaultSizes.lineHeight,
109
+ lineGap: sizes.lineGap ?? defaultSizes.lineGap,
110
+ menuWidth: sizes.menuWidth ?? defaultSizes.menuWidth
111
+ };
112
+ if (tocItems.length === 0) return null;
113
+ const handleClick = (id) => {
114
+ scrollToSection.scrollToSection(id, scrollOffset);
115
+ };
116
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fixed left-0 top-1/2 -translate-y-1/2 z-40 hidden lg:block ${className}`, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(
118
+ "div",
119
+ {
120
+ className: "absolute -left-20 top-1/2 -translate-y-1/2 w-32 h-96",
121
+ onMouseEnter: () => setIsHovered(true),
122
+ onMouseLeave: () => setIsHovered(false)
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsxRuntime.jsx(
126
+ react$1.motion.div,
127
+ {
128
+ className: "relative",
129
+ initial: { opacity: 0, x: -20 },
130
+ animate: { opacity: 1, x: 0 },
131
+ transition: { duration: 0.3, delay: 0.5 },
132
+ onMouseEnter: () => setIsHovered(true),
133
+ onMouseLeave: () => setIsHovered(false),
134
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-2 md:pl-4 lg:pl-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
135
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: !isHovered && /* @__PURE__ */ jsxRuntime.jsx(
136
+ react$1.motion.div,
137
+ {
138
+ initial: { opacity: 0 },
139
+ animate: { opacity: 1 },
140
+ exit: {
141
+ opacity: 0,
142
+ x: 20,
143
+ transition: { duration: 0.15, ease: "easeOut" }
144
+ },
145
+ transition: { duration: 0.15 },
146
+ className: "absolute top-1/2 left-0 -translate-y-1/2",
147
+ style: { display: "flex", flexDirection: "column", gap: mergedSizes.lineGap },
148
+ children: tocItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
149
+ react$1.motion.button,
150
+ {
151
+ onClick: () => handleClick(item.id),
152
+ className: "block rounded-sm transition-opacity duration-150 hover:opacity-70",
153
+ style: {
154
+ width: getLineWidth(item.level, mergedSizes.lineWidth),
155
+ height: mergedSizes.lineHeight,
156
+ backgroundColor: getLineColor(item.level, mergedColors.line)
157
+ },
158
+ whileTap: { scale: 0.95 },
159
+ tabIndex: -1,
160
+ animate: {
161
+ x: isHovered ? 20 : 0,
162
+ transition: {
163
+ duration: 0.15,
164
+ delay: index * 0.02,
165
+ ease: "easeOut"
166
+ }
167
+ }
168
+ },
169
+ item.id
170
+ ))
171
+ }
172
+ ) }),
173
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: isHovered && /* @__PURE__ */ jsxRuntime.jsx(
174
+ react$1.motion.div,
175
+ {
176
+ initial: {
177
+ opacity: 0,
178
+ x: -20,
179
+ scale: 0.9,
180
+ borderRadius: 0
181
+ },
182
+ animate: {
183
+ opacity: 1,
184
+ x: 0,
185
+ scale: 1,
186
+ borderRadius: 6
187
+ },
188
+ exit: {
189
+ opacity: 0,
190
+ x: -20,
191
+ scale: 0.9,
192
+ transition: { duration: 0.15, ease: "easeOut" }
193
+ },
194
+ transition: {
195
+ duration: 0.2,
196
+ delay: 0.05,
197
+ ease: "easeOut"
198
+ },
199
+ className: "absolute top-1/2 left-0 -translate-y-1/2 backdrop-blur-sm shadow-md p-3",
200
+ style: {
201
+ width: mergedSizes.menuWidth,
202
+ backgroundColor: mergedColors.menu.background,
203
+ boxShadow: `0 0 0 1px ${mergedColors.menu.border}`
204
+ },
205
+ children: /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "space-y-0.5", children: tocItems.map((item) => {
206
+ const baseLevel = Math.min(...headingLevels);
207
+ const indent = (item.level - baseLevel) * 12;
208
+ return /* @__PURE__ */ jsxRuntime.jsx(
209
+ MenuItem,
210
+ {
211
+ item,
212
+ indent,
213
+ colors: mergedColors.menu,
214
+ onClick: () => handleClick(item.id)
215
+ },
216
+ item.id
217
+ );
218
+ }) })
219
+ }
220
+ ) })
221
+ ] }) })
222
+ }
223
+ )
224
+ ] });
225
+ }
226
+
227
+ exports.MorphingToc = MorphingToc;
228
+ //# sourceMappingURL=MorphingToc.js.map
229
+ //# sourceMappingURL=MorphingToc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/MorphingToc.tsx"],"names":["useState","jsx","useTocItems","scrollToSection","jsxs","motion","AnimatePresence"],"mappings":"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","file":"MorphingToc.js","sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { useTocItems } from './useTocItems';\nimport { scrollToSection } from './scrollToSection';\nimport type { MorphingTocProps, MorphingTocColors, MorphingTocSizes, TocItem } from './types';\n\ninterface MenuItemProps {\n item: TocItem;\n indent: number;\n colors: NonNullable<MorphingTocColors['menu']>;\n onClick: () => void;\n}\n\nfunction MenuItem({ item, indent, colors, onClick }: MenuItemProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n return (\n <button\n onClick={onClick}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n className=\"block w-full text-left px-2 py-1 rounded text-sm cursor-pointer\"\n style={{\n paddingLeft: `${8 + indent}px`,\n color: isHovered ? colors.textHover : colors.text,\n backgroundColor: isHovered ? colors.itemHover : 'transparent',\n }}\n tabIndex={-1}\n >\n <span className=\"block truncate\">{item.title}</span>\n </button>\n );\n}\n\nconst defaultColors: Required<MorphingTocColors> = {\n line: {\n h1: '#475569',\n h2: '#94a3b8',\n h3: '#cbd5e1',\n h4: '#cbd5e1',\n default: '#cbd5e1',\n },\n menu: {\n background: 'rgba(255, 255, 255, 0.95)',\n border: 'rgba(203, 213, 225, 0.3)',\n text: '#475569',\n textHover: '#0f172a',\n itemHover: '#f8fafc',\n },\n};\n\nconst defaultSizes: Required<MorphingTocSizes> = {\n lineWidth: {\n h1: '1.5rem',\n h2: '1rem',\n h3: '0.75rem',\n h4: '0.5rem',\n default: '0.5rem',\n },\n lineHeight: '1px',\n lineGap: '0.5rem',\n menuWidth: '16rem',\n};\n\nfunction getLineColor(level: number, colors: MorphingTocColors['line'] = {}): string {\n const merged = { ...defaultColors.line, ...colors };\n switch (level) {\n case 1:\n return merged.h1!;\n case 2:\n return merged.h2!;\n case 3:\n return merged.h3!;\n case 4:\n return merged.h4!;\n default:\n return merged.default!;\n }\n}\n\nfunction getLineWidth(level: number, sizes: MorphingTocSizes['lineWidth'] = {}): string {\n const merged = { ...defaultSizes.lineWidth, ...sizes };\n switch (level) {\n case 1:\n return merged.h1!;\n case 2:\n return merged.h2!;\n case 3:\n return merged.h3!;\n case 4:\n return merged.h4!;\n default:\n return merged.default!;\n }\n}\n\nexport function MorphingToc({\n className = '',\n scrollOffset = 80,\n headingLevels = [2, 3, 4],\n skipFirstH1 = true,\n containerSelector,\n colors = {},\n sizes = {},\n}: MorphingTocProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n const tocItems = useTocItems({\n headingLevels,\n skipFirstH1,\n containerSelector,\n });\n\n const mergedColors = {\n line: { ...defaultColors.line, ...colors.line },\n menu: { ...defaultColors.menu, ...colors.menu },\n };\n\n const mergedSizes = {\n lineWidth: { ...defaultSizes.lineWidth, ...sizes.lineWidth },\n lineHeight: sizes.lineHeight ?? defaultSizes.lineHeight,\n lineGap: sizes.lineGap ?? defaultSizes.lineGap,\n menuWidth: sizes.menuWidth ?? defaultSizes.menuWidth,\n };\n\n if (tocItems.length === 0) return null;\n\n const handleClick = (id: string) => {\n scrollToSection(id, scrollOffset);\n };\n\n return (\n <div className={`fixed left-0 top-1/2 -translate-y-1/2 z-40 hidden lg:block ${className}`}>\n {/* Invisible hover area for better UX */}\n <div\n className=\"absolute -left-20 top-1/2 -translate-y-1/2 w-32 h-96\"\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n />\n\n <motion.div\n className=\"relative\"\n initial={{ opacity: 0, x: -20 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ duration: 0.3, delay: 0.5 }}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <div className=\"pl-2 md:pl-4 lg:pl-6\">\n <div className=\"relative\">\n {/* Collapsed state: minimal lines */}\n <AnimatePresence>\n {!isHovered && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{\n opacity: 0,\n x: 20,\n transition: { duration: 0.15, ease: 'easeOut' },\n }}\n transition={{ duration: 0.15 }}\n className=\"absolute top-1/2 left-0 -translate-y-1/2\"\n style={{ display: 'flex', flexDirection: 'column', gap: mergedSizes.lineGap }}\n >\n {tocItems.map((item, index) => (\n <motion.button\n key={item.id}\n onClick={() => handleClick(item.id)}\n className=\"block rounded-sm transition-opacity duration-150 hover:opacity-70\"\n style={{\n width: getLineWidth(item.level, mergedSizes.lineWidth),\n height: mergedSizes.lineHeight,\n backgroundColor: getLineColor(item.level, mergedColors.line),\n }}\n whileTap={{ scale: 0.95 }}\n tabIndex={-1}\n animate={{\n x: isHovered ? 20 : 0,\n transition: {\n duration: 0.15,\n delay: index * 0.02,\n ease: 'easeOut',\n },\n }}\n />\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Expanded state: full menu */}\n <AnimatePresence>\n {isHovered && (\n <motion.div\n initial={{\n opacity: 0,\n x: -20,\n scale: 0.9,\n borderRadius: 0,\n }}\n animate={{\n opacity: 1,\n x: 0,\n scale: 1,\n borderRadius: 6,\n }}\n exit={{\n opacity: 0,\n x: -20,\n scale: 0.9,\n transition: { duration: 0.15, ease: 'easeOut' },\n }}\n transition={{\n duration: 0.2,\n delay: 0.05,\n ease: 'easeOut',\n }}\n className=\"absolute top-1/2 left-0 -translate-y-1/2 backdrop-blur-sm shadow-md p-3\"\n style={{\n width: mergedSizes.menuWidth,\n backgroundColor: mergedColors.menu.background,\n boxShadow: `0 0 0 1px ${mergedColors.menu.border}`,\n }}\n >\n <nav className=\"space-y-0.5\">\n {tocItems.map((item) => {\n const baseLevel = Math.min(...headingLevels);\n const indent = (item.level - baseLevel) * 12;\n\n return (\n <MenuItem\n key={item.id}\n item={item}\n indent={indent}\n colors={mergedColors.menu}\n onClick={() => handleClick(item.id)}\n />\n );\n })}\n </nav>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n </motion.div>\n </div>\n );\n}\n"]}
@@ -0,0 +1,227 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { motion, AnimatePresence } from 'motion/react';
5
+ import { useTocItems } from './useTocItems';
6
+ import { scrollToSection } from './scrollToSection';
7
+
8
+ function MenuItem({ item, indent, colors, onClick }) {
9
+ const [isHovered, setIsHovered] = useState(false);
10
+ return /* @__PURE__ */ jsx(
11
+ "button",
12
+ {
13
+ onClick,
14
+ onMouseEnter: () => setIsHovered(true),
15
+ onMouseLeave: () => setIsHovered(false),
16
+ className: "block w-full text-left px-2 py-1 rounded text-sm cursor-pointer",
17
+ style: {
18
+ paddingLeft: `${8 + indent}px`,
19
+ color: isHovered ? colors.textHover : colors.text,
20
+ backgroundColor: isHovered ? colors.itemHover : "transparent"
21
+ },
22
+ tabIndex: -1,
23
+ children: /* @__PURE__ */ jsx("span", { className: "block truncate", children: item.title })
24
+ }
25
+ );
26
+ }
27
+ const defaultColors = {
28
+ line: {
29
+ h1: "#475569",
30
+ h2: "#94a3b8",
31
+ h3: "#cbd5e1",
32
+ h4: "#cbd5e1",
33
+ default: "#cbd5e1"
34
+ },
35
+ menu: {
36
+ background: "rgba(255, 255, 255, 0.95)",
37
+ border: "rgba(203, 213, 225, 0.3)",
38
+ text: "#475569",
39
+ textHover: "#0f172a",
40
+ itemHover: "#f8fafc"
41
+ }
42
+ };
43
+ const defaultSizes = {
44
+ lineWidth: {
45
+ h1: "1.5rem",
46
+ h2: "1rem",
47
+ h3: "0.75rem",
48
+ h4: "0.5rem",
49
+ default: "0.5rem"
50
+ },
51
+ lineHeight: "1px",
52
+ lineGap: "0.5rem",
53
+ menuWidth: "16rem"
54
+ };
55
+ function getLineColor(level, colors = {}) {
56
+ const merged = { ...defaultColors.line, ...colors };
57
+ switch (level) {
58
+ case 1:
59
+ return merged.h1;
60
+ case 2:
61
+ return merged.h2;
62
+ case 3:
63
+ return merged.h3;
64
+ case 4:
65
+ return merged.h4;
66
+ default:
67
+ return merged.default;
68
+ }
69
+ }
70
+ function getLineWidth(level, sizes = {}) {
71
+ const merged = { ...defaultSizes.lineWidth, ...sizes };
72
+ switch (level) {
73
+ case 1:
74
+ return merged.h1;
75
+ case 2:
76
+ return merged.h2;
77
+ case 3:
78
+ return merged.h3;
79
+ case 4:
80
+ return merged.h4;
81
+ default:
82
+ return merged.default;
83
+ }
84
+ }
85
+ function MorphingToc({
86
+ className = "",
87
+ scrollOffset = 80,
88
+ headingLevels = [2, 3, 4],
89
+ skipFirstH1 = true,
90
+ containerSelector,
91
+ colors = {},
92
+ sizes = {}
93
+ }) {
94
+ const [isHovered, setIsHovered] = useState(false);
95
+ const tocItems = useTocItems({
96
+ headingLevels,
97
+ skipFirstH1,
98
+ containerSelector
99
+ });
100
+ const mergedColors = {
101
+ line: { ...defaultColors.line, ...colors.line },
102
+ menu: { ...defaultColors.menu, ...colors.menu }
103
+ };
104
+ const mergedSizes = {
105
+ lineWidth: { ...defaultSizes.lineWidth, ...sizes.lineWidth },
106
+ lineHeight: sizes.lineHeight ?? defaultSizes.lineHeight,
107
+ lineGap: sizes.lineGap ?? defaultSizes.lineGap,
108
+ menuWidth: sizes.menuWidth ?? defaultSizes.menuWidth
109
+ };
110
+ if (tocItems.length === 0) return null;
111
+ const handleClick = (id) => {
112
+ scrollToSection(id, scrollOffset);
113
+ };
114
+ return /* @__PURE__ */ jsxs("div", { className: `fixed left-0 top-1/2 -translate-y-1/2 z-40 hidden lg:block ${className}`, children: [
115
+ /* @__PURE__ */ jsx(
116
+ "div",
117
+ {
118
+ className: "absolute -left-20 top-1/2 -translate-y-1/2 w-32 h-96",
119
+ onMouseEnter: () => setIsHovered(true),
120
+ onMouseLeave: () => setIsHovered(false)
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(
124
+ motion.div,
125
+ {
126
+ className: "relative",
127
+ initial: { opacity: 0, x: -20 },
128
+ animate: { opacity: 1, x: 0 },
129
+ transition: { duration: 0.3, delay: 0.5 },
130
+ onMouseEnter: () => setIsHovered(true),
131
+ onMouseLeave: () => setIsHovered(false),
132
+ children: /* @__PURE__ */ jsx("div", { className: "pl-2 md:pl-4 lg:pl-6", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
133
+ /* @__PURE__ */ jsx(AnimatePresence, { children: !isHovered && /* @__PURE__ */ jsx(
134
+ motion.div,
135
+ {
136
+ initial: { opacity: 0 },
137
+ animate: { opacity: 1 },
138
+ exit: {
139
+ opacity: 0,
140
+ x: 20,
141
+ transition: { duration: 0.15, ease: "easeOut" }
142
+ },
143
+ transition: { duration: 0.15 },
144
+ className: "absolute top-1/2 left-0 -translate-y-1/2",
145
+ style: { display: "flex", flexDirection: "column", gap: mergedSizes.lineGap },
146
+ children: tocItems.map((item, index) => /* @__PURE__ */ jsx(
147
+ motion.button,
148
+ {
149
+ onClick: () => handleClick(item.id),
150
+ className: "block rounded-sm transition-opacity duration-150 hover:opacity-70",
151
+ style: {
152
+ width: getLineWidth(item.level, mergedSizes.lineWidth),
153
+ height: mergedSizes.lineHeight,
154
+ backgroundColor: getLineColor(item.level, mergedColors.line)
155
+ },
156
+ whileTap: { scale: 0.95 },
157
+ tabIndex: -1,
158
+ animate: {
159
+ x: isHovered ? 20 : 0,
160
+ transition: {
161
+ duration: 0.15,
162
+ delay: index * 0.02,
163
+ ease: "easeOut"
164
+ }
165
+ }
166
+ },
167
+ item.id
168
+ ))
169
+ }
170
+ ) }),
171
+ /* @__PURE__ */ jsx(AnimatePresence, { children: isHovered && /* @__PURE__ */ jsx(
172
+ motion.div,
173
+ {
174
+ initial: {
175
+ opacity: 0,
176
+ x: -20,
177
+ scale: 0.9,
178
+ borderRadius: 0
179
+ },
180
+ animate: {
181
+ opacity: 1,
182
+ x: 0,
183
+ scale: 1,
184
+ borderRadius: 6
185
+ },
186
+ exit: {
187
+ opacity: 0,
188
+ x: -20,
189
+ scale: 0.9,
190
+ transition: { duration: 0.15, ease: "easeOut" }
191
+ },
192
+ transition: {
193
+ duration: 0.2,
194
+ delay: 0.05,
195
+ ease: "easeOut"
196
+ },
197
+ className: "absolute top-1/2 left-0 -translate-y-1/2 backdrop-blur-sm shadow-md p-3",
198
+ style: {
199
+ width: mergedSizes.menuWidth,
200
+ backgroundColor: mergedColors.menu.background,
201
+ boxShadow: `0 0 0 1px ${mergedColors.menu.border}`
202
+ },
203
+ children: /* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: tocItems.map((item) => {
204
+ const baseLevel = Math.min(...headingLevels);
205
+ const indent = (item.level - baseLevel) * 12;
206
+ return /* @__PURE__ */ jsx(
207
+ MenuItem,
208
+ {
209
+ item,
210
+ indent,
211
+ colors: mergedColors.menu,
212
+ onClick: () => handleClick(item.id)
213
+ },
214
+ item.id
215
+ );
216
+ }) })
217
+ }
218
+ ) })
219
+ ] }) })
220
+ }
221
+ )
222
+ ] });
223
+ }
224
+
225
+ export { MorphingToc };
226
+ //# sourceMappingURL=MorphingToc.mjs.map
227
+ //# sourceMappingURL=MorphingToc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/MorphingToc.tsx"],"names":[],"mappings":";;;;;;AAeA,SAAS,SAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,SAAQ,EAAkB;AAClE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAEhD,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,MACrC,YAAA,EAAc,MAAM,YAAA,CAAa,KAAK,CAAA;AAAA,MACtC,SAAA,EAAU,iEAAA;AAAA,MACV,KAAA,EAAO;AAAA,QACL,WAAA,EAAa,CAAA,EAAG,CAAA,GAAI,MAAM,CAAA,EAAA,CAAA;AAAA,QAC1B,KAAA,EAAO,SAAA,GAAY,MAAA,CAAO,SAAA,GAAY,MAAA,CAAO,IAAA;AAAA,QAC7C,eAAA,EAAiB,SAAA,GAAY,MAAA,CAAO,SAAA,GAAY;AAAA,OAClD;AAAA,MACA,QAAA,EAAU,EAAA;AAAA,MAEV,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAkB,eAAK,KAAA,EAAM;AAAA;AAAA,GAC/C;AAEJ;AAEA,MAAM,aAAA,GAA6C;AAAA,EACjD,IAAA,EAAM;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,OAAA,EAAS;AAAA,GACX;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,UAAA,EAAY,2BAAA;AAAA,IACZ,MAAA,EAAQ,0BAAA;AAAA,IACR,IAAA,EAAM,SAAA;AAAA,IACN,SAAA,EAAW,SAAA;AAAA,IACX,SAAA,EAAW;AAAA;AAEf,CAAA;AAEA,MAAM,YAAA,GAA2C;AAAA,EAC/C,SAAA,EAAW;AAAA,IACT,EAAA,EAAI,QAAA;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,QAAA;AAAA,IACJ,OAAA,EAAS;AAAA,GACX;AAAA,EACA,UAAA,EAAY,KAAA;AAAA,EACZ,OAAA,EAAS,QAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAEA,SAAS,YAAA,CAAa,KAAA,EAAe,MAAA,GAAoC,EAAC,EAAW;AACnF,EAAA,MAAM,SAAS,EAAE,GAAG,aAAA,CAAc,IAAA,EAAM,GAAG,MAAA,EAAO;AAClD,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB;AACE,MAAA,OAAO,MAAA,CAAO,OAAA;AAAA;AAEpB;AAEA,SAAS,YAAA,CAAa,KAAA,EAAe,KAAA,GAAuC,EAAC,EAAW;AACtF,EAAA,MAAM,SAAS,EAAE,GAAG,YAAA,CAAa,SAAA,EAAW,GAAG,KAAA,EAAM;AACrD,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB,KAAK,CAAA;AACH,MAAA,OAAO,MAAA,CAAO,EAAA;AAAA,IAChB;AACE,MAAA,OAAO,MAAA,CAAO,OAAA;AAAA;AAEpB;AAEO,SAAS,WAAA,CAAY;AAAA,EAC1B,SAAA,GAAY,EAAA;AAAA,EACZ,YAAA,GAAe,EAAA;AAAA,EACf,aAAA,GAAgB,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,EACxB,WAAA,GAAc,IAAA;AAAA,EACd,iBAAA;AAAA,EACA,SAAS,EAAC;AAAA,EACV,QAAQ;AACV,CAAA,EAAqB;AACnB,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAEhD,EAAA,MAAM,WAAW,WAAA,CAAY;AAAA,IAC3B,aAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,YAAA,GAAe;AAAA,IACnB,MAAM,EAAE,GAAG,cAAc,IAAA,EAAM,GAAG,OAAO,IAAA,EAAK;AAAA,IAC9C,MAAM,EAAE,GAAG,cAAc,IAAA,EAAM,GAAG,OAAO,IAAA;AAAK,GAChD;AAEA,EAAA,MAAM,WAAA,GAAc;AAAA,IAClB,WAAW,EAAE,GAAG,aAAa,SAAA,EAAW,GAAG,MAAM,SAAA,EAAU;AAAA,IAC3D,UAAA,EAAY,KAAA,CAAM,UAAA,IAAc,YAAA,CAAa,UAAA;AAAA,IAC7C,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW,YAAA,CAAa,OAAA;AAAA,IACvC,SAAA,EAAW,KAAA,CAAM,SAAA,IAAa,YAAA,CAAa;AAAA,GAC7C;AAEA,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAElC,EAAA,MAAM,WAAA,GAAc,CAAC,EAAA,KAAe;AAClC,IAAA,eAAA,CAAgB,IAAI,YAAY,CAAA;AAAA,EAClC,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,2DAAA,EAA8D,SAAS,CAAA,CAAA,EAErF,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,sDAAA;AAAA,QACV,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,QACrC,YAAA,EAAc,MAAM,YAAA,CAAa,KAAK;AAAA;AAAA,KACxC;AAAA,oBAEA,GAAA;AAAA,MAAC,MAAA,CAAO,GAAA;AAAA,MAAP;AAAA,QACC,SAAA,EAAU,UAAA;AAAA,QACV,OAAA,EAAS,EAAE,OAAA,EAAS,CAAA,EAAG,GAAG,GAAA,EAAI;AAAA,QAC9B,OAAA,EAAS,EAAE,OAAA,EAAS,CAAA,EAAG,GAAG,CAAA,EAAE;AAAA,QAC5B,UAAA,EAAY,EAAE,QAAA,EAAU,GAAA,EAAK,OAAO,GAAA,EAAI;AAAA,QACxC,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,QACrC,YAAA,EAAc,MAAM,YAAA,CAAa,KAAK,CAAA;AAAA,QAEtC,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBACb,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,UAAA,EAEb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,eAAA,EAAA,EACE,WAAC,SAAA,oBACA,GAAA;AAAA,YAAC,MAAA,CAAO,GAAA;AAAA,YAAP;AAAA,cACC,OAAA,EAAS,EAAE,OAAA,EAAS,CAAA,EAAE;AAAA,cACtB,OAAA,EAAS,EAAE,OAAA,EAAS,CAAA,EAAE;AAAA,cACtB,IAAA,EAAM;AAAA,gBACJ,OAAA,EAAS,CAAA;AAAA,gBACT,CAAA,EAAG,EAAA;AAAA,gBACH,UAAA,EAAY,EAAE,QAAA,EAAU,IAAA,EAAM,MAAM,SAAA;AAAU,eAChD;AAAA,cACA,UAAA,EAAY,EAAE,QAAA,EAAU,IAAA,EAAK;AAAA,cAC7B,SAAA,EAAU,0CAAA;AAAA,cACV,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,eAAe,QAAA,EAAU,GAAA,EAAK,YAAY,OAAA,EAAQ;AAAA,cAE3E,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,qBACnB,GAAA;AAAA,gBAAC,MAAA,CAAO,MAAA;AAAA,gBAAP;AAAA,kBAEC,OAAA,EAAS,MAAM,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AAAA,kBAClC,SAAA,EAAU,mEAAA;AAAA,kBACV,KAAA,EAAO;AAAA,oBACL,KAAA,EAAO,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO,YAAY,SAAS,CAAA;AAAA,oBACrD,QAAQ,WAAA,CAAY,UAAA;AAAA,oBACpB,eAAA,EAAiB,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO,aAAa,IAAI;AAAA,mBAC7D;AAAA,kBACA,QAAA,EAAU,EAAE,KAAA,EAAO,IAAA,EAAK;AAAA,kBACxB,QAAA,EAAU,EAAA;AAAA,kBACV,OAAA,EAAS;AAAA,oBACP,CAAA,EAAG,YAAY,EAAA,GAAK,CAAA;AAAA,oBACpB,UAAA,EAAY;AAAA,sBACV,QAAA,EAAU,IAAA;AAAA,sBACV,OAAO,KAAA,GAAQ,IAAA;AAAA,sBACf,IAAA,EAAM;AAAA;AACR;AACF,iBAAA;AAAA,gBAjBK,IAAA,CAAK;AAAA,eAmBb;AAAA;AAAA,WACH,EAEJ,CAAA;AAAA,0BAGA,GAAA,CAAC,mBACE,QAAA,EAAA,SAAA,oBACC,GAAA;AAAA,YAAC,MAAA,CAAO,GAAA;AAAA,YAAP;AAAA,cACC,OAAA,EAAS;AAAA,gBACP,OAAA,EAAS,CAAA;AAAA,gBACT,CAAA,EAAG,GAAA;AAAA,gBACH,KAAA,EAAO,GAAA;AAAA,gBACP,YAAA,EAAc;AAAA,eAChB;AAAA,cACA,OAAA,EAAS;AAAA,gBACP,OAAA,EAAS,CAAA;AAAA,gBACT,CAAA,EAAG,CAAA;AAAA,gBACH,KAAA,EAAO,CAAA;AAAA,gBACP,YAAA,EAAc;AAAA,eAChB;AAAA,cACA,IAAA,EAAM;AAAA,gBACJ,OAAA,EAAS,CAAA;AAAA,gBACT,CAAA,EAAG,GAAA;AAAA,gBACH,KAAA,EAAO,GAAA;AAAA,gBACP,UAAA,EAAY,EAAE,QAAA,EAAU,IAAA,EAAM,MAAM,SAAA;AAAU,eAChD;AAAA,cACA,UAAA,EAAY;AAAA,gBACV,QAAA,EAAU,GAAA;AAAA,gBACV,KAAA,EAAO,IAAA;AAAA,gBACP,IAAA,EAAM;AAAA,eACR;AAAA,cACA,SAAA,EAAU,yEAAA;AAAA,cACV,KAAA,EAAO;AAAA,gBACL,OAAO,WAAA,CAAY,SAAA;AAAA,gBACnB,eAAA,EAAiB,aAAa,IAAA,CAAK,UAAA;AAAA,gBACnC,SAAA,EAAW,CAAA,UAAA,EAAa,YAAA,CAAa,IAAA,CAAK,MAAM,CAAA;AAAA,eAClD;AAAA,cAEA,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eACZ,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,IAAA,KAAS;AACtB,gBAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,GAAG,aAAa,CAAA;AAC3C,gBAAA,MAAM,MAAA,GAAA,CAAU,IAAA,CAAK,KAAA,GAAQ,SAAA,IAAa,EAAA;AAE1C,gBAAA,uBACE,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBAEC,IAAA;AAAA,oBACA,MAAA;AAAA,oBACA,QAAQ,YAAA,CAAa,IAAA;AAAA,oBACrB,OAAA,EAAS,MAAM,WAAA,CAAY,IAAA,CAAK,EAAE;AAAA,mBAAA;AAAA,kBAJ7B,IAAA,CAAK;AAAA,iBAKZ;AAAA,cAEJ,CAAC,CAAA,EACH;AAAA;AAAA,WACF,EAEJ;AAAA,SAAA,EACF,CAAA,EACF;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ","file":"MorphingToc.mjs","sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { useTocItems } from './useTocItems';\nimport { scrollToSection } from './scrollToSection';\nimport type { MorphingTocProps, MorphingTocColors, MorphingTocSizes, TocItem } from './types';\n\ninterface MenuItemProps {\n item: TocItem;\n indent: number;\n colors: NonNullable<MorphingTocColors['menu']>;\n onClick: () => void;\n}\n\nfunction MenuItem({ item, indent, colors, onClick }: MenuItemProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n return (\n <button\n onClick={onClick}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n className=\"block w-full text-left px-2 py-1 rounded text-sm cursor-pointer\"\n style={{\n paddingLeft: `${8 + indent}px`,\n color: isHovered ? colors.textHover : colors.text,\n backgroundColor: isHovered ? colors.itemHover : 'transparent',\n }}\n tabIndex={-1}\n >\n <span className=\"block truncate\">{item.title}</span>\n </button>\n );\n}\n\nconst defaultColors: Required<MorphingTocColors> = {\n line: {\n h1: '#475569',\n h2: '#94a3b8',\n h3: '#cbd5e1',\n h4: '#cbd5e1',\n default: '#cbd5e1',\n },\n menu: {\n background: 'rgba(255, 255, 255, 0.95)',\n border: 'rgba(203, 213, 225, 0.3)',\n text: '#475569',\n textHover: '#0f172a',\n itemHover: '#f8fafc',\n },\n};\n\nconst defaultSizes: Required<MorphingTocSizes> = {\n lineWidth: {\n h1: '1.5rem',\n h2: '1rem',\n h3: '0.75rem',\n h4: '0.5rem',\n default: '0.5rem',\n },\n lineHeight: '1px',\n lineGap: '0.5rem',\n menuWidth: '16rem',\n};\n\nfunction getLineColor(level: number, colors: MorphingTocColors['line'] = {}): string {\n const merged = { ...defaultColors.line, ...colors };\n switch (level) {\n case 1:\n return merged.h1!;\n case 2:\n return merged.h2!;\n case 3:\n return merged.h3!;\n case 4:\n return merged.h4!;\n default:\n return merged.default!;\n }\n}\n\nfunction getLineWidth(level: number, sizes: MorphingTocSizes['lineWidth'] = {}): string {\n const merged = { ...defaultSizes.lineWidth, ...sizes };\n switch (level) {\n case 1:\n return merged.h1!;\n case 2:\n return merged.h2!;\n case 3:\n return merged.h3!;\n case 4:\n return merged.h4!;\n default:\n return merged.default!;\n }\n}\n\nexport function MorphingToc({\n className = '',\n scrollOffset = 80,\n headingLevels = [2, 3, 4],\n skipFirstH1 = true,\n containerSelector,\n colors = {},\n sizes = {},\n}: MorphingTocProps) {\n const [isHovered, setIsHovered] = useState(false);\n\n const tocItems = useTocItems({\n headingLevels,\n skipFirstH1,\n containerSelector,\n });\n\n const mergedColors = {\n line: { ...defaultColors.line, ...colors.line },\n menu: { ...defaultColors.menu, ...colors.menu },\n };\n\n const mergedSizes = {\n lineWidth: { ...defaultSizes.lineWidth, ...sizes.lineWidth },\n lineHeight: sizes.lineHeight ?? defaultSizes.lineHeight,\n lineGap: sizes.lineGap ?? defaultSizes.lineGap,\n menuWidth: sizes.menuWidth ?? defaultSizes.menuWidth,\n };\n\n if (tocItems.length === 0) return null;\n\n const handleClick = (id: string) => {\n scrollToSection(id, scrollOffset);\n };\n\n return (\n <div className={`fixed left-0 top-1/2 -translate-y-1/2 z-40 hidden lg:block ${className}`}>\n {/* Invisible hover area for better UX */}\n <div\n className=\"absolute -left-20 top-1/2 -translate-y-1/2 w-32 h-96\"\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n />\n\n <motion.div\n className=\"relative\"\n initial={{ opacity: 0, x: -20 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ duration: 0.3, delay: 0.5 }}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <div className=\"pl-2 md:pl-4 lg:pl-6\">\n <div className=\"relative\">\n {/* Collapsed state: minimal lines */}\n <AnimatePresence>\n {!isHovered && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{\n opacity: 0,\n x: 20,\n transition: { duration: 0.15, ease: 'easeOut' },\n }}\n transition={{ duration: 0.15 }}\n className=\"absolute top-1/2 left-0 -translate-y-1/2\"\n style={{ display: 'flex', flexDirection: 'column', gap: mergedSizes.lineGap }}\n >\n {tocItems.map((item, index) => (\n <motion.button\n key={item.id}\n onClick={() => handleClick(item.id)}\n className=\"block rounded-sm transition-opacity duration-150 hover:opacity-70\"\n style={{\n width: getLineWidth(item.level, mergedSizes.lineWidth),\n height: mergedSizes.lineHeight,\n backgroundColor: getLineColor(item.level, mergedColors.line),\n }}\n whileTap={{ scale: 0.95 }}\n tabIndex={-1}\n animate={{\n x: isHovered ? 20 : 0,\n transition: {\n duration: 0.15,\n delay: index * 0.02,\n ease: 'easeOut',\n },\n }}\n />\n ))}\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Expanded state: full menu */}\n <AnimatePresence>\n {isHovered && (\n <motion.div\n initial={{\n opacity: 0,\n x: -20,\n scale: 0.9,\n borderRadius: 0,\n }}\n animate={{\n opacity: 1,\n x: 0,\n scale: 1,\n borderRadius: 6,\n }}\n exit={{\n opacity: 0,\n x: -20,\n scale: 0.9,\n transition: { duration: 0.15, ease: 'easeOut' },\n }}\n transition={{\n duration: 0.2,\n delay: 0.05,\n ease: 'easeOut',\n }}\n className=\"absolute top-1/2 left-0 -translate-y-1/2 backdrop-blur-sm shadow-md p-3\"\n style={{\n width: mergedSizes.menuWidth,\n backgroundColor: mergedColors.menu.background,\n boxShadow: `0 0 0 1px ${mergedColors.menu.border}`,\n }}\n >\n <nav className=\"space-y-0.5\">\n {tocItems.map((item) => {\n const baseLevel = Math.min(...headingLevels);\n const indent = (item.level - baseLevel) * 12;\n\n return (\n <MenuItem\n key={item.id}\n item={item}\n indent={indent}\n colors={mergedColors.menu}\n onClick={() => handleClick(item.id)}\n />\n );\n })}\n </nav>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n </motion.div>\n </div>\n );\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export { MorphingToc } from './MorphingToc.mjs';
2
+ export { useTocItems } from './useTocItems.mjs';
3
+ export { scrollToSection } from './scrollToSection.mjs';
4
+ export { a as MorphingTocColors, M as MorphingTocProps, b as MorphingTocSizes, T as TocItem, U as UseTocItemsOptions } from './types-Dol3SnRo.mjs';
5
+ import 'react/jsx-runtime';
@@ -0,0 +1,5 @@
1
+ export { MorphingToc } from './MorphingToc.js';
2
+ export { useTocItems } from './useTocItems.js';
3
+ export { scrollToSection } from './scrollToSection.js';
4
+ export { a as MorphingTocColors, M as MorphingTocProps, b as MorphingTocSizes, T as TocItem, U as UseTocItemsOptions } from './types-Dol3SnRo.js';
5
+ import 'react/jsx-runtime';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var MorphingToc = require('./MorphingToc');
5
+ var useTocItems = require('./useTocItems');
6
+ var scrollToSection = require('./scrollToSection');
7
+
8
+
9
+
10
+ Object.defineProperty(exports, "MorphingToc", {
11
+ enumerable: true,
12
+ get: function () { return MorphingToc.MorphingToc; }
13
+ });
14
+ Object.defineProperty(exports, "useTocItems", {
15
+ enumerable: true,
16
+ get: function () { return useTocItems.useTocItems; }
17
+ });
18
+ Object.defineProperty(exports, "scrollToSection", {
19
+ enumerable: true,
20
+ get: function () { return scrollToSection.scrollToSection; }
21
+ });
22
+ //# sourceMappingURL=index.js.map
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ export { MorphingToc } from './MorphingToc';
3
+ export { useTocItems } from './useTocItems';
4
+ export { scrollToSection } from './scrollToSection';
5
+ //# sourceMappingURL=index.mjs.map
6
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs","sourcesContent":[]}
@@ -0,0 +1,3 @@
1
+ declare function scrollToSection(id: string, offset?: number): void;
2
+
3
+ export { scrollToSection };
@@ -0,0 +1,3 @@
1
+ declare function scrollToSection(id: string, offset?: number): void;
2
+
3
+ export { scrollToSection };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ function scrollToSection(id, offset = 80) {
4
+ const element = document.getElementById(id);
5
+ if (element) {
6
+ const elementPosition = element.getBoundingClientRect().top;
7
+ const offsetPosition = elementPosition + window.pageYOffset - offset;
8
+ window.scrollTo({
9
+ top: offsetPosition,
10
+ behavior: "smooth"
11
+ });
12
+ }
13
+ }
14
+
15
+ exports.scrollToSection = scrollToSection;
16
+ //# sourceMappingURL=scrollToSection.js.map
17
+ //# sourceMappingURL=scrollToSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scrollToSection.ts"],"names":[],"mappings":";;AAAO,SAAS,eAAA,CAAgB,EAAA,EAAY,MAAA,GAAiB,EAAA,EAAU;AACrE,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,cAAA,CAAe,EAAE,CAAA;AAE1C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,qBAAA,EAAsB,CAAE,GAAA;AACxD,IAAA,MAAM,cAAA,GAAiB,eAAA,GAAkB,MAAA,CAAO,WAAA,GAAc,MAAA;AAE9D,IAAA,MAAA,CAAO,QAAA,CAAS;AAAA,MACd,GAAA,EAAK,cAAA;AAAA,MACL,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,EACH;AACF","file":"scrollToSection.js","sourcesContent":["export function scrollToSection(id: string, offset: number = 80): void {\n const element = document.getElementById(id);\n\n if (element) {\n const elementPosition = element.getBoundingClientRect().top;\n const offsetPosition = elementPosition + window.pageYOffset - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior: 'smooth',\n });\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ function scrollToSection(id, offset = 80) {
2
+ const element = document.getElementById(id);
3
+ if (element) {
4
+ const elementPosition = element.getBoundingClientRect().top;
5
+ const offsetPosition = elementPosition + window.pageYOffset - offset;
6
+ window.scrollTo({
7
+ top: offsetPosition,
8
+ behavior: "smooth"
9
+ });
10
+ }
11
+ }
12
+
13
+ export { scrollToSection };
14
+ //# sourceMappingURL=scrollToSection.mjs.map
15
+ //# sourceMappingURL=scrollToSection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scrollToSection.ts"],"names":[],"mappings":"AAAO,SAAS,eAAA,CAAgB,EAAA,EAAY,MAAA,GAAiB,EAAA,EAAU;AACrE,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,cAAA,CAAe,EAAE,CAAA;AAE1C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,qBAAA,EAAsB,CAAE,GAAA;AACxD,IAAA,MAAM,cAAA,GAAiB,eAAA,GAAkB,MAAA,CAAO,WAAA,GAAc,MAAA;AAE9D,IAAA,MAAA,CAAO,QAAA,CAAS;AAAA,MACd,GAAA,EAAK,cAAA;AAAA,MACL,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,EACH;AACF","file":"scrollToSection.mjs","sourcesContent":["export function scrollToSection(id: string, offset: number = 80): void {\n const element = document.getElementById(id);\n\n if (element) {\n const elementPosition = element.getBoundingClientRect().top;\n const offsetPosition = elementPosition + window.pageYOffset - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior: 'smooth',\n });\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ interface TocItem {
2
+ id: string;
3
+ title: string;
4
+ level: number;
5
+ }
6
+ interface MorphingTocColors {
7
+ /** Colors for the collapsed state (lines) */
8
+ line?: {
9
+ h1?: string;
10
+ h2?: string;
11
+ h3?: string;
12
+ h4?: string;
13
+ default?: string;
14
+ };
15
+ /** Colors for the expanded state (menu) */
16
+ menu?: {
17
+ background?: string;
18
+ border?: string;
19
+ text?: string;
20
+ textHover?: string;
21
+ itemHover?: string;
22
+ };
23
+ }
24
+ interface MorphingTocSizes {
25
+ /** Line widths for each heading level */
26
+ lineWidth?: {
27
+ h1?: string;
28
+ h2?: string;
29
+ h3?: string;
30
+ h4?: string;
31
+ default?: string;
32
+ };
33
+ /** Height of each line */
34
+ lineHeight?: string;
35
+ /** Spacing between lines */
36
+ lineGap?: string;
37
+ /** Width of the expanded menu */
38
+ menuWidth?: string;
39
+ }
40
+ interface MorphingTocProps {
41
+ /** CSS class name for the root container */
42
+ className?: string;
43
+ /** Scroll offset from top in pixels (accounts for fixed headers) */
44
+ scrollOffset?: number;
45
+ /** Which heading levels to include (default: [2, 3, 4]) */
46
+ headingLevels?: number[];
47
+ /** Whether to skip the first h1 element (typically the page title) */
48
+ skipFirstH1?: boolean;
49
+ /** CSS selector to scope heading search (default: searches entire document) */
50
+ containerSelector?: string;
51
+ /** Custom color configuration */
52
+ colors?: MorphingTocColors;
53
+ /** Custom size configuration */
54
+ sizes?: MorphingTocSizes;
55
+ }
56
+ interface UseTocItemsOptions {
57
+ headingLevels?: number[];
58
+ skipFirstH1?: boolean;
59
+ containerSelector?: string;
60
+ }
61
+
62
+ export type { MorphingTocProps as M, TocItem as T, UseTocItemsOptions as U, MorphingTocColors as a, MorphingTocSizes as b };
@@ -0,0 +1,62 @@
1
+ interface TocItem {
2
+ id: string;
3
+ title: string;
4
+ level: number;
5
+ }
6
+ interface MorphingTocColors {
7
+ /** Colors for the collapsed state (lines) */
8
+ line?: {
9
+ h1?: string;
10
+ h2?: string;
11
+ h3?: string;
12
+ h4?: string;
13
+ default?: string;
14
+ };
15
+ /** Colors for the expanded state (menu) */
16
+ menu?: {
17
+ background?: string;
18
+ border?: string;
19
+ text?: string;
20
+ textHover?: string;
21
+ itemHover?: string;
22
+ };
23
+ }
24
+ interface MorphingTocSizes {
25
+ /** Line widths for each heading level */
26
+ lineWidth?: {
27
+ h1?: string;
28
+ h2?: string;
29
+ h3?: string;
30
+ h4?: string;
31
+ default?: string;
32
+ };
33
+ /** Height of each line */
34
+ lineHeight?: string;
35
+ /** Spacing between lines */
36
+ lineGap?: string;
37
+ /** Width of the expanded menu */
38
+ menuWidth?: string;
39
+ }
40
+ interface MorphingTocProps {
41
+ /** CSS class name for the root container */
42
+ className?: string;
43
+ /** Scroll offset from top in pixels (accounts for fixed headers) */
44
+ scrollOffset?: number;
45
+ /** Which heading levels to include (default: [2, 3, 4]) */
46
+ headingLevels?: number[];
47
+ /** Whether to skip the first h1 element (typically the page title) */
48
+ skipFirstH1?: boolean;
49
+ /** CSS selector to scope heading search (default: searches entire document) */
50
+ containerSelector?: string;
51
+ /** Custom color configuration */
52
+ colors?: MorphingTocColors;
53
+ /** Custom size configuration */
54
+ sizes?: MorphingTocSizes;
55
+ }
56
+ interface UseTocItemsOptions {
57
+ headingLevels?: number[];
58
+ skipFirstH1?: boolean;
59
+ containerSelector?: string;
60
+ }
61
+
62
+ export type { MorphingTocProps as M, TocItem as T, UseTocItemsOptions as U, MorphingTocColors as a, MorphingTocSizes as b };
@@ -0,0 +1,5 @@
1
+ import { U as UseTocItemsOptions, T as TocItem } from './types-Dol3SnRo.mjs';
2
+
3
+ declare function useTocItems(options?: UseTocItemsOptions): TocItem[];
4
+
5
+ export { useTocItems };
@@ -0,0 +1,5 @@
1
+ import { U as UseTocItemsOptions, T as TocItem } from './types-Dol3SnRo.js';
2
+
3
+ declare function useTocItems(options?: UseTocItemsOptions): TocItem[];
4
+
5
+ export { useTocItems };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ const defaultOptions = {
6
+ headingLevels: [2, 3, 4],
7
+ skipFirstH1: true,
8
+ containerSelector: ""
9
+ };
10
+ function useTocItems(options = {}) {
11
+ const [tocItems, setTocItems] = react.useState([]);
12
+ const { headingLevels, skipFirstH1, containerSelector } = {
13
+ ...defaultOptions,
14
+ ...options
15
+ };
16
+ const headingLevelsKey = react.useMemo(() => headingLevels.join(","), [headingLevels]);
17
+ react.useEffect(() => {
18
+ const container = containerSelector ? document.querySelector(containerSelector) : document;
19
+ if (!container) {
20
+ setTocItems([]);
21
+ return;
22
+ }
23
+ const selector = headingLevels.map((level) => `h${level}`).join(", ");
24
+ const headings = container.querySelectorAll(selector);
25
+ const items = [];
26
+ const usedIds = /* @__PURE__ */ new Set();
27
+ let skippedFirstH1 = false;
28
+ headings.forEach((heading) => {
29
+ const level = parseInt(heading.tagName.charAt(1));
30
+ const title = heading.textContent || "";
31
+ if (skipFirstH1 && level === 1 && !skippedFirstH1) {
32
+ skippedFirstH1 = true;
33
+ return;
34
+ }
35
+ let id = heading.id;
36
+ if (!id) {
37
+ const baseId = title.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").trim();
38
+ id = baseId;
39
+ let counter = 1;
40
+ while (usedIds.has(id)) {
41
+ id = `${baseId}-${counter}`;
42
+ counter++;
43
+ }
44
+ heading.id = id;
45
+ }
46
+ usedIds.add(id);
47
+ items.push({
48
+ id,
49
+ title,
50
+ level
51
+ });
52
+ });
53
+ setTocItems(items);
54
+ }, [headingLevelsKey, skipFirstH1, containerSelector]);
55
+ return tocItems;
56
+ }
57
+
58
+ exports.useTocItems = useTocItems;
59
+ //# sourceMappingURL=useTocItems.js.map
60
+ //# sourceMappingURL=useTocItems.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useTocItems.ts"],"names":["useState","useMemo","useEffect"],"mappings":";;;;AAGA,MAAM,cAAA,GAA+C;AAAA,EACnD,aAAA,EAAe,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,EACvB,WAAA,EAAa,IAAA;AAAA,EACb,iBAAA,EAAmB;AACrB,CAAA;AAEO,SAAS,WAAA,CAAY,OAAA,GAA8B,EAAC,EAAc;AACvE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,cAAA,CAAoB,EAAE,CAAA;AAEtD,EAAA,MAAM,EAAE,aAAA,EAAe,WAAA,EAAa,iBAAA,EAAkB,GAAI;AAAA,IACxD,GAAG,cAAA;AAAA,IACH,GAAG;AAAA,GACL;AAGA,EAAA,MAAM,gBAAA,GAAmBC,cAAQ,MAAM,aAAA,CAAc,KAAK,GAAG,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAE/E,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAA,GAAY,iBAAA,GACd,QAAA,CAAS,aAAA,CAAc,iBAAiB,CAAA,GACxC,QAAA;AAEJ,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,WAAA,CAAY,EAAE,CAAA;AACd,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,aAAA,CAAc,GAAA,CAAI,CAAC,KAAA,KAAU,IAAI,KAAK,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACpE,IAAA,MAAM,QAAA,GAAW,SAAA,CAAU,gBAAA,CAAiB,QAAQ,CAAA;AACpD,IAAA,MAAM,QAAmB,EAAC;AAC1B,IAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,IAAA,IAAI,cAAA,GAAiB,KAAA;AAErB,IAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC5B,MAAA,MAAM,QAAQ,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAC,CAAA;AAChD,MAAA,MAAM,KAAA,GAAQ,QAAQ,WAAA,IAAe,EAAA;AAGrC,MAAA,IAAI,WAAA,IAAe,KAAA,KAAU,CAAA,IAAK,CAAC,cAAA,EAAgB;AACjD,QAAA,cAAA,GAAiB,IAAA;AACjB,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,KAAK,OAAA,CAAQ,EAAA;AACjB,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,MAAM,MAAA,GAAS,KAAA,CACZ,WAAA,EAAY,CACZ,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAA,CACvB,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CACnB,IAAA,EAAK;AAER,QAAA,EAAA,GAAK,MAAA;AACL,QAAA,IAAI,OAAA,GAAU,CAAA;AACd,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,EAAG;AACtB,UAAA,EAAA,GAAK,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACzB,UAAA,OAAA,EAAA;AAAA,QACF;AAEA,QAAA,OAAA,CAAQ,EAAA,GAAK,EAAA;AAAA,MACf;AAEA,MAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AAEd,MAAA,KAAA,CAAM,IAAA,CAAK;AAAA,QACT,EAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,WAAA,CAAY,KAAK,CAAA;AAAA,EACnB,CAAA,EAAG,CAAC,gBAAA,EAAkB,WAAA,EAAa,iBAAiB,CAAC,CAAA;AAErD,EAAA,OAAO,QAAA;AACT","file":"useTocItems.js","sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport type { TocItem, UseTocItemsOptions } from './types';\n\nconst defaultOptions: Required<UseTocItemsOptions> = {\n headingLevels: [2, 3, 4],\n skipFirstH1: true,\n containerSelector: '',\n};\n\nexport function useTocItems(options: UseTocItemsOptions = {}): TocItem[] {\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n\n const { headingLevels, skipFirstH1, containerSelector } = {\n ...defaultOptions,\n ...options,\n };\n\n // Stable reference for headingLevels to avoid unnecessary re-renders\n const headingLevelsKey = useMemo(() => headingLevels.join(','), [headingLevels]);\n\n useEffect(() => {\n const container = containerSelector\n ? document.querySelector(containerSelector)\n : document;\n\n if (!container) {\n setTocItems([]);\n return;\n }\n\n const selector = headingLevels.map((level) => `h${level}`).join(', ');\n const headings = container.querySelectorAll(selector);\n const items: TocItem[] = [];\n const usedIds = new Set<string>();\n let skippedFirstH1 = false;\n\n headings.forEach((heading) => {\n const level = parseInt(heading.tagName.charAt(1));\n const title = heading.textContent || '';\n\n // Skip the first h1 if configured\n if (skipFirstH1 && level === 1 && !skippedFirstH1) {\n skippedFirstH1 = true;\n return;\n }\n\n // Generate unique ID if heading doesn't have one\n let id = heading.id;\n if (!id) {\n const baseId = title\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .trim();\n\n id = baseId;\n let counter = 1;\n while (usedIds.has(id)) {\n id = `${baseId}-${counter}`;\n counter++;\n }\n\n heading.id = id;\n }\n\n usedIds.add(id);\n\n items.push({\n id,\n title,\n level,\n });\n });\n\n setTocItems(items);\n }, [headingLevelsKey, skipFirstH1, containerSelector]);\n\n return tocItems;\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import { useState, useMemo, useEffect } from 'react';
2
+
3
+ const defaultOptions = {
4
+ headingLevels: [2, 3, 4],
5
+ skipFirstH1: true,
6
+ containerSelector: ""
7
+ };
8
+ function useTocItems(options = {}) {
9
+ const [tocItems, setTocItems] = useState([]);
10
+ const { headingLevels, skipFirstH1, containerSelector } = {
11
+ ...defaultOptions,
12
+ ...options
13
+ };
14
+ const headingLevelsKey = useMemo(() => headingLevels.join(","), [headingLevels]);
15
+ useEffect(() => {
16
+ const container = containerSelector ? document.querySelector(containerSelector) : document;
17
+ if (!container) {
18
+ setTocItems([]);
19
+ return;
20
+ }
21
+ const selector = headingLevels.map((level) => `h${level}`).join(", ");
22
+ const headings = container.querySelectorAll(selector);
23
+ const items = [];
24
+ const usedIds = /* @__PURE__ */ new Set();
25
+ let skippedFirstH1 = false;
26
+ headings.forEach((heading) => {
27
+ const level = parseInt(heading.tagName.charAt(1));
28
+ const title = heading.textContent || "";
29
+ if (skipFirstH1 && level === 1 && !skippedFirstH1) {
30
+ skippedFirstH1 = true;
31
+ return;
32
+ }
33
+ let id = heading.id;
34
+ if (!id) {
35
+ const baseId = title.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").trim();
36
+ id = baseId;
37
+ let counter = 1;
38
+ while (usedIds.has(id)) {
39
+ id = `${baseId}-${counter}`;
40
+ counter++;
41
+ }
42
+ heading.id = id;
43
+ }
44
+ usedIds.add(id);
45
+ items.push({
46
+ id,
47
+ title,
48
+ level
49
+ });
50
+ });
51
+ setTocItems(items);
52
+ }, [headingLevelsKey, skipFirstH1, containerSelector]);
53
+ return tocItems;
54
+ }
55
+
56
+ export { useTocItems };
57
+ //# sourceMappingURL=useTocItems.mjs.map
58
+ //# sourceMappingURL=useTocItems.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useTocItems.ts"],"names":[],"mappings":";;AAGA,MAAM,cAAA,GAA+C;AAAA,EACnD,aAAA,EAAe,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,EACvB,WAAA,EAAa,IAAA;AAAA,EACb,iBAAA,EAAmB;AACrB,CAAA;AAEO,SAAS,WAAA,CAAY,OAAA,GAA8B,EAAC,EAAc;AACvE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA,CAAoB,EAAE,CAAA;AAEtD,EAAA,MAAM,EAAE,aAAA,EAAe,WAAA,EAAa,iBAAA,EAAkB,GAAI;AAAA,IACxD,GAAG,cAAA;AAAA,IACH,GAAG;AAAA,GACL;AAGA,EAAA,MAAM,gBAAA,GAAmB,QAAQ,MAAM,aAAA,CAAc,KAAK,GAAG,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAE/E,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAA,GAAY,iBAAA,GACd,QAAA,CAAS,aAAA,CAAc,iBAAiB,CAAA,GACxC,QAAA;AAEJ,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,WAAA,CAAY,EAAE,CAAA;AACd,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,aAAA,CAAc,GAAA,CAAI,CAAC,KAAA,KAAU,IAAI,KAAK,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACpE,IAAA,MAAM,QAAA,GAAW,SAAA,CAAU,gBAAA,CAAiB,QAAQ,CAAA;AACpD,IAAA,MAAM,QAAmB,EAAC;AAC1B,IAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,IAAA,IAAI,cAAA,GAAiB,KAAA;AAErB,IAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC5B,MAAA,MAAM,QAAQ,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAC,CAAA;AAChD,MAAA,MAAM,KAAA,GAAQ,QAAQ,WAAA,IAAe,EAAA;AAGrC,MAAA,IAAI,WAAA,IAAe,KAAA,KAAU,CAAA,IAAK,CAAC,cAAA,EAAgB;AACjD,QAAA,cAAA,GAAiB,IAAA;AACjB,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,KAAK,OAAA,CAAQ,EAAA;AACjB,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,MAAM,MAAA,GAAS,KAAA,CACZ,WAAA,EAAY,CACZ,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAA,CACvB,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CACnB,IAAA,EAAK;AAER,QAAA,EAAA,GAAK,MAAA;AACL,QAAA,IAAI,OAAA,GAAU,CAAA;AACd,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,EAAG;AACtB,UAAA,EAAA,GAAK,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACzB,UAAA,OAAA,EAAA;AAAA,QACF;AAEA,QAAA,OAAA,CAAQ,EAAA,GAAK,EAAA;AAAA,MACf;AAEA,MAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AAEd,MAAA,KAAA,CAAM,IAAA,CAAK;AAAA,QACT,EAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,WAAA,CAAY,KAAK,CAAA;AAAA,EACnB,CAAA,EAAG,CAAC,gBAAA,EAAkB,WAAA,EAAa,iBAAiB,CAAC,CAAA;AAErD,EAAA,OAAO,QAAA;AACT","file":"useTocItems.mjs","sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport type { TocItem, UseTocItemsOptions } from './types';\n\nconst defaultOptions: Required<UseTocItemsOptions> = {\n headingLevels: [2, 3, 4],\n skipFirstH1: true,\n containerSelector: '',\n};\n\nexport function useTocItems(options: UseTocItemsOptions = {}): TocItem[] {\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n\n const { headingLevels, skipFirstH1, containerSelector } = {\n ...defaultOptions,\n ...options,\n };\n\n // Stable reference for headingLevels to avoid unnecessary re-renders\n const headingLevelsKey = useMemo(() => headingLevels.join(','), [headingLevels]);\n\n useEffect(() => {\n const container = containerSelector\n ? document.querySelector(containerSelector)\n : document;\n\n if (!container) {\n setTocItems([]);\n return;\n }\n\n const selector = headingLevels.map((level) => `h${level}`).join(', ');\n const headings = container.querySelectorAll(selector);\n const items: TocItem[] = [];\n const usedIds = new Set<string>();\n let skippedFirstH1 = false;\n\n headings.forEach((heading) => {\n const level = parseInt(heading.tagName.charAt(1));\n const title = heading.textContent || '';\n\n // Skip the first h1 if configured\n if (skipFirstH1 && level === 1 && !skippedFirstH1) {\n skippedFirstH1 = true;\n return;\n }\n\n // Generate unique ID if heading doesn't have one\n let id = heading.id;\n if (!id) {\n const baseId = title\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .trim();\n\n id = baseId;\n let counter = 1;\n while (usedIds.has(id)) {\n id = `${baseId}-${counter}`;\n counter++;\n }\n\n heading.id = id;\n }\n\n usedIds.add(id);\n\n items.push({\n id,\n title,\n level,\n });\n });\n\n setTocItems(items);\n }, [headingLevelsKey, skipFirstH1, containerSelector]);\n\n return tocItems;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "morphing-toc",
3
+ "version": "0.1.0",
4
+ "description": "A React table of contents component that shows minimal lines and morphs into a full menu on hover",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup && node scripts/ensure-use-client.js",
20
+ "dev": "tsup --watch",
21
+ "clean": "rm -rf dist",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=18.0.0",
26
+ "react-dom": ">=18.0.0",
27
+ "motion": ">=11.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19.0.0",
31
+ "@types/react-dom": "^19.0.0",
32
+ "react": "^19.0.0",
33
+ "react-dom": "^19.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "keywords": [
38
+ "react",
39
+ "table-of-contents",
40
+ "toc",
41
+ "navigation",
42
+ "animation",
43
+ "framer-motion",
44
+ "motion",
45
+ "morphing"
46
+ ],
47
+ "author": "Antoine Pirard",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/antoinepirard/morphing-toc"
52
+ },
53
+ "homepage": "https://antoinepirard.com/open-source/morphing-toc"
54
+ }