Import all nix files in a directory tree.
Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/
dendrix.oeiuwq.com/Dendritic.html
dendritic
inputs
1---
2import TabListItem from './TabListItem.astro';
3
4export interface Props {
5 /**
6 * List of content for the tab list.
7 *
8 * To use more complex mark-up for the tab list, pass `<TabListItem>`s
9 * inside a `<Fragment slot="tab-list">`.
10 */
11 tabs?: { label: string; id: string; initial?: boolean }[];
12 /** Enable default styles for the tab list and panels. */
13 styled?: boolean;
14 /** Additional class names to apply to `.tab-list` and `.panels`. */
15 class?: string;
16}
17
18const { tabs, styled } = Astro.props as Props;
19---
20
21<tabbed-content>
22 <ul class:list={['tab-list', Astro.props.class, { 'tab-list--styled': styled }]}>
23 <slot name="tab-list">
24 {
25 tabs?.map((tab) => (
26 <TabListItem id={tab.id} initial={tab.initial}>
27 {tab.label}
28 </TabListItem>
29 ))
30 }
31 </slot>
32 </ul>
33
34 <div class:list={['panels', Astro.props.class, { 'panels--styled': styled }]}>
35 <slot />
36 </div>
37</tabbed-content>
38
39<style>
40 .tab-list {
41 list-style: none;
42 padding: 0;
43 }
44 .tab-list--styled {
45 display: flex;
46 margin-top: -1px;
47 overflow-x: auto;
48 overflow-y: hidden;
49 }
50 @media (min-width: 72em) {
51 .tab-list--styled {
52 justify-content: space-between;
53 margin-top: 0;
54 padding: 1px;
55 }
56 }
57
58 .panels--styled {
59 padding-left: 1px;
60 padding-right: 1px;
61 }
62</style>
63
64<script>
65 class Tabs extends HTMLElement {
66 readonly id = Math.floor(Math.random() * 10e10).toString(32);
67 count = 0;
68 TabStore: Set<HTMLElement>[] = [];
69 PanelStore: Set<HTMLElement>[] = [];
70
71 constructor() {
72 super();
73
74 // Get relevant elements and collections
75 const panels = this.querySelectorAll<HTMLElement>('.panels > [id]');
76 const tablist = this.querySelector('.tab-list')!;
77 const tabs = tablist.querySelectorAll('a');
78
79 // Add the tablist role to the first <ul> in the .tabbed container
80 tablist.setAttribute('role', 'tablist');
81
82 let initialTab = 0;
83
84 // Add semantics are remove user focusability for each tab
85 Array.prototype.forEach.call(tabs, (tab: HTMLElement, i: number) => {
86 tab.setAttribute('role', 'tab');
87 tab.setAttribute('id', this.id + 'tab' + this.count++);
88 tab.setAttribute('tabindex', '-1');
89 tab.parentElement?.setAttribute('role', 'presentation');
90 if (!this.TabStore[i]) this.TabStore.push(new Set());
91 this.TabStore[i].add(tab);
92 if ('initial' in tab.dataset && tab.dataset.initial !== 'false') initialTab = i;
93
94 // Handle clicking of tabs for mouse users
95 const onClick = (e: MouseEvent) => {
96 e.preventDefault();
97 const currentTab = tablist.querySelector('[aria-selected]');
98 if (e.currentTarget !== currentTab) {
99 this.switchTab(e.currentTarget as HTMLElement, i);
100 }
101 };
102 tab.addEventListener('click', onClick);
103 tab.addEventListener('auxclick', onClick);
104
105 // Handle keydown events for keyboard users
106 tab.addEventListener('keydown', (e) => {
107 // Get the index of the current tab in the tabs node list
108 const index: number = Array.prototype.indexOf.call(tabs, e.currentTarget);
109 // Work out which key the user is pressing and
110 // Calculate the new tab's index where appropriate
111 const dir =
112 e.key === 'ArrowLeft'
113 ? index - 1
114 : e.key === 'ArrowRight'
115 ? index + 1
116 : e.key === 'Home'
117 ? 0
118 : e.key === 'End'
119 ? tabs.length - 1
120 : null;
121 if (dir !== null) {
122 e.preventDefault();
123 if (tabs[dir]) this.switchTab(tabs[dir], dir);
124 }
125 });
126 });
127
128 // Add tab panel semantics and hide them all
129 Array.prototype.forEach.call(panels, (panel: HTMLElement, i: number) => {
130 panel.setAttribute('role', 'tabpanel');
131 panel.setAttribute('tabindex', '-1');
132 panel.setAttribute('aria-labelledby', tabs[i].id);
133 panel.hidden = true;
134 if (!this.PanelStore[i]) this.PanelStore.push(new Set());
135 this.PanelStore[i].add(panel);
136 });
137
138 // Activate and reveal the initial tab panel
139 tabs[initialTab].removeAttribute('tabindex');
140 tabs[initialTab].setAttribute('aria-selected', 'true');
141 panels[initialTab].hidden = false;
142 }
143
144 // The tab switching function
145 switchTab(newTab: HTMLElement, index: number) {
146 this.TabStore.forEach((store) =>
147 store.forEach((oldTab) => {
148 oldTab.removeAttribute('aria-selected');
149 oldTab.setAttribute('tabindex', '-1');
150 })
151 );
152 this.TabStore[index].forEach((newTab) => {
153 // Make the active tab focusable by the user (Tab key)
154 newTab.removeAttribute('tabindex');
155 // Set the selected state
156 newTab.setAttribute('aria-selected', 'true');
157 });
158
159 this.PanelStore.forEach((store) =>
160 store.forEach((oldPanel) => {
161 oldPanel.hidden = true;
162 })
163 );
164 this.PanelStore[index].forEach((newPanel) => {
165 newPanel.hidden = false;
166 });
167
168 newTab.focus();
169 }
170 }
171
172 customElements.define('tabbed-content', Tabs);
173</script>