Skip to content

Commit 745c8bd

Browse files
shairezigalklebanovgioragutt
committed
feat(headless/tabs): end,home,pagedn,pageup
Co-authored-by: Igal Klebanov <[email protected]> Co-authored-by: Giora Guttsait <[email protected]>
1 parent 01e4d42 commit 745c8bd

File tree

12 files changed

+159
-160
lines changed

12 files changed

+159
-160
lines changed

apps/website/src/routes/docs/headless/(components)/tabs/index.mdx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ import { APITable } from '../../../../../components/api-table/api-table';
111111
description: 'In "vertical mode", moves focus to the previous tab.',
112112
},
113113
{
114-
/* {
115114
keyTitle: 'Home',
116115
description: 'Moves focus to the first tab.',
117116
},
@@ -126,7 +125,6 @@ import { APITable } from '../../../../../components/api-table/api-table';
126125
{
127126
keyTitle: 'PageDown',
128127
description: 'Moves focus to the first tab.',
129-
}, */
130128
},
131129
]}
132130
/>
@@ -143,24 +141,22 @@ import { APITable } from '../../../../../components/api-table/api-table';
143141
description:
144142
'Toggle between automatic or manual. The automatic behavior moves between tabs when hover. The manual behavior moves between tabs on click.',
145143
},
144+
{
145+
name: 'selectedIndex',
146+
type: 'number',
147+
description: 'A way to set the selected index programmatically',
148+
},
146149
{
147150
name: 'vertical',
148151
type: 'boolean',
149152
description:
150153
'If set to true, the behavior of UpArrow and DownArrow will navigate between tabs vertically instead of horizontally.',
151154
},
152-
]}
153-
/>
154-
155-
### TabList
156-
157-
<APITable
158-
propDescriptors={[
159155
{
160-
name: 'labelledBy',
161-
type: 'string',
156+
name: 'onSelectedIndexChange$',
157+
type: '(index: number) => void',
162158
description:
163-
'The aria-labelledby for this tablist. Mainly for accessibility purpose.',
159+
'An event hook that getting notified whenever the selected index changes',
164160
},
165161
]}
166162
/>
@@ -176,8 +172,13 @@ import { APITable } from '../../../../../components/api-table/api-table';
176172
},
177173
{
178174
name: 'onClick',
179-
type: 'PropFunction<() => void>',
175+
type: '(event: QwikMouseEvent) => void',
180176
description: 'A custom click handler to wire to tab click event',
181177
},
178+
{
179+
name: 'disabled',
180+
type: 'boolean',
181+
description: 'Set the disabled state of a specific tab',
182+
},
182183
]}
183184
/>

packages/kit-headless/src/components/tabs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ export * from './behavior.type';
22
export * from './tabs-context.type';
33
export * from './tabs-context-id';
44
export * from './tabs';
5-
export * from './tabs-panel';
5+
export * from './tab-panel';
66
export * from './tabs-list';
77
export * from './tab';

packages/kit-headless/src/components/tabs/tabs-panel.tsx renamed to packages/kit-headless/src/components/tabs/tab-panel.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ import {
66
useTask$,
77
useSignal,
88
useVisibleTask$,
9+
QwikIntrinsicElements,
910
} from '@builder.io/qwik';
1011
import { tabsContextId } from './tabs-context-id';
1112
import { isBrowser, isServer } from '@builder.io/qwik/build';
1213

13-
export interface TabPanelProps {
14-
class?: string;
15-
}
14+
export type TabPanelProps = QwikIntrinsicElements['div'];
1615

1716
export const TabPanel = component$(({ ...props }: TabPanelProps) => {
1817
const contextService = useContext(tabsContextId);
@@ -22,8 +21,7 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
2221

2322
const panelUID = useId();
2423

25-
// Index task
26-
useTask$(async ({ cleanup }) => {
24+
useTask$(async function initIndexTask({ cleanup }) {
2725
if (isServer) {
2826
serverAssignedIndexSig.value =
2927
await contextService.getNextServerAssignedPanelIndex$();
@@ -36,24 +34,24 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
3634
});
3735
});
3836

39-
// matched panel id task
40-
useVisibleTask$(async ({ track }) => {
41-
matchedTabIdSig.value = await track(() =>
42-
contextService.getMatchedTabId$(panelUID)
37+
useTask$(async function isSelectedPanelTask({ track }) {
38+
const isSelected = await track(() =>
39+
contextService.isPanelSelected$(panelUID)
4340
);
44-
});
4541

46-
// is selected task
47-
useTask$(async ({ track }) => {
4842
if (isServer) {
4943
isSelectedSig.value = await contextService.isIndexSelected$(
5044
serverAssignedIndexSig.value
5145
);
5246
return;
5347
}
5448

55-
isSelectedSig.value = await track(() =>
56-
contextService.isPanelSelected$(panelUID)
49+
isSelectedSig.value = isSelected;
50+
});
51+
52+
useVisibleTask$(async function matchedPanelIdTask({ track }) {
53+
matchedTabIdSig.value = await track(() =>
54+
contextService.getMatchedTabId$(panelUID)
5755
);
5856
});
5957

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

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import {
2-
PropFunction,
32
component$,
43
useContext,
54
useId,
65
Slot,
7-
useComputed$,
86
useTask$,
97
$,
108
useSignal,
119
useVisibleTask$,
10+
QwikIntrinsicElements,
11+
type QwikMouseEvent,
1212
} from '@builder.io/qwik';
1313
import { tabsContextId } from './tabs-context-id';
1414
import { KeyCode } from '../../utils/key-code.type';
1515
import { isBrowser, isServer } from '@builder.io/qwik/build';
1616

17-
export interface TabProps {
18-
onClick?: PropFunction<() => void>;
19-
class?: string;
17+
export type TabProps = {
18+
onClick$?: (event: QwikMouseEvent) => void;
2019
selectedClassName?: string;
2120
disabled?: boolean;
22-
}
21+
} & QwikIntrinsicElements['button'];
2322

2423
export const Tab = component$((props: TabProps) => {
2524
const contextService = useContext(tabsContextId);
@@ -28,8 +27,7 @@ export const Tab = component$((props: TabProps) => {
2827
const matchedTabPanelIdSig = useSignal<string | undefined>(undefined);
2928
const uniqueTabId = useId();
3029

31-
// Index task
32-
useTask$(async ({ cleanup }) => {
30+
useTask$(async function indexInitTask({ cleanup }) {
3331
if (isServer) {
3432
serverAssignedIndexSig.value =
3533
await contextService.getNextServerAssignedTabIndex$();
@@ -43,46 +41,35 @@ export const Tab = component$((props: TabProps) => {
4341
});
4442
});
4543

46-
// is selected task
47-
useTask$(async ({ track }) => {
44+
useTask$(async function isSelectedTask({ track }) {
45+
const isTabSelected = await track(() =>
46+
contextService.isTabSelected$(uniqueTabId)
47+
);
4848
if (isServer) {
4949
isSelectedSig.value = await contextService.isIndexSelected$(
5050
serverAssignedIndexSig.value
5151
);
5252
return;
5353
}
54-
isSelectedSig.value = await track(() =>
55-
contextService.isTabSelected$(uniqueTabId)
56-
);
54+
isSelectedSig.value = isTabSelected;
5755
});
5856

59-
// disabled task
60-
useTask$(({ track }) => {
57+
useTask$(function disabledTask({ track }) {
6158
track(() => props.disabled);
6259

6360
if (props.disabled) {
6461
contextService.updateTabState$(uniqueTabId, { disabled: true });
6562
}
6663
});
6764

68-
// matched panel id task
69-
useVisibleTask$(async ({ track }) => {
65+
useVisibleTask$(async function setMatchedTabPanelIdTask({ track }) {
7066
matchedTabPanelIdSig.value = await track(() =>
7167
contextService.getMatchedPanelId$(uniqueTabId)
7268
);
7369
});
7470

75-
const selectTab$ = $(() => {
76-
if (props.disabled) {
77-
return;
78-
}
79-
contextService.selectTab$(uniqueTabId);
80-
});
81-
8271
const selectIfAutomatic$ = $(() => {
83-
if (contextService.behavior === 'automatic') {
84-
selectTab$();
85-
}
72+
contextService.selectIfAutomatic$(uniqueTabId);
8673
});
8774

8875
return (
@@ -101,12 +88,13 @@ export const Tab = component$((props: TabProps) => {
10188
class={`${
10289
isSelectedSig.value ? `selected ${props.selectedClassName || ''}` : ''
10390
}${props.class ? ` ${props.class}` : ''}`}
104-
onClick$={() => {
105-
selectTab$();
106-
if (props.onClick) {
107-
props.onClick();
91+
onClick$={(event) => {
92+
contextService.selectTab$(uniqueTabId);
93+
if (props.onClick$) {
94+
props.onClick$(event);
10895
}
10996
}}
97+
preventdefault:keydown
11098
onKeyDown$={(e) => {
11199
contextService.onTabKeyDown$(
112100
e.key as KeyCode,

packages/kit-headless/src/components/tabs/tabs-context.type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ export interface TabsContext {
1515
isIndexSelected$: QRL<(index?: number) => boolean>;
1616
isTabSelected$: QRL<(tabId: string) => boolean>;
1717
isPanelSelected$: QRL<(panelId: string) => boolean>;
18-
behavior: Behavior;
18+
selectIfAutomatic$: QRL<(tabId: string) => void>;
1919
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
import { component$, QwikIntrinsicElements, Slot } from '@builder.io/qwik';
2-
import { Behavior } from './behavior.type';
32

4-
export type TabListProps = QwikIntrinsicElements['div'] & {
5-
labelledBy?: string;
6-
behavior?: Behavior;
7-
};
3+
export type TabListProps = QwikIntrinsicElements['div'];
84

95
// List of tabs that can be clicked to show different content.
106
export const TabList = component$((props: TabListProps) => {
11-
const { labelledBy, ...rest } = props;
12-
137
return (
14-
<div role="tablist" aria-labelledby={labelledBy} {...rest}>
8+
<div role="tablist" {...props}>
159
<Slot />
1610
</div>
1711
);

0 commit comments

Comments
 (0)