Skip to content

Commit de08124

Browse files
shairezwmertens
andcommitted
feat(headless/tabs): shortcut for adding tabs
Co-authored-by: Wout Mertens <Wout.Mertens@gmail.com>
1 parent 05323c6 commit de08124

File tree

6 files changed

+167
-98
lines changed

6 files changed

+167
-98
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ export const AutomaticBehaviorTabsExample = component$(() => {
102102
return (
103103
<PreviewCodeExample>
104104
<div q:slot="actualComponent" class="tabs-example mr-auto">
105+
<h3>Danish Composers</h3>
106+
<h4>(Hover over the tabs)</h4>
105107
<Tabs behavior="automatic">
106-
<h3>Danish Composers</h3>
107-
<h4>(Hover over the tabs)</h4>
108108
<TabList>
109109
<Tab>Maria Ahlefeldt</Tab>
110110
<Tab>Carl Andersen</Tab>

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import {
2+
QwikIntrinsicElements,
3+
Slot,
24
component$,
35
useContext,
46
useId,
5-
Slot,
6-
useTask$,
77
useSignal,
8-
useVisibleTask$,
9-
QwikIntrinsicElements,
8+
useTask$,
9+
useVisibleTask$
1010
} from '@builder.io/qwik';
11-
import { tabsContextId } from './tabs-context-id';
1211
import { isBrowser, isServer } from '@builder.io/qwik/build';
12+
import { tabsContextId } from './tabs-context-id';
1313

14-
export type TabPanelProps = QwikIntrinsicElements['div'];
14+
export type TabPanelProps = {
15+
/** @deprecated Internal use only */
16+
_key?: string;
17+
} & QwikIntrinsicElements['div'];
1518

1619
export const TabPanel = component$(({ ...props }: TabPanelProps) => {
1720
const contextService = useContext(tabsContextId);
@@ -35,9 +38,7 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
3538
});
3639

3740
useTask$(async function isSelectedPanelTask({ track }) {
38-
const isSelected = await track(() =>
39-
contextService.isPanelSelected$(panelUID)
40-
);
41+
const isSelected = await track(() => contextService.isPanelSelected$(panelUID));
4142

4243
if (isServer) {
4344
isSelectedSig.value = await contextService.isIndexSelected$(
@@ -50,9 +51,7 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
5051
});
5152

5253
useVisibleTask$(async function matchedPanelIdTask({ track }) {
53-
matchedTabIdSig.value = await track(() =>
54-
contextService.getMatchedTabId$(panelUID)
55-
);
54+
matchedTabIdSig.value = await track(() => contextService.getMatchedTabId$(panelUID));
5655
});
5756

5857
return (

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import {
2+
$,
3+
Slot,
24
component$,
5+
useComputed$,
36
useContext,
47
useId,
5-
Slot,
6-
useTask$,
7-
$,
88
useSignal,
9+
useTask$,
910
useVisibleTask$,
1011
type QwikIntrinsicElements,
11-
type QwikMouseEvent,
12-
useComputed$,
12+
type QwikMouseEvent
1313
} from '@builder.io/qwik';
14-
import { tabsContextId } from './tabs-context-id';
15-
import { KeyCode } from '../../utils/key-code.type';
1614
import { isBrowser, isServer } from '@builder.io/qwik/build';
15+
import { KeyCode } from '../../utils/key-code.type';
16+
import { tabsContextId } from './tabs-context-id';
1717

1818
export type TabProps = {
1919
onClick$?: (event: QwikMouseEvent) => void;
2020
selectedClassName?: string;
2121
disabled?: boolean;
22+
/** @deprecated Internal use only */
23+
_key?: string;
2224
} & QwikIntrinsicElements['button'];
2325

2426
export const preventedKeys = [
@@ -29,7 +31,7 @@ export const preventedKeys = [
2931
KeyCode.ArrowDown,
3032
KeyCode.ArrowUp,
3133
KeyCode.ArrowLeft,
32-
KeyCode.ArrowRight,
34+
KeyCode.ArrowRight
3335
];
3436

3537
export const Tab = component$((props: TabProps) => {
@@ -59,9 +61,7 @@ export const Tab = component$((props: TabProps) => {
5961
});
6062

6163
useTask$(async function isSelectedTask({ track }) {
62-
const isTabSelected = await track(() =>
63-
contextService.isTabSelected$(uniqueTabId)
64-
);
64+
const isTabSelected = await track(() => contextService.isTabSelected$(uniqueTabId));
6565
if (isServer && !props.disabled) {
6666
isSelectedSig.value = await contextService.isIndexSelected$(
6767
serverAssignedIndexSig.value
@@ -117,9 +117,7 @@ export const Tab = component$((props: TabProps) => {
117117
tabIndex={isSelectedSig.value ? 0 : -1}
118118
aria-controls={'tabpanel-' + matchedTabPanelIdSig.value}
119119
class={`${
120-
isSelectedSig.value
121-
? `selected ${selectedClassNameSig.value || ''}`
122-
: ''
120+
isSelectedSig.value ? `selected ${selectedClassNameSig.value || ''}` : ''
123121
}${props.class ? ` ${props.class}` : ''}`}
124122
onClick$={(event) => {
125123
contextService.selectTab$(uniqueTabId);

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

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { component$, useSignal, useStore, $ } from '@builder.io/qwik';
1+
import { $, component$, useSignal, useStore } from '@builder.io/qwik';
22
import { Tab } from './tab';
3+
import { TabPanel } from './tab-panel';
34
import { Tabs } from './tabs';
45
import { TabList } from './tabs-list';
5-
import { TabPanel } from './tab-panel';
66

77
describe('Tabs', () => {
88
it('INIT', () => {
@@ -53,9 +53,7 @@ describe('Tabs', () => {
5353
<TabList>
5454
<Tab onClick$={() => (wasSelectedSig.value = true)}>Tab 1</Tab>
5555
</TabList>
56-
<TabPanel>
57-
Custom onClick was called: {`${wasSelectedSig.value}`}
58-
</TabPanel>
56+
<TabPanel>Custom onClick was called: {`${wasSelectedSig.value}`}</TabPanel>
5957
</Tabs>
6058
);
6159
});
@@ -64,10 +62,7 @@ describe('Tabs', () => {
6462

6563
cy.findByRole('tab', { name: /Tab 1/i }).click();
6664

67-
cy.findByRole('tabpanel').should(
68-
'contain',
69-
'Custom onClick was called: true'
70-
);
65+
cy.findByRole('tabpanel').should('contain', 'Custom onClick was called: true');
7166
});
7267

7368
describe('Dynamic Tabs', () => {
@@ -121,11 +116,7 @@ describe('Tabs', () => {
121116
}
122117

123118
const DynamicTabsComponent = component$(
124-
({
125-
tabIndexToDelete = 0,
126-
tabIndexToAdd = 0,
127-
tabsLength,
128-
}: DynamicTabsProps) => {
119+
({ tabIndexToDelete = 0, tabIndexToAdd = 0, tabsLength }: DynamicTabsProps) => {
129120
const tabNames = Array(tabsLength)
130121
.fill(1)
131122
.map((_, index) => `Dynamic Tab ${index + 1}`);
@@ -147,11 +138,7 @@ describe('Tabs', () => {
147138
<button onClick$={() => tabsState.splice(tabIndexToDelete, 1)}>
148139
Remove Tab
149140
</button>
150-
<button
151-
onClick$={() =>
152-
tabsState.splice(tabIndexToAdd, 0, 'new added tab')
153-
}
154-
>
141+
<button onClick$={() => tabsState.splice(tabIndexToAdd, 0, 'new added tab')}>
155142
Add Tab
156143
</button>
157144
</>
@@ -168,9 +155,7 @@ describe('Tabs', () => {
168155

169156
cy.findAllByRole('tab', { name: /Tab 2/i }).first().click();
170157

171-
cy.findByRole('tabpanel')
172-
.should('be.visible')
173-
.should('contain', 'Root Panel 2');
158+
cy.findByRole('tabpanel').should('be.visible').should('contain', 'Root Panel 2');
174159
});
175160

176161
it(`GIVEN tabs inside of tabs
@@ -433,7 +418,7 @@ describe('Tabs', () => {
433418
<TabList
434419
style={{
435420
display: 'flex',
436-
flexDirection: isVertical ? 'column' : 'row',
421+
flexDirection: isVertical ? 'column' : 'row'
437422
}}
438423
>
439424
<Tab>Tab 1</Tab>
@@ -602,9 +587,7 @@ describe('Tabs', () => {
602587
it(`GIVEN 3 vertical tabs and the second is disabled and the focus is on the first,
603588
WHEN triggering the down arrow key
604589
THEN the focus should be on the third tab`, () => {
605-
cy.mount(
606-
<PotentiallyDisabledThreeTabs isVertical={true} disabledIndex={1} />
607-
);
590+
cy.mount(<PotentiallyDisabledThreeTabs isVertical={true} disabledIndex={1} />);
608591

609592
cy.findByRole('tab', { name: /Tab 1/i }).type('{downarrow}');
610593

@@ -614,9 +597,7 @@ describe('Tabs', () => {
614597
it(`GIVEN 3 vertical tabs and the second is disabled and the focus is on the third,
615598
WHEN triggering the up arrow key
616599
THEN the focus should be on the first tab`, () => {
617-
cy.mount(
618-
<PotentiallyDisabledThreeTabs isVertical={true} disabledIndex={1} />
619-
);
600+
cy.mount(<PotentiallyDisabledThreeTabs isVertical={true} disabledIndex={1} />);
620601

621602
cy.findByRole('tab', { name: /Tab 3/i }).type('{uparrow}');
622603

@@ -636,16 +617,12 @@ describe('Tabs', () => {
636617
vertical={!!props.isVertical}
637618
style={{
638619
display: 'flex',
639-
flexDirection: props.isVertical ? 'column' : 'row',
620+
flexDirection: props.isVertical ? 'column' : 'row'
640621
}}
641622
>
642623
<TabList>
643624
<Tab disabled={props.disabledIndex === 0}>Tab 1</Tab>
644-
<Tab
645-
disabled={
646-
props.disabledIndex === 1 || isMiddleDisabledSig.value
647-
}
648-
>
625+
<Tab disabled={props.disabledIndex === 1 || isMiddleDisabledSig.value}>
649626
Tab 2
650627
</Tab>
651628
<Tab disabled={props.disabledIndex === 2}>Tab 3</Tab>
@@ -656,9 +633,7 @@ describe('Tabs', () => {
656633
</Tabs>
657634
{props.showDisableButton && (
658635
<button
659-
onClick$={() =>
660-
(isMiddleDisabledSig.value = !isMiddleDisabledSig.value)
661-
}
636+
onClick$={() => (isMiddleDisabledSig.value = !isMiddleDisabledSig.value)}
662637
>
663638
Toggle middle tab disabled
664639
</button>

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

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1+
import { expect } from '@storybook/jest';
2+
import { screen, userEvent, within } from '@storybook/testing-library';
13
import { Meta, StoryObj } from 'storybook-framework-qwik';
2-
import {
3-
useSignal,
4-
useStore,
5-
component$,
6-
useComputed$,
7-
} from '@builder.io/qwik';
84
import { Tab, TabList, TabPanel, Tabs, TabsProps } from './';
9-
import { userEvent, within, screen, waitFor } from '@storybook/testing-library';
10-
import { expect } from '@storybook/jest';
115

126
const meta: Meta<TabsProps> = {
137
component: Tabs,
148
args: {
15-
behavior: 'automatic',
9+
behavior: 'automatic'
1610
},
1711
argTypes: {
1812
behavior: {
1913
control: {
20-
type: 'select',
14+
type: 'select'
2115
},
22-
options: ['automatic', 'manual'],
23-
},
24-
},
16+
options: ['automatic', 'manual']
17+
}
18+
}
2519
};
2620

2721
export default meta;
@@ -55,7 +49,7 @@ export const Primary: Story = {
5549
const activeTabPanel = await canvas.findByRole('tabpanel');
5650

5751
await expect(activeTabPanel).toHaveTextContent('Panel 2');
58-
},
52+
}
5953
};
6054

6155
// export const TabsWithMiddleDisabled: Story = {

0 commit comments

Comments
 (0)