master
1import { t } from '@utils/i18n';
2import type { ParentComponent } from 'solid-js';
3import { createSignal, onCleanup, onMount } from 'solid-js';
4
5const TocButton: ParentComponent = (props) => {
6 const [isOpen, setIsOpen] = createSignal(false);
7 const [isWideScreen, setIsWideScreen] = createSignal(false);
8 const [hasToc, setHasToc] = createSignal(false);
9 let tocWrapperRef: HTMLDivElement | undefined;
10 let buttonRef: HTMLButtonElement | undefined;
11
12 const handleResize = () => {
13 setIsWideScreen(window.innerWidth > 1280);
14 };
15
16 const setup = () => {
17 const toc = document.getElementById('toc');
18 const wrapper = tocWrapperRef;
19 if (toc && wrapper) {
20 wrapper.innerHTML = '';
21 wrapper.appendChild(toc.cloneNode(true));
22 (wrapper.children[0] as HTMLElement).id = 'stb-toc-content';
23
24 const remainAttrs = ['class', 'style'];
25 Array.from((wrapper.children[0] as Element).attributes).forEach((attr) => {
26 if (!remainAttrs.includes(attr.name)) {
27 (wrapper.children[0] as Element).removeAttribute(attr.name);
28 }
29 });
30
31 setHasToc(true);
32 } else {
33 setHasToc(false);
34 }
35
36 window.addEventListener('resize', handleResize);
37 handleResize(); // 初始一次
38 };
39
40 const cleanup = () => {
41 setIsOpen(false);
42 setHasToc(false);
43 tocWrapperRef!.innerHTML = '';
44 window.removeEventListener('resize', handleResize);
45 setIsWideScreen(false);
46 };
47
48 onMount(() => {
49 document.addEventListener('astro:page-load', setup);
50 setup();
51 document.addEventListener('astro:before-swap', cleanup);
52
53 onCleanup(() => {
54 document.removeEventListener('astro:page-load', setup);
55 document.removeEventListener('astro:before-swap', cleanup);
56 cleanup();
57 setIsOpen(false);
58 tocWrapperRef!.innerHTML = '';
59 });
60 });
61
62 return (
63 <div
64 classList={{
65 hidden: !hasToc() || isWideScreen(),
66 }}
67 >
68 <button
69 ref={buttonRef}
70 type="button"
71 class="btn btn-circle btn-secondary btn-sm"
72 title={t.info.toc()}
73 aria-label={t.info.toc()}
74 aria-expanded={isOpen()}
75 aria-controls="stb-toc-wrapper"
76 onClick={() => setIsOpen((v) => !v)}
77 >
78 {props.children}
79 </button>
80
81 <div
82 ref={tocWrapperRef}
83 id="stb-toc-wrapper"
84 classList={{
85 'rounded-box absolute w-[calc(100vw-4rem)] -translate-x-1/2 -translate-y-1/2 max-w-72 backdrop-blur-md duration-300 text-base-content text-start': true,
86 'scale-0 opacity-0': !isOpen() || isWideScreen(), // closed
87 '-translate-x-[calc(100%+0.5rem)]! -translate-y-[calc(100%-2.5rem)]!':
88 isOpen() && !isWideScreen(),
89 hidden: !hasToc() || isWideScreen(),
90 }}
91 inert={!isOpen() || isWideScreen()}
92 />
93 </div>
94 );
95};
96
97export default TocButton;