Skip to content

Commit d01cc1c

Browse files
feat(muk): export Tab component
2 parents 89b3a1f + c8428cb commit d01cc1c

26 files changed

+1548
-210
lines changed

packages/manager-ui-kit/src/components/base-layout/__tests__/BaseLayout.snapshot.test.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ import { describe, expect, it, vi } from 'vitest';
22

33
import { renderBaseLayout } from '@/commons/tests-utils/Render.utils';
44
import { changelogLinks, guideMenuItems } from '@/commons/tests-utils/StaticData.constants';
5-
import { Breadcrumb, ChangelogMenu, GuideMenu, Notifications, TabsComponent } from '@/components';
5+
import {
6+
Breadcrumb,
7+
ChangelogMenu,
8+
GuideMenu,
9+
Notifications,
10+
Tab,
11+
TabContent,
12+
TabList,
13+
Tabs,
14+
} from '@/components';
615

716
vi.mock('react-router-dom', async () => ({
817
...(await vi.importActual('react-router-dom')),
@@ -24,7 +33,18 @@ vi.mock('@ovh-ux/manager-react-shell-client', async (importActual) => ({
2433
const guideMenu = <GuideMenu items={guideMenuItems} />;
2534
const changelogMenu = <ChangelogMenu links={changelogLinks} />;
2635
const breadcrumb = <Breadcrumb appName="Test-App" rootLabel="Test App" />;
27-
const tabs = <TabsComponent items={['tab1', 'tab2']} />;
36+
const tabs = (
37+
<Tabs>
38+
<TabList>
39+
<Tab value="tab1">Tab 1</Tab>
40+
<Tab value="tab2">Tab 2</Tab>
41+
<Tab value="tab3">Tab 3</Tab>
42+
</TabList>
43+
<TabContent value="tab1">Tab 1 Content</TabContent>
44+
<TabContent value="tab2">Tab 2 Content</TabContent>
45+
<TabContent value="tab3">Tab 3 Content</TabContent>
46+
</Tabs>
47+
);
2848
const message = <Notifications />;
2949

3050
describe('BaseLayout - Snapshot Tests', () => {

packages/manager-ui-kit/src/components/base-layout/__tests__/__snapshots__/BaseLayout.snapshot.test.tsx.snap

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -108,80 +108,139 @@ exports[`BaseLayout - Snapshot Tests > renders complete layout with all props >
108108
<div
109109
class="mb-6"
110110
>
111-
<div>
111+
<div
112+
data-ods="tabs"
113+
data-orientation="horizontal"
114+
data-part="root"
115+
data-scope="tabs"
116+
dir="ltr"
117+
id="tabs::r3:"
118+
>
112119
<div
113-
data-ods="tabs"
120+
aria-orientation="horizontal"
121+
class="_tab-list_wv0v9_2"
122+
data-ods="tab-list"
114123
data-orientation="horizontal"
115-
data-part="root"
124+
data-part="list"
116125
data-scope="tabs"
117126
dir="ltr"
118-
id="tabs::r3:"
127+
id="tabs::r3::list"
128+
role="tablist"
119129
>
120130
<div
121-
aria-orientation="horizontal"
122-
class="_tab-list_wv0v9_2"
123-
data-ods="tab-list"
124-
data-orientation="horizontal"
125-
data-part="list"
126-
data-scope="tabs"
127-
dir="ltr"
128-
id="tabs::r3::list"
129-
role="tablist"
131+
class="_tab-list__container_wv0v9_40"
132+
tabindex="-1"
130133
>
131134
<div
132-
class="_tab-list__container_wv0v9_40"
133-
tabindex="-1"
135+
class="_tab-list__container__tabs_wv0v9_60"
134136
>
135-
<div
136-
class="_tab-list__container__tabs_wv0v9_60"
137+
<button
138+
aria-selected="false"
139+
class="_tab_bhw12_2"
140+
data-ods="tab"
141+
data-orientation="horizontal"
142+
data-ownedby="tabs::r3::list"
143+
data-part="trigger"
144+
data-scope="tabs"
145+
data-ssr=""
146+
data-value="tab1"
147+
dir="ltr"
148+
id="tabs::r3::trigger-tab1"
149+
role="tab"
150+
tabindex="-1"
151+
type="button"
152+
>
153+
Tab 1
154+
</button>
155+
<button
156+
aria-selected="false"
157+
class="_tab_bhw12_2"
158+
data-ods="tab"
159+
data-orientation="horizontal"
160+
data-ownedby="tabs::r3::list"
161+
data-part="trigger"
162+
data-scope="tabs"
163+
data-ssr=""
164+
data-value="tab2"
165+
dir="ltr"
166+
id="tabs::r3::trigger-tab2"
167+
role="tab"
168+
tabindex="-1"
169+
type="button"
137170
>
138-
<button
139-
aria-controls="tabs::r3::content-tab1"
140-
aria-selected="true"
141-
class="_tab_bhw12_2"
142-
data-focus=""
143-
data-ods="tab"
144-
data-orientation="horizontal"
145-
data-ownedby="tabs::r3::list"
146-
data-part="trigger"
147-
data-scope="tabs"
148-
data-selected=""
149-
data-ssr=""
150-
data-value="tab1"
151-
dir="ltr"
152-
id="tabs::r3::trigger-tab1"
153-
role="tab"
154-
tabindex="0"
155-
type="button"
156-
>
157-
tab1
158-
</button>
159-
<button
160-
aria-selected="false"
161-
class="_tab_bhw12_2"
162-
data-ods="tab"
163-
data-orientation="horizontal"
164-
data-ownedby="tabs::r3::list"
165-
data-part="trigger"
166-
data-scope="tabs"
167-
data-ssr=""
168-
data-value="tab2"
169-
dir="ltr"
170-
id="tabs::r3::trigger-tab2"
171-
role="tab"
172-
tabindex="-1"
173-
type="button"
174-
>
175-
tab2
176-
</button>
177-
</div>
171+
Tab 2
172+
</button>
173+
<button
174+
aria-selected="false"
175+
class="_tab_bhw12_2"
176+
data-ods="tab"
177+
data-orientation="horizontal"
178+
data-ownedby="tabs::r3::list"
179+
data-part="trigger"
180+
data-scope="tabs"
181+
data-ssr=""
182+
data-value="tab3"
183+
dir="ltr"
184+
id="tabs::r3::trigger-tab3"
185+
role="tab"
186+
tabindex="-1"
187+
type="button"
188+
>
189+
Tab 3
190+
</button>
178191
</div>
179192
</div>
180193
</div>
181194
<div
182-
class="bg-[--ods-color-primary-050] border border-solid border-[--ods-color-primary-100] border-t-0"
195+
aria-labelledby="tabs::r3::trigger-tab1"
196+
class="_tab-content_1xgrg_2"
197+
data-ods="tab-content"
198+
data-orientation="horizontal"
199+
data-ownedby="tabs::r3::list"
200+
data-part="content"
201+
data-scope="tabs"
202+
data-state="closed"
203+
dir="ltr"
204+
hidden=""
205+
id="tabs::r3::content-tab1"
206+
role="tabpanel"
207+
tabindex="0"
208+
>
209+
Tab 1 Content
210+
</div>
211+
<div
212+
aria-labelledby="tabs::r3::trigger-tab2"
213+
class="_tab-content_1xgrg_2"
214+
data-ods="tab-content"
215+
data-orientation="horizontal"
216+
data-ownedby="tabs::r3::list"
217+
data-part="content"
218+
data-scope="tabs"
219+
data-state="closed"
220+
dir="ltr"
221+
hidden=""
222+
id="tabs::r3::content-tab2"
223+
role="tabpanel"
224+
tabindex="0"
225+
>
226+
Tab 2 Content
227+
</div>
228+
<div
229+
aria-labelledby="tabs::r3::trigger-tab3"
230+
class="_tab-content_1xgrg_2"
231+
data-ods="tab-content"
232+
data-orientation="horizontal"
233+
data-ownedby="tabs::r3::list"
234+
data-part="content"
235+
data-scope="tabs"
236+
data-state="closed"
237+
dir="ltr"
238+
hidden=""
239+
id="tabs::r3::content-tab3"
240+
role="tabpanel"
241+
tabindex="0"
183242
>
184-
tab1
243+
Tab 3 Content
185244
</div>
186245
</div>
187246
</div>

packages/manager-ui-kit/src/components/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ export { Step } from './step/Step.component';
8787
export { SwitchItem } from './switch/switch-item/SwitchItem.component';
8888
export { Switch } from './switch/Switch.component';
8989
export { Table } from './table/Table.component';
90-
export { TabsComponent } from './tabs/Tabs.component';
90+
export { TabContent } from './tabs/tab-content/TabContent.component';
91+
export { TabList } from './tabs/tab-list/TabList.component';
92+
export { Tab } from './tabs/tab/Tab.component';
93+
export { Tabs } from './tabs/Tabs.component';
9194
export { Tag } from './tag/Tag.component';
9295
export { TagsList } from './tags-list/TagsList.component';
9396
export { TagsTile } from './tags-tile/TagsTile.component';
@@ -190,6 +193,9 @@ export type { SpinnerProps } from './spinner/Spinner.props';
190193
export type { StepProps } from './step/Step.props';
191194
export type { SwitchProps } from './switch/Switch.props';
192195
export type { TableProps } from './table/Table.props';
196+
export type { TabContentProps } from './tabs/tab-content/TabContent.props';
197+
export type { TabListProps } from './tabs/tab-list/TabList.props';
198+
export type { TabProps } from './tabs/tab/Tab.props';
193199
export type { TabsProps } from './tabs/Tabs.props';
194200
export type { TagProps } from './tag/Tag.props';
195201
export type { TagsListProps } from './tags-list/TagsList.props';
Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,7 @@
1-
import { JSX, useState } from 'react';
1+
import { Tabs as OdsTabs } from '@ovhcloud/ods-react';
22

3-
import { Tab, TabList, Tabs, TabsValueChangeEvent } from '@ovhcloud/ods-react';
3+
import { TabsProps } from '@/components/tabs/Tabs.props';
44

5-
import { TabsProps } from './Tabs.props';
6-
7-
export function TabsComponent<Item>({
8-
items = [],
9-
titleElement = ({ item }) => <>{String(item)}</>,
10-
contentElement = ({ item }) => <>{String(item)}</>,
11-
className,
12-
onChange,
13-
}: TabsProps<Item>): JSX.Element {
14-
const [selectedTabItem, setSelectedTabItem] = useState<string>(String(items?.[0] ?? ''));
15-
16-
const TitleComponent = titleElement;
17-
const ContentComponent = contentElement;
18-
19-
return (
20-
<div className={className}>
21-
<Tabs
22-
onValueChange={(value: TabsValueChangeEvent) => {
23-
setSelectedTabItem(value?.value);
24-
onChange?.(value as Item);
25-
}}
26-
value={selectedTabItem}
27-
>
28-
<TabList>
29-
{items.map((item) => {
30-
const itemValue = String(item);
31-
return (
32-
<Tab key={itemValue} value={itemValue}>
33-
<TitleComponent item={item} isSelected={itemValue === selectedTabItem} />
34-
</Tab>
35-
);
36-
})}
37-
</TabList>
38-
</Tabs>
39-
<div className="bg-[--ods-color-primary-050] border border-solid border-[--ods-color-primary-100] border-t-0">
40-
<ContentComponent item={selectedTabItem as Item} />
41-
</div>
42-
</div>
43-
);
44-
}
45-
46-
export default TabsComponent;
5+
export const Tabs = ({ children, ...others }: TabsProps) => (
6+
<OdsTabs {...others}>{children}</OdsTabs>
7+
);
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { JSX } from 'react';
1+
import { PropsWithChildren } from 'react';
22

3-
export type TabsProps<Item> = {
4-
items?: Item[];
5-
titleElement?: ({ item, isSelected }: { item: Item; isSelected?: boolean }) => JSX.Element;
6-
contentElement?: ({ item }: { item: Item }) => JSX.Element;
7-
className?: string;
8-
onChange?: (item: Item) => void;
9-
};
3+
import { TabsProp as OdsTabsProps } from '@ovhcloud/ods-react';
4+
/**
5+
* External types (from contexts, utils, or shared ODS exports)
6+
*/
7+
import { type TabsValueChangeEvent } from '@ovhcloud/ods-react';
8+
9+
export type TabsProps = PropsWithChildren<OdsTabsProps> & {};
10+
11+
export type { TabsValueChangeEvent };
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { render } from '@testing-library/react';
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { Tabs } from '@/components/tabs/Tabs.component';
5+
import { TabContent } from '@/components/tabs/tab-content/TabContent.component';
6+
import { TabList } from '@/components/tabs/tab-list/TabList.component';
7+
import { Tab } from '@/components/tabs/tab/Tab.component';
8+
9+
describe('Tabs Snapshot tests', () => {
10+
it('renders the component with default props and children', () => {
11+
const { container } = render(
12+
<Tabs>
13+
<TabList>
14+
<Tab value="tab1">Tab 1</Tab>
15+
<Tab value="tab2">Tab 2</Tab>
16+
<Tab value="tab3">Tab 3</Tab>
17+
</TabList>
18+
<TabContent value="tab1">Tab 1 Content</TabContent>
19+
<TabContent value="tab2">Tab 2 Content</TabContent>
20+
<TabContent value="tab3">Tab 3 Content</TabContent>
21+
</Tabs>,
22+
);
23+
expect(container).toMatchSnapshot();
24+
});
25+
});

0 commit comments

Comments
 (0)