Skip to content

Commit a4110dc

Browse files
committed
WIP: trying to fix tabs
1 parent 3902404 commit a4110dc

File tree

10 files changed

+290
-140
lines changed

10 files changed

+290
-140
lines changed

apps/website/src/routes/docs/headless/(components)/tabs/examples.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ export const Example01 = component$(() => {
1818
</TabList>
1919
<TabPanel>
2020
<p>
21-
Maria Theresia Ahlefeldt (16 January 1755 20 December 1810) was
21+
Maria Theresia Ahlefeldt (16 January 1755 - 20 December 1810) was
2222
a ...
2323
</p>
2424
</TabPanel>
2525
<TabPanel>
26-
<p>Carl Joachim Andersen (29 April 1847 7 May 1909) was a ...</p>
26+
<p>Carl Joachim Andersen (29 April 1847 - 7 May 1909) was a ...</p>
2727
</TabPanel>
2828
<TabPanel>
2929
<p>
30-
Ida Henriette da Fonseca (July 27, 1802 July 6, 1858) was a ...
30+
Ida Henriette da Fonseca (July 27, 1802 - July 6, 1858) was a ...
3131
</p>
3232
</TabPanel>
3333
</Tabs>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Behavior = 'automatic' | 'manual';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from './behavior.type';
2+
export * from './tabs-context.type';
3+
export * from './tabs-context-id';
4+
export * from './tabs';
5+
export * from './tabs-panel';
6+
export * from './tabs-list';
7+
export * from './tab';
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {
2+
PropFunction,
3+
component$,
4+
useContext,
5+
useId,
6+
Slot,
7+
useComputed$,
8+
useTask$,
9+
$,
10+
useVisibleTask$,
11+
useSignal,
12+
} from '@builder.io/qwik';
13+
import { tabsContextId } from './tabs-context-id';
14+
15+
export interface TabProps {
16+
onClick?: PropFunction<() => void>;
17+
class?: string;
18+
selectedClassName?: string;
19+
}
20+
21+
// Tab button inside of a tab list
22+
export const Tab = component$(
23+
({ selectedClassName, onClick, class: classString }: TabProps) => {
24+
const contextService = useContext(tabsContextId);
25+
26+
const uniqueId = useId();
27+
28+
const currentTabIndex = useSignal(0);
29+
30+
const isSelectedSignal = useSignal(false);
31+
32+
useTask$(({ cleanup }) => {
33+
contextService.tabsChanged$();
34+
35+
cleanup(() => {
36+
contextService.tabsChanged$();
37+
});
38+
});
39+
40+
useTask$(({ track }) => {
41+
track(contextService.indexByTabId);
42+
console.log('contextService.indexByTabId', contextService.indexByTabId);
43+
currentTabIndex.value = contextService.indexByTabId[uniqueId];
44+
});
45+
46+
useVisibleTask$(() => {
47+
console.log(
48+
'contextService.selectedIndex.value',
49+
contextService.selectedIndex.value
50+
);
51+
console.log('currentTabIndex.value', currentTabIndex.value);
52+
isSelectedSignal.value =
53+
contextService.selectedIndex.value === currentTabIndex.value;
54+
});
55+
56+
const selectTab$ = $(() => {
57+
contextService.selectedIndex.value = currentTabIndex.value;
58+
});
59+
60+
const selectIfAutomatic$ = $(() => {
61+
if (contextService.behavior === 'automatic') {
62+
selectTab$();
63+
}
64+
});
65+
66+
return (
67+
<button
68+
data-tab-id={uniqueId}
69+
type="button"
70+
role="tab"
71+
onFocus$={selectIfAutomatic$}
72+
onMouseEnter$={selectIfAutomatic$}
73+
aria-selected={isSelectedSignal.value}
74+
aria-controls={'tabpanel-' + currentTabIndex.value}
75+
class={`${isSelectedSignal ? `selected ${selectedClassName}` : ''}${
76+
classString ? ` ${classString}` : ''
77+
}`}
78+
onClick$={async () => {
79+
await selectTab$();
80+
if (onClick) {
81+
onClick();
82+
}
83+
}}
84+
>
85+
<Slot />
86+
</button>
87+
);
88+
}
89+
);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createContextId } from '@builder.io/qwik';
2+
import { TabsContext } from './tabs-context.type';
3+
4+
export const tabsContextId = createContextId<TabsContext>('qui--tabList');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Signal, QRL } from '@builder.io/qwik';
2+
import { Behavior } from './behavior.type';
3+
4+
export interface TabsContext {
5+
selectedIndex: Signal<number>;
6+
selectedTabId: Signal<string>;
7+
selectedTabPanelId: Signal<string>;
8+
selectTab$: QRL<(tabId: string) => void>;
9+
tabsChanged$: QRL<() => void>;
10+
indexByTabId: { [key: string]: number };
11+
indexByTabPanelId: { [key: string]: number };
12+
behavior: Behavior;
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { component$, Slot } from '@builder.io/qwik';
2+
import { Behavior } from './behavior.type';
3+
4+
export interface TabListProps {
5+
labelledBy?: string;
6+
behavior?: Behavior;
7+
class?: string;
8+
}
9+
10+
// List of tabs that can be clicked to show different content.
11+
export const TabList = component$((props: TabListProps) => {
12+
const { labelledBy, ...rest } = props;
13+
14+
return (
15+
<div role="tablist" aria-labelledby={labelledBy} {...rest}>
16+
<Slot />
17+
</div>
18+
);
19+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
component$,
3+
useContext,
4+
useSignal,
5+
useVisibleTask$,
6+
useId,
7+
Slot,
8+
useTask$,
9+
useComputed$,
10+
} from '@builder.io/qwik';
11+
import { tabsContextId } from './tabs-context-id';
12+
13+
export interface TabPanelProps {
14+
class?: string;
15+
}
16+
17+
// Tab Panel implementation
18+
export const TabPanel = component$(({ ...props }: TabPanelProps) => {
19+
const { class: classNames, ...rest } = props;
20+
const contextService = useContext(tabsContextId);
21+
const uniqueId = useId();
22+
23+
const currentPanelIndex = useSignal(0);
24+
25+
const isSelectedSignal = useComputed$(
26+
() => contextService.selectedIndex.value === currentPanelIndex.value
27+
);
28+
29+
useTask$(({ cleanup }) => {
30+
contextService.tabsChanged$();
31+
32+
cleanup(() => {
33+
contextService.tabsChanged$();
34+
});
35+
});
36+
37+
useTask$(({ track }) => {
38+
track(contextService.indexByTabPanelId);
39+
currentPanelIndex.value = contextService.indexByTabPanelId[uniqueId];
40+
});
41+
42+
return (
43+
<div
44+
data-tabpanel-id={uniqueId}
45+
id={'tabpanel-' + currentPanelIndex.value}
46+
role="tabpanel"
47+
tabIndex={0}
48+
aria-labelledby={`tab-${currentPanelIndex}`}
49+
class={`${isSelectedSignal.value ? 'is-hidden' : ''}${
50+
classNames ? ` ${classNames}` : ''
51+
}`}
52+
style={isSelectedSignal.value ? 'display: block' : 'display: none'}
53+
{...rest}
54+
>
55+
<p>thisPanelIndex.value: {currentPanelIndex.value} </p>
56+
<p>contextService.selectedIndex: {contextService.selectedIndex} </p>
57+
<Slot />
58+
</div>
59+
);
60+
});

0 commit comments

Comments
 (0)