Skip to content

Commit 156e2af

Browse files
authored
Merge pull request #554 from maiieul/fix-tabs
2 parents 252884b + a82feae commit 156e2af

File tree

6 files changed

+172
-12
lines changed

6 files changed

+172
-12
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
66
],
77
"editor.codeActionsOnSave": {
8-
"source.removeUnusedImports": true
8+
"source.removeUnusedImports": "explicit"
99
}
1010
}

apps/website/src/components/mdx-components/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,10 @@ export const components: Record<string, any> = {
159159
}),
160160
AnatomyTable,
161161
APITable,
162+
CodeSnippet,
163+
InstallSnippet,
162164
KeyboardInteractionTable,
165+
Note,
163166
StatusBanner,
164167
Showcase,
165-
CodeSnippet,
166-
InstallSnippet,
167168
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Slot, component$ } from '@builder.io/qwik';
2+
import {
3+
Tab,
4+
TabList,
5+
TabListProps,
6+
TabPanel,
7+
TabPanelProps,
8+
TabProps,
9+
Tabs,
10+
TabsProps,
11+
} from '@qwik-ui/headless';
12+
13+
const CustomTabs = (props: TabsProps) => (
14+
<Tabs {...props} TabList={CustomTabList} Tab={CustomTab} TabPanel={CustomTabPanel} />
15+
);
16+
17+
const CustomTabList = component$<TabListProps>(() => {
18+
return (
19+
<TabList>
20+
<Slot />
21+
</TabList>
22+
);
23+
});
24+
25+
const CustomTab = component$<TabProps>(({ ...props }) => {
26+
return (
27+
<Tab {...props}>
28+
<span class="text-red-500">Custom</span>
29+
<Slot />
30+
</Tab>
31+
);
32+
});
33+
34+
const CustomTabPanel = component$<TabPanelProps>(({ ...props }) => {
35+
return (
36+
<TabPanel {...props}>
37+
<span class="text-red-500">Description:</span>
38+
<Slot />
39+
</TabPanel>
40+
);
41+
});
42+
43+
export default component$(() => {
44+
return (
45+
<div class="tabs-example mr-auto">
46+
<CustomTabs>
47+
<CustomTabList>
48+
<CustomTab>Tab 1</CustomTab>
49+
<CustomTab>Tab 2</CustomTab>
50+
<CustomTab>Tab 3</CustomTab>
51+
</CustomTabList>
52+
<CustomTabPanel>
53+
<p>Maria Theresia Ahlefeldt (16 January 1755 - 20 December 1810) ...</p>
54+
</CustomTabPanel>
55+
<CustomTabPanel>
56+
<p>Carl Joachim Andersen (29 April 1847 - 7 May 1909) ...</p>
57+
</CustomTabPanel>
58+
<CustomTabPanel>
59+
<p>Ida Henriette da Fonseca (July 27, 1802 - July 6, 1858) ...</p>
60+
</CustomTabPanel>
61+
</CustomTabs>
62+
</div>
63+
);
64+
});

apps/website/src/routes/docs/headless/tabs/index.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ You can pass a custom `onClick$` handler.
8585

8686
<Showcase name="on-click" />
8787

88+
## Creating reusable Tabs
89+
90+
In order to remove the need for a linking `value` prop for each Tab and TabPanel pair, we have chosen to create the Tabs component as an [inline component](https://qwik.builder.io/docs/components/overview/#inline-components).
91+
92+
By consequence, the Tabs component needs to be aware of its children. If you want to create your custom reusable TabList/Tab/TabPanel components, you will have to pass them to the Tabs component through its `TabList`, `Tab`, and `TabPanel` props:
93+
94+
```tsx
95+
const CustomTabs = (props: TabsProps) => (
96+
<Tabs {...props} TabList={CustomTabList} Tab={CustomTab} TabPanel={CustomTabPanel} />
97+
);
98+
```
99+
100+
<br />
101+
102+
<Note status="warning">
103+
If you don't do the above, the Tabs component cannot know if your CustomTab component is
104+
a Tab component.
105+
</Note>
106+
107+
<Showcase name="reusable" />
108+
88109
## Accessibility
89110

90111
### Keyboard interaction

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

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { $, component$, useSignal, useStore } from '@builder.io/qwik';
2-
import { Tab } from './tab';
3-
import { TabPanel } from './tab-panel';
1+
import { $, Slot, component$, useSignal, useStore } from '@builder.io/qwik';
42
import { Tabs } from './tabs';
5-
import { TabList } from './tabs-list';
3+
import { TabList, TabListProps } from './tabs-list';
4+
import { Tab, TabProps } from './tab';
5+
import { TabPanel, TabPanelProps } from './tab-panel';
66

77
describe('Tabs', () => {
88
it('INIT', () => {
@@ -768,4 +768,62 @@ describe('Tabs', () => {
768768
cy.findByRole('tablist').should('exist');
769769
});
770770
});
771+
772+
describe('User-defined reusable TabList/Tab/TabPanel components', () => {
773+
const CustomTabList = component$<TabListProps>(() => {
774+
return (
775+
<TabList>
776+
<Slot />
777+
</TabList>
778+
);
779+
});
780+
781+
const CustomTab = component$<TabProps>(({ ...props }) => {
782+
return (
783+
<Tab {...props}>
784+
<Slot />
785+
</Tab>
786+
);
787+
});
788+
789+
const CustomTabPanel = component$<TabPanelProps>(({ ...props }) => {
790+
return (
791+
<TabPanel {...props}>
792+
<Slot />
793+
</TabPanel>
794+
);
795+
});
796+
797+
const CustomThreeTabsComponent = component$(() => {
798+
return (
799+
<>
800+
<Tabs TabList={CustomTabList} Tab={CustomTab} TabPanel={CustomTabPanel}>
801+
<CustomTabList>
802+
<CustomTab>Tab 1</CustomTab>
803+
<CustomTab>Tab 2</CustomTab>
804+
<CustomTab>Tab 3</CustomTab>
805+
</CustomTabList>
806+
<CustomTabPanel>Panel 1</CustomTabPanel>
807+
<CustomTabPanel>Panel 2</CustomTabPanel>
808+
<CustomTabPanel>Panel 3</CustomTabPanel>
809+
</Tabs>
810+
</>
811+
);
812+
});
813+
it(`GIVEN a user-defined TabList to Tabs
814+
WHEN clicking the middle Tab
815+
THEN render the middle panel`, () => {
816+
cy.mount(<CustomThreeTabsComponent />);
817+
818+
cy.get('[role="tab"]').should('have.length', 3);
819+
820+
cy.findByRole('tabpanel').should('contain', 'Panel 1');
821+
822+
cy.findByRole('tab', { name: /Tab 2/i }).click();
823+
824+
cy.findByRole('tabpanel').should('contain', 'Panel 2');
825+
826+
cy.findByRole('tablist').should('exist');
827+
});
828+
});
771829
});

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {
1313
} from '@builder.io/qwik';
1414
import { KeyCode } from '../../utils/key-code.type';
1515
import { Behavior } from './behavior.type';
16-
import { Tab, TabProps } from './tab';
17-
import { TabPanel, TabPanelProps } from './tab-panel';
16+
import { Tab as InternalTab, TabProps } from './tab';
17+
import { TabPanel as InternalTabPanel, TabPanelProps } from './tab-panel';
1818
import { tabsContextId } from './tabs-context-id';
1919
import { TabsContext } from './tabs-context.type';
20-
import { TabList } from './tabs-list';
20+
import { TabList as InternalTabList } from './tabs-list';
2121

2222
/**
2323
* TABS TODOs
@@ -56,6 +56,9 @@ export type TabsProps = {
5656
'bind:selectedTabId'?: Signal<string | undefined>;
5757
tabClass?: QwikIntrinsicElements['div']['class'];
5858
panelClass?: QwikIntrinsicElements['div']['class'];
59+
TabList?: typeof InternalTabList;
60+
Tab?: typeof InternalTab;
61+
TabPanel?: typeof InternalTabPanel;
5962
} & QwikIntrinsicElements['div'];
6063

6164
export type TabInfo = {
@@ -69,8 +72,21 @@ export type TabInfo = {
6972
// standard structure. It must take care to retain the props objects
7073
// unchanged so signals keep working
7174
export const Tabs: FunctionComponent<TabsProps> = (props) => {
72-
const { children: myChildren, tabClass, panelClass, ...rest } = props;
73-
const childrenToProcess = Array.isArray(myChildren) ? [...myChildren] : [myChildren];
75+
const {
76+
children,
77+
tabClass,
78+
panelClass,
79+
TabList: UserTabList,
80+
Tab: UserTab,
81+
TabPanel: UserTabPanel,
82+
...rest
83+
} = props;
84+
85+
const TabList = UserTabList ? UserTabList : InternalTabList;
86+
const Tab = UserTab ? UserTab : InternalTab;
87+
const TabPanel = UserTabPanel ? UserTabPanel : InternalTabPanel;
88+
89+
const childrenToProcess = Array.isArray(children) ? [...children] : [children];
7490
let tabListComponent: JSXNode | undefined;
7591
const tabComponents: JSXNode[] = [];
7692
const panelComponents: JSXNode[] = [];

0 commit comments

Comments
 (0)