Skip to content

Commit

Permalink
feat: add tabPanel component
Browse files Browse the repository at this point in the history
  • Loading branch information
kostasdano committed Oct 10, 2024
1 parent f2ca768 commit adfbc2f
Show file tree
Hide file tree
Showing 14 changed files with 260 additions and 46 deletions.
6 changes: 6 additions & 0 deletions src/components/Tabs/Tabs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ Tabs organize content into multiple sections and allow users to navigate between

<Canvas of={TabsStories.SimpleTabs} />

## Tabs with content

In order to add content to your Tabs and render it conditionally based on the selected tab, you can use the TabPanel component. Otherwise you can just use the Tabs component without children and implement a custom panel functionality.

<Canvas of={TabsStories.TabsWithContent} />

## Orientation

Tabs can be horizontal or vertical. Use the `orientation` prop to toggle this feature.
Expand Down
94 changes: 79 additions & 15 deletions src/components/Tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import React from 'react';
import Tabs, { Tab } from '~/components/Tabs';
import Tabs, { TabPanel } from '~/components/Tabs';
import { TabKey } from './types';
import { getItems } from './constants';
import { getContent, getItems } from './constants';

export default {
title: 'Updated Components/Tabs/Tabs',
component: Tab,
component: Tabs,

argTypes: {
orientation: { type: 'radio', options: ['horizontal', 'vertical'] },
hasCounter: { type: 'boolean' },
},

args: {
orientation: 'horizontal',
},
};

export const SimpleTabs = {
render: () => {
const [timePeriod, setTimePeriod] = React.useState<TabKey>('geller');
const [selectedFriend, setSelectedFriend] = React.useState<TabKey>('geller');

return (
<Tabs selectedKey={timePeriod} onSelectionChange={setTimePeriod} items={getItems(false)} />
<Tabs
selectedKey={selectedFriend}
onSelectionChange={setSelectedFriend}
items={getItems(false)}
/>
);
},

Expand All @@ -29,19 +37,55 @@ export const SimpleTabs = {
},
};

export const TabsWithContent = {
render: () => {
const [selectedFriend, setSelectedFriend] = React.useState<TabKey>('geller');

const content = getContent('horizontal');

return (
<Tabs
selectedKey={selectedFriend}
onSelectionChange={setSelectedFriend}
items={getItems(false)}
>
{Object.keys(content).map((friend) => (
<TabPanel key={friend} tabId={friend}>
{content[friend]}
</TabPanel>
))}
</Tabs>
);
},

name: 'Tabs with content',

parameters: {
controls: { disable: true },
},
};

export const TabsOrientation = {
render: (args) => {
const { orientation } = args;

const [timePeriod, setTimePeriod] = React.useState<TabKey>('geller');
const [selectedFriend, setSelectedFriend] = React.useState<TabKey>('geller');

const content = getContent(orientation);

return (
<Tabs
orientation={orientation}
selectedKey={timePeriod}
onSelectionChange={setTimePeriod}
selectedKey={selectedFriend}
onSelectionChange={setSelectedFriend}
items={getItems(false)}
/>
>
{Object.keys(content).map((friend) => (
<TabPanel key={friend} tabId={friend}>
{content[friend]}
</TabPanel>
))}
</Tabs>
);
},

Expand All @@ -54,10 +98,22 @@ export const TabsOrientation = {

export const TabsWithCounter = {
render: () => {
const [timePeriod, setTimePeriod] = React.useState<TabKey>('geller');
const [selectedFriend, setSelectedFriend] = React.useState<TabKey>('geller');

const content = getContent('horizontal');

return (
<Tabs selectedKey={timePeriod} onSelectionChange={setTimePeriod} items={getItems(true)} />
<Tabs
selectedKey={selectedFriend}
onSelectionChange={setSelectedFriend}
items={getItems(true)}
>
{Object.keys(content).map((friend) => (
<TabPanel key={friend} tabId={friend}>
{content[friend]}
</TabPanel>
))}
</Tabs>
);
},

Expand All @@ -72,15 +128,23 @@ export const Playground = {
render: (args) => {
const { orientation, hasCounter } = args;

const [timePeriod, setTimePeriod] = React.useState<TabKey>('geller');
const [selectedFriend, setSelectedFriend] = React.useState<TabKey>('geller');

const content = getContent(orientation);

return (
<Tabs
orientation={orientation}
selectedKey={timePeriod}
onSelectionChange={setTimePeriod}
selectedKey={selectedFriend}
onSelectionChange={setSelectedFriend}
items={getItems(hasCounter)}
/>
>
{Object.keys(content).map((friend) => (
<TabPanel key={friend} tabId={friend}>
{content[friend]}
</TabPanel>
))}
</Tabs>
);
},

Expand Down
12 changes: 12 additions & 0 deletions src/components/Tabs/Tabs.style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Theme } from '@emotion/react';
import { css } from '@emotion/react';

import type { TabOrientation } from './types';

export const tagStyles =
(isActive = false) =>
(theme: Theme) =>
Expand All @@ -13,3 +15,13 @@ export const tagStyles =
transition: background 0.2s;
transition: color 0.2s;
`;

export const showcaseContent = (orientation: TabOrientation) => (theme: Theme) =>
css`
display: flex;
flex-direction: ${orientation === 'horizontal' ? 'row' : 'column'};
gap: ${theme.globals.spacing.get('7')};
padding: ${`${theme.globals.spacing.get('5')} ${
orientation === 'horizontal' ? 0 : theme.globals.spacing.get('7')
}`};
`;
2 changes: 2 additions & 0 deletions src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
onSelectionChange,
items,
dataTestPrefixId = 'ictinus',
children,
} = props;

return (
Expand Down Expand Up @@ -42,6 +43,7 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
);
})}
</TabList>
{children}
</TabsContainer>
);
});
Expand Down
54 changes: 54 additions & 0 deletions src/components/Tabs/components/TabPanel/TabPanel.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
import TabPanel from './TabPanel';

<Meta title="Updated Components/Tabs/Components/TabPanel" />

<SectionHeader
title={'TabPanel'}
sections={[
{ title: 'Overview', href: '#overview' },
{ title: 'Props', href: '#props' },
{ title: 'Usage', href: '#usage' },
]}
/>

## Overview

TabPanel component is used to conditionally render the content that corresponds to the selected tab.

## Props

<ArgTypes of={TabPanel} />

## Usage

Use TabPanels as children of (monolith) Tabs or under Tab components in a composition to toggle content when selecting tabs.

<Tip>The tabId should be the same as in the Tab component that the TabPanel corresponds to</Tip>

```js
const [selectedKey, setSelectedKey] = React.useState<TabKey>('tab_1');

return (
// <TabsContainer
// orientation="horizontal"
// selectedKey={selectedKey}
// onSelectionChange={setSelectedKey}
// >
// <TabList aria-label="My Tabs" sx={styleOverrides}>
// <Tab key="tab_1" tabId="tab_1">
// Tab 1
// </Tab>
// <Tab key="tab_2" tabId="tab_2">
// Tab 2
// </Tab>
// <Tab key="tab_3" tabId="tab_3">
// Tab 3
// </Tab>
<TabPanel tabId="tab_1">This is Tab 1 content</TabPanel>
<TabPanel tabId="tab_2">This is Tab 2 content</TabPanel>
<TabPanel tabId="tab_3">This is Tab 3 content</TabPanel>
// </TabList>
// </TabsContainer>

```
13 changes: 13 additions & 0 deletions src/components/Tabs/components/TabPanel/TabPanel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import TabPanel from '.';

export default {
title: 'Updated Components/Tabs/Components/TabPanel',
component: TabPanel,

parameters: {
storyshots: {
disable: true,
},
controls: { disable: true },
},
};
18 changes: 18 additions & 0 deletions src/components/Tabs/components/TabPanel/TabPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { TabPanel as ReactAriaTabPanel } from 'react-aria-components';

import type { TabPanelProps } from '../../types';

const TabPanel = React.forwardRef<HTMLDivElement, TabPanelProps>((props, ref) => {
const { children, tabId, ...rest } = props;

return (
<ReactAriaTabPanel id={tabId} {...rest} ref={ref}>
{children}
</ReactAriaTabPanel>
);
});

TabPanel.displayName = 'TabPanel';

export default TabPanel;
1 change: 1 addition & 0 deletions src/components/Tabs/components/TabPanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './TabPanel';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from '@emotion/react';

import type { TabOrientation } from '../../types';

export const tabsContainerStyles = (orientation: TabOrientation) => css`
width: fit-content;
display: flex;
flex-direction: ${orientation === 'horizontal' ? 'column' : 'row'};
`;
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import { Tabs as AriaTabs } from 'react-aria-components';

import { tabsContainerStyles } from './TabsContainer.style';
import type { TabsContainerProps } from '../../types';

const TabsContainer = React.forwardRef<HTMLDivElement, TabsContainerProps>((props, ref) => {
const { selectedKey, onSelectionChange, orientation, children } = props;
const { selectedKey, onSelectionChange, orientation = 'horizontal', children } = props;

return (
<AriaTabs
css={{ width: 'fit-content' }}
css={tabsContainerStyles(orientation)}
orientation={orientation}
selectedKey={selectedKey}
onSelectionChange={onSelectionChange}
Expand Down
1 change: 1 addition & 0 deletions src/components/Tabs/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as Tab } from './Tab';
export { default as TabList } from './TabList';
export { default as TabsContainer } from './TabsContainer';
export { default as TabPanel } from './TabPanel';
29 changes: 0 additions & 29 deletions src/components/Tabs/constants.ts

This file was deleted.

Loading

0 comments on commit adfbc2f

Please sign in to comment.