Skip to content

Commit 19bff2a

Browse files
authored
feat: adds subcomponent mapping to tabs and tooltips (#1734)
1 parent df6bf42 commit 19bff2a

File tree

12 files changed

+146
-74
lines changed

12 files changed

+146
-74
lines changed

docs/migration.md

+7
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ consider additional positioning prop support on a case-by-case basis.
127127
- All subcomponent exports have been deprecated and will be removed in a future major version.
128128
Update to subcomponent properties (e.g., `Table.Body`).
129129

130+
#### @zendeskgarden/react-tabs
131+
132+
- All subcomponent exports have been deprecated and will be removed in a future major version.
133+
Update to subcomponent properties (e.g., `Tabs.TabList`).
134+
130135
#### @zendeskgarden/react-theming
131136

132137
- Utility function `isRtl` has been removed. Use `props.theme.rtl` instead.
@@ -143,6 +148,8 @@ consider additional positioning prop support on a case-by-case basis.
143148
- `Tooltip`
144149
- removed `eventsEnabled` prop (no longer exposed by Floating UI)
145150
- removed `popperModifiers` prop (see [note](#breaking-changes))
151+
- All subcomponent exports have been deprecated and will be removed in a future major version.
152+
Update to subcomponent properties (e.g., `Tooltip.Title`).
146153

147154
#### @zendeskgarden/react-utilities
148155

packages/tabs/README.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ npm install react react-dom styled-components @zendeskgarden/react-theming
1717
```jsx
1818
import React, { useState } from 'react';
1919
import { ThemeProvider } from '@zendeskgarden/react-theming';
20-
import { Tabs, TabList, Tab, TabPanel } from '@zendeskgarden/react-tabs';
20+
import { Tabs } from '@zendeskgarden/react-tabs';
2121

2222
const Example = () => {
2323
const [selectedTab, setSelectedTab] = useState('tab-1');
@@ -28,15 +28,15 @@ const Example = () => {
2828
return (
2929
<ThemeProvider>
3030
<Tabs selectedItem={selectedTab} onChange={setSelectedTab}>
31-
<TabList>
32-
<Tab item="tab-1">Tab 1</Tab>
33-
<Tab item="tab-2">Tab 2</Tab>
34-
<Tab disabled>Disabled Tab</Tab>
35-
<Tab item="tab-3">Tab 3</Tab>
36-
</TabList>
37-
<TabPanel item="tab-1">Tab 1 content</TabPanel>
38-
<TabPanel item="tab-2">Tab 2 content</TabPanel>
39-
<TabPanel item="tab-3">Tab 3 content</TabPanel>
31+
<Tabs.TabList>
32+
<Tabs.Tab item="tab-1">Tab 1</Tabs.Tab>
33+
<Tabs.Tab item="tab-2">Tab 2</Tabs.Tab>
34+
<Tabs.Tab disabled>Disabled Tab</Tabs.Tab>
35+
<Tabs.Tab item="tab-3">Tab 3</Tabs.Tab>
36+
</Tabs.TabList>
37+
<Tabs.TabPanel item="tab-1">Tab 1 content</Tabs.TabPanel>
38+
<Tabs.TabPanel item="tab-2">Tab 2 content</Tabs.TabPanel>
39+
<Tabs.TabPanel item="tab-3">Tab 3 content</Tabs.TabPanel>
4040
</Tabs>
4141
</ThemeProvider>
4242
);

packages/tabs/demo/stories/TabsStory.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@
66
*/
77

88
import React from 'react';
9-
import { Story } from '@storybook/react';
10-
import { ITabsProps, Tab, TabList, TabPanel, Tabs } from '@zendeskgarden/react-tabs';
9+
import { StoryFn } from '@storybook/react';
10+
import { ITabsProps, Tabs } from '@zendeskgarden/react-tabs';
1111
import { ITab } from './types';
1212

1313
interface IArgs extends ITabsProps {
1414
tabs: ITab[];
1515
}
1616

17-
export const TabsStory: Story<IArgs> = ({ tabs, ...args }) => (
17+
export const TabsStory: StoryFn<IArgs> = ({ tabs, ...args }) => (
1818
<Tabs {...args}>
19-
<TabList>
19+
<Tabs.TabList>
2020
{tabs.map(tab => (
21-
<Tab key={tab.value} item={tab.value} disabled={tab.disabled}>
21+
<Tabs.Tab key={tab.value} item={tab.value} disabled={tab.disabled}>
2222
{tab.value}
23-
</Tab>
23+
</Tabs.Tab>
2424
))}
25-
</TabList>
25+
</Tabs.TabList>
2626
{tabs.map(tab => (
27-
<TabPanel key={tab.value} item={tab.value}>
27+
<Tabs.TabPanel key={tab.value} item={tab.value}>
2828
{tab.panel}
29-
</TabPanel>
29+
</Tabs.TabPanel>
3030
))}
3131
</Tabs>
3232
);

packages/tabs/demo/tabs.stories.mdx

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { Meta, ArgsTable, Canvas, Story, Markdown } from '@storybook/addon-docs';
22
import { useArgs } from '@storybook/client-api';
3-
import { Tabs, Tab, TabList, TabPanel } from '@zendeskgarden/react-tabs';
3+
import { Tabs } from '@zendeskgarden/react-tabs';
44
import { TabsStory } from './stories/TabsStory';
55
import { TABS } from './stories/data';
66
import README from '../README.md';
77

88
<Meta
99
title="Packages/Tabs/Tabs"
1010
component={Tabs}
11-
subcomponents={{ Tab, TabList, TabPanel }}
11+
subcomponents={{
12+
'Tabs.Tab': Tabs.Tab,
13+
'Tabs.TabList': Tabs.TabList,
14+
'Tabs.TabPanel': Tabs.TabPanel
15+
}}
1216
args={{ tabs: TABS }}
1317
argTypes={{ tabs: { table: { category: 'Story' } } }}
1418
parameters={{

packages/tabs/src/elements/Tabs.spec.tsx

+32-32
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,30 @@ import userEvent from '@testing-library/user-event';
1010
import { render as baseRender } from '@testing-library/react';
1111
import { render } from 'garden-test-utils';
1212

13-
import { Tabs, ITabsProps, TabList, TabPanel, Tab, ITabProps } from '../';
13+
import { Tabs, ITabsProps, ITabProps } from '../';
1414

1515
describe('Tabs', () => {
1616
const user = userEvent.setup();
1717

1818
/* Validates `Tab` component extension works as expected with `toTabs` */
19-
const TestTab = (props: ITabProps) => <Tab {...props} />;
19+
const TestTab = (props: ITabProps) => <Tabs.Tab {...props} />;
2020

2121
const BasicExample = (props: ITabsProps) => (
2222
<Tabs data-test-id="container" {...props}>
23-
<TabList>
24-
<Tab item="tab-1" data-test-id="tab">
23+
<Tabs.TabList>
24+
<Tabs.Tab item="tab-1" data-test-id="tab">
2525
Tab 1
26-
</Tab>
26+
</Tabs.Tab>
2727
<TestTab item="tab-2" data-test-id="tab">
2828
Tab 2
2929
</TestTab>
30-
</TabList>
31-
<TabPanel item="tab-1" data-test-id="panel">
30+
</Tabs.TabList>
31+
<Tabs.TabPanel item="tab-1" data-test-id="panel">
3232
Tab 1 content
33-
</TabPanel>
34-
<TabPanel item="tab-2" data-test-id="panel">
33+
</Tabs.TabPanel>
34+
<Tabs.TabPanel item="tab-2" data-test-id="panel">
3535
Tab 2 content
36-
</TabPanel>
36+
</Tabs.TabPanel>
3737
</Tabs>
3838
);
3939

@@ -62,10 +62,10 @@ describe('Tabs', () => {
6262
const ref = React.createRef<HTMLDivElement>();
6363
const { container } = render(
6464
<Tabs ref={ref}>
65-
<TabList>
66-
<Tab item="tab-1">Tab 1</Tab>
67-
</TabList>
68-
<TabPanel item="tab-1">Tab 1 content</TabPanel>
65+
<Tabs.TabList>
66+
<Tabs.Tab item="tab-1">Tab 1</Tabs.Tab>
67+
</Tabs.TabList>
68+
<Tabs.TabPanel item="tab-1">Tab 1 content</Tabs.TabPanel>
6969
</Tabs>
7070
);
7171

@@ -91,15 +91,15 @@ describe('Tabs', () => {
9191
it('applies disabled styling if provided', () => {
9292
const { getAllByTestId } = render(
9393
<Tabs>
94-
<TabList>
95-
<Tab data-test-id="tab" item="tab-1">
94+
<Tabs.TabList>
95+
<Tabs.Tab data-test-id="tab" item="tab-1">
9696
Tab 1
97-
</Tab>
98-
<Tab data-test-id="tab" disabled>
97+
</Tabs.Tab>
98+
<Tabs.Tab data-test-id="tab" disabled>
9999
Disabled Tab
100-
</Tab>
101-
</TabList>
102-
<TabPanel item="tab-1">Tab 1 content</TabPanel>
100+
</Tabs.Tab>
101+
</Tabs.TabList>
102+
<Tabs.TabPanel item="tab-1">Tab 1 content</Tabs.TabPanel>
103103
</Tabs>
104104
);
105105

@@ -109,12 +109,12 @@ describe('Tabs', () => {
109109
it('applies custom props if provided', () => {
110110
const { getByTestId } = render(
111111
<Tabs>
112-
<TabList>
113-
<Tab item="custom" data-test-id="custom-tab">
112+
<Tabs.TabList>
113+
<Tabs.Tab item="custom" data-test-id="custom-tab">
114114
Custom Tab
115-
</Tab>
116-
</TabList>
117-
<TabPanel item="custom">Custom Tab content</TabPanel>
115+
</Tabs.Tab>
116+
</Tabs.TabList>
117+
<Tabs.TabPanel item="custom">Custom Tab content</Tabs.TabPanel>
118118
</Tabs>
119119
);
120120

@@ -128,15 +128,15 @@ describe('Tabs', () => {
128128
});
129129
});
130130

131-
describe('TabPanel', () => {
132-
it('does not throw if a item is provided to TabPanel', () => {
131+
describe('Tabs.TabPanel', () => {
132+
it('does not throw if a item is provided to Tabs.TabPanel', () => {
133133
expect(() => {
134134
render(
135135
<Tabs>
136-
<TabList>
137-
<Tab item="valid-panel">Panel</Tab>
138-
</TabList>
139-
<TabPanel item="valid-panel">Valid panel</TabPanel>
136+
<Tabs.TabList>
137+
<Tabs.Tab item="valid-panel">Panel</Tabs.Tab>
138+
</Tabs.TabList>
139+
<Tabs.TabPanel item="valid-panel">Valid panel</Tabs.TabPanel>
140140
</Tabs>
141141
);
142142
}).not.toThrow();

packages/tabs/src/elements/Tabs.tsx

+20-7
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import { ITabsProps } from '../types';
1515
import { toTabs } from '../utils/toTabs';
1616
import { TabsContext } from '../utils/useTabsContext';
1717
import { StyledTabs } from '../styled/StyledTabs';
18+
import { Tab } from './Tab';
19+
import { TabList } from './TabList';
20+
import { TabPanel } from './TabPanel';
1821

19-
/**
20-
* @extends HTMLAttributes<HTMLDivElement>
21-
*/
22-
export const Tabs = forwardRef<HTMLDivElement, ITabsProps>(
22+
export const TabsComponent = forwardRef<HTMLDivElement, ITabsProps>(
2323
(
2424
{ isVertical, children, onChange, selectedItem: controlledSelectedItem, ...otherProps },
2525
ref
@@ -55,14 +55,27 @@ export const Tabs = forwardRef<HTMLDivElement, ITabsProps>(
5555
}
5656
);
5757

58-
Tabs.propTypes = {
58+
TabsComponent.propTypes = {
5959
isVertical: PropTypes.bool,
6060
selectedItem: PropTypes.any,
6161
onChange: PropTypes.func
6262
};
6363

64-
Tabs.defaultProps = {
64+
TabsComponent.defaultProps = {
6565
isVertical: false
6666
};
6767

68-
Tabs.displayName = 'Tabs';
68+
TabsComponent.displayName = 'Tabs';
69+
70+
/**
71+
* @extends HTMLAttributes<HTMLDivElement>
72+
*/
73+
export const Tabs = TabsComponent as typeof TabsComponent & {
74+
Tab: typeof Tab;
75+
TabList: typeof TabList;
76+
TabPanel: typeof TabPanel;
77+
};
78+
79+
Tabs.Tab = Tab;
80+
Tabs.TabList = TabList;
81+
Tabs.TabPanel = TabPanel;

packages/tabs/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
* found at http://www.apache.org/licenses/LICENSE-2.0.
66
*/
77

8+
/** @deprecated use `Tabs.Tab` instead */
89
export { Tab } from './elements/Tab';
10+
/** @deprecated use `Tabs.TabList` instead */
911
export { TabList } from './elements/TabList';
12+
/** @deprecated use `Tabs.TabPanel` instead */
1013
export { TabPanel } from './elements/TabPanel';
14+
1115
export { Tabs } from './elements/Tabs';
1216

1317
export type { ITabProps, ITabPanelProps, ITabsProps } from './types';

packages/tooltips/demo/stories/TooltipStory.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
*/
77

88
import React from 'react';
9-
import { Story } from '@storybook/react';
9+
import { StoryFn } from '@storybook/react';
1010
import { PALETTE } from '@zendeskgarden/react-theming';
1111
import { Col, Grid, Row } from '@zendeskgarden/react-grid';
12-
import { ITooltipProps, Paragraph, Title, Tooltip } from '@zendeskgarden/react-tooltips';
12+
import { ITooltipProps, Tooltip } from '@zendeskgarden/react-tooltips';
1313
import { ITooltipContent } from './types';
1414

1515
interface IArgs extends Omit<ITooltipProps, 'content'> {
1616
content: ITooltipContent;
1717
}
1818

19-
export const TooltipStory: Story<IArgs> = ({ content, ...args }: IArgs) => (
19+
export const TooltipStory: StoryFn<IArgs> = ({ content, ...args }: IArgs) => (
2020
<Grid>
2121
<Row style={{ height: 'calc(100vh - 80px)' }}>
2222
<Col textAlign="center" alignSelf="center">
2323
<Tooltip
2424
{...args}
2525
content={
2626
<>
27-
<Title>{content.title}</Title>
28-
<Paragraph>{content.paragraph}</Paragraph>
27+
<Tooltip.Title>{content.title}</Tooltip.Title>
28+
<Tooltip.Paragraph>{content.paragraph}</Tooltip.Paragraph>
2929
</>
3030
}
3131
>

packages/tooltips/demo/tooltip.stories.mdx

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { TooltipStory } from './stories/TooltipStory';
44
import { TOOLTIP_CONTENT as CONTENT } from './stories/data';
55
import README from '../README.md';
66

7-
<Meta title="Packages/Tooltips/Tooltip" component={Tooltip} subcomponents={{ Paragraph, Title }} />
7+
<Meta
8+
title="Packages/Tooltips/Tooltip"
9+
component={Tooltip}
10+
subcomponents={{
11+
'Tooltip.Paragraph': Tooltip.Paragraph,
12+
'Tooltip.Title': Tooltip.Title
13+
}}
14+
/>
815

916
# API
1017

packages/tooltips/src/elements/Tooltip.spec.tsx

+24
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,28 @@ describe('Tooltip', () => {
165165
expect(tooltip).toHaveStyleRule('max-width', '140px');
166166
});
167167
});
168+
169+
describe('React node content', () => {
170+
it('renders with Title and Paragraph', async () => {
171+
const { getByTestId } = renderRtl(
172+
<BasicExample
173+
data-test-id="tooltip"
174+
content={
175+
<>
176+
<Tooltip.Title>Title</Tooltip.Title>
177+
<Tooltip.Paragraph>Paragraph</Tooltip.Paragraph>
178+
</>
179+
}
180+
/>
181+
);
182+
183+
await act(async () => {
184+
await user.tab();
185+
jest.runOnlyPendingTimers();
186+
});
187+
188+
expect(getByTestId('tooltip')).toHaveTextContent('Title');
189+
expect(getByTestId('tooltip')).toHaveTextContent('Paragraph');
190+
});
191+
});
168192
});

0 commit comments

Comments
 (0)