master
1---
2interface Props {
3 syncKey?: string;
4}
5
6const syncKey = Astro.props.syncKey;
7const tabsName = `tabs-${crypto.randomUUID()}`;
8
9const html = (await Astro.slots.render('default')).replaceAll(/{{{tabs-name}}}/g, tabsName);
10---
11
12<div
13 class="tabs bg-base-200 tabs-box border-base-content/25 rounded-none not-[.card-body>:only-child]:my-4 not-[.card-body>:only-child]:rounded-xl not-[.card-body>:only-child]:border"
14 {...syncKey ? { 'data-sync-key': syncKey } : {}}
15>
16 <Fragment set:html={html} />
17</div>
18
19<script>
20 interface SyncTabs {
21 [key: string]: number;
22 }
23
24 async function init() {
25 const tabsNeedSync = document.querySelectorAll('.tabs[data-sync-key]');
26 if (tabsNeedSync.length === 0) return;
27 const { listenKeys, map } = await import('nanostores');
28 const syncTabs = map<SyncTabs>();
29 tabsNeedSync.forEach((tab) => {
30 const syncKey = tab.getAttribute('data-sync-key')!;
31 const tabItems: NodeListOf<HTMLInputElement> = tab.querySelectorAll(
32 ':scope > input[type="radio"]'
33 );
34 if (syncKey in syncTabs.get()) {
35 const activeTabIndex = syncTabs.get()[syncKey];
36 if (activeTabIndex === -1) {
37 tabItems.forEach((tab) => tab.removeAttribute('checked'));
38 } else {
39 tabItems[activeTabIndex].setAttribute('checked', 'checked');
40 }
41 } else {
42 const activeTabIndex = Array.from(tabItems).findIndex((tab) => tab.checked);
43 syncTabs.setKey(syncKey, activeTabIndex);
44 }
45 tabItems.forEach((tab, index) => {
46 tab.addEventListener('change', () => {
47 if (tab.checked) {
48 syncTabs.setKey(syncKey, index);
49 }
50 });
51 });
52 listenKeys(syncTabs, [syncKey], (curr) => {
53 const activeTabIndex = curr[syncKey];
54 if (activeTabIndex === -1) {
55 tabItems.forEach((tab) => (tab.checked = false));
56 } else {
57 tabItems[activeTabIndex].checked = true;
58 }
59 });
60 });
61 }
62
63 document.addEventListener('astro:after-swap', init);
64 await init();
65</script>