Skip to content

Commit 3902404

Browse files
committed
WIP: fixing tabs
1 parent 4e7caf1 commit 3902404

File tree

1 file changed

+50
-26
lines changed
  • packages/kit-headless/src/components/tabs

1 file changed

+50
-26
lines changed

packages/kit-headless/src/components/tabs/tabs.tsx

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,38 +48,50 @@ export type Behavior = 'automatic' | 'manual';
4848

4949
interface TabsContext {
5050
selectedIndex: Signal<number>;
51-
getNextTabIndex: QRL<() => number>;
52-
getNextPanelIndex: QRL<() => number>;
51+
selectedTabId: Signal<string>;
52+
registerTab: QRL<(tabId: string) => void>;
5353
behavior: Behavior;
54+
reIndexTabs: Signal<boolean>;
55+
setTabIndex: QRL<(tabId: string, index: number) => void>;
5456
}
5557

5658
export const tabsContextId = createContextId<TabsContext>('qui--tabList');
5759

5860
export interface TabsProps {
5961
behavior?: Behavior;
6062
class?: string;
63+
selectedIndex?: number;
64+
}
65+
66+
export interface TabIndexMap {
67+
[key: string]: number | undefined;
6168
}
6269

6370
export const Tabs = component$((props: TabsProps) => {
6471
const behavior = props.behavior ?? 'manual';
65-
const lastTabIndex = useSignal(0);
66-
const lastPanelIndex = useSignal(0);
72+
const selectedIndex = useSignal(props.selectedIndex || 0);
73+
const reIndexTabs = useSignal(false);
74+
75+
const tabsRegistry: TabIndexMap = {};
6776

68-
const getNextTabIndex = $(() => {
69-
return lastTabIndex.value++;
77+
const setTabIndex = $((tabId: string, index: number) => {
78+
tabsRegistry[tabId] = index;
7079
});
7180

72-
const getNextPanelIndex = $(() => {
73-
return lastPanelIndex.value++;
81+
const registerTab = $((tabId: string) => {
82+
tabsRegistry[tabId] = undefined;
83+
reIndexTabs.value = true;
7484
});
7585

76-
const selected = useSignal(0);
86+
const selectedTabId = useSignal('');
7787

7888
const contextService: TabsContext = {
79-
selectedIndex: selected,
80-
getNextTabIndex,
81-
getNextPanelIndex,
89+
selectedIndex,
90+
registerTab,
91+
setTabIndex,
8292
behavior,
93+
reIndexTabs,
94+
selectedTabId,
8395
};
8496

8597
const tabsInitialized = useSignal(false);
@@ -109,8 +121,25 @@ interface TabListProps {
109121
// List of tabs that can be clicked to show different content.
110122
export const TabList = component$((props: TabListProps) => {
111123
const { labelledBy, ...rest } = props;
124+
const contextService = useContext(tabsContextId);
125+
const ref = useSignal<Element | undefined>();
126+
127+
useVisibleTask$(({ track }) => {
128+
track(() => contextService.reIndexTabs.value);
129+
130+
if (ref.value) {
131+
ref.value.querySelectorAll('[role="tab"]').forEach((tab, index) => {
132+
const tabId = tab.getAttribute('id');
133+
134+
if (tabId) {
135+
contextService.setTabIndex(tabId, index);
136+
}
137+
});
138+
}
139+
});
140+
112141
return (
113-
<div role="tablist" aria-labelledby={labelledBy} {...rest}>
142+
<div ref={ref} role="tablist" aria-labelledby={labelledBy} {...rest}>
114143
<Slot />
115144
</div>
116145
);
@@ -124,38 +153,31 @@ interface TabProps {
124153

125154
// Tab button inside of a tab list
126155
export const Tab = component$(
127-
({ selectedClassName, onClick, ...props }: TabProps) => {
156+
({ selectedClassName, onClick, class: classString }: TabProps) => {
128157
const contextService = useContext(tabsContextId);
129158
const thisTabIndex = useSignal(0);
130159

131-
useVisibleTask$(async () => {
132-
thisTabIndex.value = await contextService.getNextTabIndex();
133-
console.log('useVisibleTask$', thisTabIndex.value);
134-
});
135-
136-
// TODO: Ask Manu about this 😊
137-
const isSelected = () =>
138-
thisTabIndex.value === contextService.selectedIndex.value;
160+
const isSelected = () => forTab === contextService.selectedTabId.value;
139161

140162
const selectIfAutomatic = $(() => {
141163
if (contextService.behavior === 'automatic') {
142164
contextService.selectedIndex.value = thisTabIndex.value;
143165
}
144166
});
145167

146-
const uniqueId = useId();
168+
const id = useId();
147169

148170
return (
149171
<button
150-
id={uniqueId}
172+
id={id}
151173
type="button"
152174
role="tab"
153175
onFocus$={selectIfAutomatic}
154176
onMouseEnter$={selectIfAutomatic}
155177
aria-selected={isSelected()}
156178
aria-controls={`tabpanel-${thisTabIndex.value}`}
157179
class={`${isSelected() ? `selected ${selectedClassName}` : ''}${
158-
props.class ? ` ${props.class}` : ''
180+
classString ? ` ${classString}` : ''
159181
}`}
160182
onClick$={$(() => {
161183
contextService.selectedIndex.value = thisTabIndex.value;
@@ -182,7 +204,9 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
182204
const isSelected = () =>
183205
thisPanelIndex.value === contextService.selectedIndex.value;
184206
useVisibleTask$(async () => {
185-
thisPanelIndex.value = await contextService.getNextPanelIndex();
207+
setTimeout(async () => {
208+
thisPanelIndex.value = await contextService.getNextPanelIndex();
209+
});
186210
});
187211
const uniqueId = useId();
188212
return (

0 commit comments

Comments
 (0)