@classic-homes/theme-docs 0.0.23 → 0.0.24
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/dist/lib/components/TocPanel.svelte +135 -127
- package/package.json +1 -1
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Provides a mobile flyout panel and optional desktop sidebar for the
|
|
6
6
|
* TableOfContents component. Features a paper folder tab trigger on mobile
|
|
7
|
-
* that slides out a panel from the right side.
|
|
7
|
+
* that slides out a panel from the right side. The tab is physically attached
|
|
8
|
+
* to the panel so they move together as one unit.
|
|
8
9
|
*/
|
|
9
10
|
import { onMount } from 'svelte';
|
|
10
|
-
import {
|
|
11
|
+
import { fade } from 'svelte/transition';
|
|
11
12
|
import { cn } from '../utils.js';
|
|
12
13
|
import TableOfContents from './TableOfContents.svelte';
|
|
13
14
|
|
|
@@ -41,19 +42,25 @@
|
|
|
41
42
|
}: Props = $props();
|
|
42
43
|
|
|
43
44
|
let open = $state(false);
|
|
44
|
-
let
|
|
45
|
+
let tabButtonRef = $state<HTMLButtonElement | null>(null);
|
|
45
46
|
let previousActiveElement: HTMLElement | null = null;
|
|
46
47
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
function toggle() {
|
|
49
|
+
if (open) {
|
|
50
|
+
open = false;
|
|
51
|
+
// Restore focus to the tab when closing
|
|
52
|
+
previousActiveElement?.focus();
|
|
53
|
+
} else {
|
|
54
|
+
// Store the currently focused element to restore focus when closing
|
|
55
|
+
previousActiveElement = document.activeElement as HTMLElement;
|
|
56
|
+
open = true;
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
function close() {
|
|
54
61
|
open = false;
|
|
55
|
-
//
|
|
56
|
-
|
|
62
|
+
// Focus the tab button when closing via backdrop/escape
|
|
63
|
+
tabButtonRef?.focus();
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
function handleKeydown(event: KeyboardEvent) {
|
|
@@ -63,14 +70,6 @@
|
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
// Focus the close button when panel opens
|
|
67
|
-
$effect(() => {
|
|
68
|
-
if (open && closeButtonRef) {
|
|
69
|
-
// Small delay to ensure the panel is rendered
|
|
70
|
-
setTimeout(() => closeButtonRef?.focus(), 0);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
73
|
onMount(() => {
|
|
75
74
|
// Add global escape key listener
|
|
76
75
|
document.addEventListener('keydown', handleKeydown);
|
|
@@ -92,63 +91,104 @@
|
|
|
92
91
|
'2xl': 'hidden 2xl:block',
|
|
93
92
|
}[breakpoint]
|
|
94
93
|
);
|
|
95
|
-
</script>
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
95
|
+
// Tailwind classes for the attached tab button
|
|
96
|
+
const tabClasses = [
|
|
97
|
+
// Positioning - absolute to panel container, sticks out on left
|
|
98
|
+
'absolute top-[30%] right-full',
|
|
99
|
+
'-translate-y-1/2',
|
|
100
|
+
// Tab shape - compact
|
|
101
|
+
'py-5 px-2',
|
|
102
|
+
// Visual styling - white background with black border
|
|
103
|
+
'bg-background',
|
|
104
|
+
'border border-foreground border-r-0',
|
|
105
|
+
'rounded-l',
|
|
106
|
+
'shadow-[-2px_0_8px_-2px_rgba(0,0,0,0.1)]',
|
|
107
|
+
// Typography
|
|
108
|
+
'font-sans leading-normal',
|
|
109
|
+
// Interactions
|
|
110
|
+
'cursor-pointer',
|
|
111
|
+
'hover:bg-muted',
|
|
112
|
+
// Focus
|
|
113
|
+
'focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2',
|
|
114
|
+
].join(' ');
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
// Tailwind classes for the backdrop
|
|
117
|
+
const backdropClasses = [
|
|
118
|
+
'fixed inset-0 z-[60]',
|
|
119
|
+
'bg-black/50',
|
|
120
|
+
'cursor-pointer',
|
|
121
|
+
'focus-visible:outline-none',
|
|
122
|
+
].join(' ');
|
|
123
|
+
|
|
124
|
+
// Tailwind classes for the close button in header
|
|
125
|
+
const closeButtonClasses = [
|
|
126
|
+
'inline-flex items-center justify-center',
|
|
127
|
+
'w-9 h-9',
|
|
128
|
+
'rounded-md',
|
|
129
|
+
'text-muted-foreground',
|
|
130
|
+
'bg-transparent border-0 p-0 m-0',
|
|
131
|
+
'cursor-pointer',
|
|
132
|
+
'transition-colors duration-150 ease-in-out',
|
|
133
|
+
'hover:bg-muted hover:text-foreground',
|
|
134
|
+
'focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2',
|
|
135
|
+
].join(' ');
|
|
136
|
+
</script>
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
<!-- Mobile TOC Panel with attached tab - always rendered, slides via CSS transform -->
|
|
139
|
+
{#if html}
|
|
140
|
+
<!-- Backdrop - only shown when open -->
|
|
141
|
+
{#if open}
|
|
142
|
+
<button
|
|
143
|
+
class={cn(breakpointHide, backdropClasses)}
|
|
144
|
+
onclick={close}
|
|
145
|
+
aria-label="Close table of contents"
|
|
146
|
+
transition:fade={{ duration: 300 }}
|
|
147
|
+
></button>
|
|
148
|
+
{/if}
|
|
149
|
+
|
|
150
|
+
<!-- Panel container with attached tab -->
|
|
121
151
|
<div
|
|
122
|
-
class={cn(breakpointHide, 'toc-panel', mobileClass)}
|
|
123
|
-
transition:fly={{ x: 320, duration: 300 }}
|
|
152
|
+
class={cn(breakpointHide, 'toc-panel-container', open && 'toc-panel-open', mobileClass)}
|
|
124
153
|
{...restProps}
|
|
125
154
|
>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
<!-- Tab - physically attached to panel -->
|
|
156
|
+
<button
|
|
157
|
+
bind:this={tabButtonRef}
|
|
158
|
+
class={tabClasses}
|
|
159
|
+
onclick={toggle}
|
|
160
|
+
aria-label={open ? 'Close table of contents' : 'Open table of contents'}
|
|
161
|
+
aria-expanded={open}
|
|
162
|
+
>
|
|
163
|
+
<span class="toc-tab-text">
|
|
164
|
+
{title}
|
|
165
|
+
</span>
|
|
166
|
+
</button>
|
|
167
|
+
|
|
168
|
+
<!-- Panel content -->
|
|
169
|
+
<div class="toc-panel-content">
|
|
170
|
+
<div class="toc-panel-header">
|
|
171
|
+
<span class="text-sm font-medium text-foreground">{title}</span>
|
|
172
|
+
<button class={closeButtonClasses} onclick={close} aria-label="Close table of contents">
|
|
173
|
+
<svg
|
|
174
|
+
class="h-5 w-5"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="currentColor"
|
|
177
|
+
viewBox="0 0 24 24"
|
|
178
|
+
aria-hidden="true"
|
|
179
|
+
>
|
|
180
|
+
<path
|
|
181
|
+
stroke-linecap="round"
|
|
182
|
+
stroke-linejoin="round"
|
|
183
|
+
stroke-width="2"
|
|
184
|
+
d="M6 18L18 6M6 6l12 12"
|
|
185
|
+
/>
|
|
186
|
+
</svg>
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="p-5">
|
|
190
|
+
<TableOfContents {html} {maxDepth} title="" />
|
|
191
|
+
</div>
|
|
152
192
|
</div>
|
|
153
193
|
</div>
|
|
154
194
|
{/if}
|
|
@@ -167,31 +207,11 @@
|
|
|
167
207
|
{/if}
|
|
168
208
|
|
|
169
209
|
<style>
|
|
170
|
-
/*
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
transform: translateY(-50%);
|
|
176
|
-
z-index: 21;
|
|
177
|
-
/* Tab shape - taller for vertical text */
|
|
178
|
-
padding: 1rem 0.375rem;
|
|
179
|
-
/* Visual styling */
|
|
180
|
-
background-color: hsl(var(--card));
|
|
181
|
-
border: 1px solid hsl(var(--border));
|
|
182
|
-
border-right: none;
|
|
183
|
-
border-radius: 0.5rem 0 0 0.5rem;
|
|
184
|
-
box-shadow: -2px 0 8px -2px rgba(0, 0, 0, 0.1);
|
|
185
|
-
/* Hover effect */
|
|
186
|
-
transition:
|
|
187
|
-
background-color 0.15s ease,
|
|
188
|
-
padding-right 0.15s ease;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.toc-tab-trigger:hover {
|
|
192
|
-
background-color: hsl(var(--muted));
|
|
193
|
-
padding-right: 0.5rem;
|
|
194
|
-
}
|
|
210
|
+
/*
|
|
211
|
+
* Non-button styles that don't conflict with Tailwind preflight.
|
|
212
|
+
* Button styles are applied via Tailwind utility classes in the script
|
|
213
|
+
* to ensure proper specificity with postcss-cascade-layers.
|
|
214
|
+
*/
|
|
195
215
|
|
|
196
216
|
/* Vertical text for folder tab */
|
|
197
217
|
.toc-tab-text {
|
|
@@ -199,33 +219,40 @@
|
|
|
199
219
|
writing-mode: vertical-rl;
|
|
200
220
|
text-orientation: mixed;
|
|
201
221
|
transform: rotate(180deg);
|
|
202
|
-
font-size: 0.
|
|
203
|
-
font-weight:
|
|
204
|
-
color: hsl(var(--
|
|
222
|
+
font-size: 0.8125rem;
|
|
223
|
+
font-weight: 600;
|
|
224
|
+
color: hsl(var(--foreground));
|
|
205
225
|
white-space: nowrap;
|
|
206
|
-
letter-spacing: 0.
|
|
226
|
+
letter-spacing: 0.05em;
|
|
207
227
|
}
|
|
208
228
|
|
|
209
|
-
/*
|
|
210
|
-
.toc-
|
|
211
|
-
position: fixed;
|
|
212
|
-
inset: 0;
|
|
213
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
214
|
-
z-index: 20;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/* Flyout panel */
|
|
218
|
-
.toc-panel {
|
|
229
|
+
/* Panel container - holds both tab and panel, slides as one unit */
|
|
230
|
+
.toc-panel-container {
|
|
219
231
|
position: fixed;
|
|
220
232
|
top: 0;
|
|
221
233
|
right: 0;
|
|
222
234
|
height: 100%;
|
|
223
235
|
width: 20rem;
|
|
224
236
|
max-width: 85vw;
|
|
237
|
+
z-index: 61;
|
|
238
|
+
/* Start off-screen (translated right by panel width) */
|
|
239
|
+
transform: translateX(100%);
|
|
240
|
+
/* Smooth slide animation */
|
|
241
|
+
transition: transform 0.3s ease-in-out;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* When open, slide into view */
|
|
245
|
+
.toc-panel-open {
|
|
246
|
+
transform: translateX(0);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Panel content area */
|
|
250
|
+
.toc-panel-content {
|
|
251
|
+
height: 100%;
|
|
252
|
+
width: 100%;
|
|
225
253
|
background-color: hsl(var(--card));
|
|
226
254
|
border-left: 1px solid hsl(var(--border));
|
|
227
255
|
box-shadow: -4px 0 16px -4px rgba(0, 0, 0, 0.15);
|
|
228
|
-
z-index: 22;
|
|
229
256
|
overflow-y: auto;
|
|
230
257
|
overscroll-behavior: contain;
|
|
231
258
|
}
|
|
@@ -241,23 +268,4 @@
|
|
|
241
268
|
background-color: hsl(var(--card));
|
|
242
269
|
border-bottom: 1px solid hsl(var(--border));
|
|
243
270
|
}
|
|
244
|
-
|
|
245
|
-
/* Close button */
|
|
246
|
-
.toc-close-button {
|
|
247
|
-
display: inline-flex;
|
|
248
|
-
align-items: center;
|
|
249
|
-
justify-content: center;
|
|
250
|
-
width: 2.25rem;
|
|
251
|
-
height: 2.25rem;
|
|
252
|
-
border-radius: 0.375rem;
|
|
253
|
-
color: hsl(var(--muted-foreground));
|
|
254
|
-
transition:
|
|
255
|
-
background-color 0.15s ease,
|
|
256
|
-
color 0.15s ease;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.toc-close-button:hover {
|
|
260
|
-
background-color: hsl(var(--muted));
|
|
261
|
-
color: hsl(var(--foreground));
|
|
262
|
-
}
|
|
263
271
|
</style>
|