1import { useEffect, useRef, useState } from "react";
2import { highlightTS } from "../marketing/highlightTS";
3
4interface Panel {
5 filename: string;
6 code: string;
7 caption: string;
8}
9
10const DWELL_MS = 5000;
11
12export default function AsyncTabs({ panels }: { panels: Panel[] }) {
13 const [active, setActive] = useState(0);
14 const wrapRef = useRef<HTMLDivElement>(null);
15
16 useEffect(() => {
17 let cancelled = false;
18 let timer: ReturnType<typeof setTimeout> | null = null;
19
20 const schedule = () => {
21 timer = setTimeout(() => {
22 if (cancelled) return;
23 setActive((i) => (i + 1) % panels.length);
24 }, DWELL_MS);
25 };
26
27 const obs = new IntersectionObserver(
28 (entries) => {
29 for (const e of entries) {
30 if (e.isIntersecting) {
31 schedule();
32 } else if (timer) {
33 clearTimeout(timer);
34 timer = null;
35 }
36 }
37 },
38 { threshold: 0.3 },
39 );
40 if (wrapRef.current) obs.observe(wrapRef.current);
41
42 return () => {
43 cancelled = true;
44 if (timer) clearTimeout(timer);
45 obs.disconnect();
46 };
47 }, [active, panels.length]);
48
49 const html = panels.map((p) => highlightTS(p.code));
50
51 return (
52 <div ref={wrapRef} className="async-tabs" data-nosnippet="">
53 <div className="async-tabs__chrome">
54 <div className="async-tabs__header">
55 <span
56 className="alc-code-block__dot"
57 style={{ background: "var(--alc-danger)" }}
58 />
59 <span
60 className="alc-code-block__dot"
61 style={{ background: "var(--alc-warn)" }}
62 />
63 <span
64 className="alc-code-block__dot"
65 style={{ background: "var(--alc-accent-bright)" }}
66 />
67 <div className="async-tabs__tabs" role="tablist">
68 {panels.map((p, i) => (
69 <button
70 key={p.filename}
71 type="button"
72 role="tab"
73 aria-selected={active === i}
74 data-active={active === i}
75 className="async-tabs__tab"
76 onClick={() => setActive(i)}
77 >
78 {p.filename}
79 </button>
80 ))}
81 </div>
82 </div>
83 <div className="async-tabs__body">
84 {panels.map((p, i) => (
85 <div
86 key={p.filename}
87 className="async-tabs__panel"
88 hidden={active !== i}
89 >
90 <pre
91 className="alc-code-block__pre async-tabs__pre"
92 dangerouslySetInnerHTML={{ __html: html[i] }}
93 />
94 </div>
95 ))}
96 </div>
97 </div>
98 <div key={active} className="alc-code-block__caption" aria-live="polite">
99 {panels[active].caption}
100 </div>
101 </div>
102 );
103}