-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add Tabs component * chore: change tabs props name * resolve: PR issues * resole: PR issues * fix: tabs css format * fix: tabs divider * fix: update Tabs component
- Loading branch information
Showing
8 changed files
with
363 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.wrapper { | ||
@apply inline-flex flex-col; | ||
|
||
.tabs { | ||
@apply inline-flex rounded-t items-center flex-nowrap overflow-auto; | ||
|
||
&.primary { | ||
@apply border border-b-0 border-gray-50 bg-gray-90; | ||
} | ||
} | ||
|
||
.content { | ||
&.primary { | ||
@apply bg-gray-90 rounded-b text-gray-10 border-b border-x border-gray-50; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { Story } from '@storybook/react'; | ||
|
||
import React, { ComponentProps, useState } from 'react'; | ||
|
||
import { Tabs } from './Tabs'; | ||
import { TabSize, TabType } from './Tabs.types'; | ||
|
||
export default { | ||
title: 'Atoms/Tabs', | ||
component: Tabs, | ||
}; | ||
|
||
const Template: Story<ComponentProps<typeof Tabs>> = args => { | ||
const [index, setIndex] = useState(args.index); | ||
return <Tabs {...args} onChange={setIndex} index={index} />; | ||
}; | ||
|
||
export const Primary = Template.bind(); | ||
Primary.args = { | ||
items: [ | ||
{ | ||
label: 'Default', | ||
content: <div>Default</div>, | ||
activeClassName: 'border-t-primary-30', | ||
dataActionId: 'default', | ||
}, | ||
{ | ||
label: 'Buy', | ||
content: <div>Buy</div>, | ||
activeClassName: 'border-t-positive', | ||
dataActionId: 'buy', | ||
}, | ||
{ | ||
label: 'Sell', | ||
content: <div>Sell</div>, | ||
activeClassName: 'border-t-negative', | ||
dataActionId: 'sell', | ||
}, | ||
{ | ||
label: 'Disabled', | ||
content: <div>Disabled</div>, | ||
disabled: true, | ||
activeClassName: '', | ||
dataActionId: 'disabled', | ||
}, | ||
], | ||
index: 0, | ||
contentClassName: 'p-6', | ||
className: '', | ||
size: TabSize.normal, | ||
type: TabType.primary, | ||
}; | ||
|
||
export const Secondary = Template.bind(); | ||
Secondary.args = { | ||
items: [ | ||
{ | ||
label: 'Default', | ||
content: <div>Default</div>, | ||
activeClassName: 'text-primary-20', | ||
dataActionId: 'default', | ||
}, | ||
{ | ||
label: 'Buy', | ||
content: <div>Buy</div>, | ||
activeClassName: 'text-positive', | ||
dataActionId: 'buy', | ||
}, | ||
{ | ||
label: 'Sell', | ||
content: <div>Sell</div>, | ||
activeClassName: 'text-negative', | ||
dataActionId: 'sell', | ||
}, | ||
{ | ||
label: 'Disabled', | ||
content: <div>Disabled</div>, | ||
disabled: true, | ||
activeClassName: '', | ||
dataActionId: 'disabled', | ||
}, | ||
], | ||
index: 0, | ||
contentClassName: 'p-4', | ||
className: '', | ||
size: TabSize.normal, | ||
type: TabType.secondary, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { render } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
import React from 'react'; | ||
|
||
import { Tabs } from './Tabs'; | ||
|
||
const items = [ | ||
{ | ||
label: '0', | ||
content: 'content-0', | ||
}, | ||
{ | ||
label: '1', | ||
content: 'content-1', | ||
}, | ||
{ | ||
label: 'disabled', | ||
content: 'disabled-content', | ||
disabled: true, | ||
}, | ||
]; | ||
|
||
describe('Tabs', () => { | ||
it('renders tabs', () => { | ||
const { getByText } = render(<Tabs items={items} index={1} />); | ||
|
||
expect(getByText('0')).toBeInTheDocument(); | ||
expect(getByText('1')).toBeInTheDocument(); | ||
expect(getByText('content-1')).toBeInTheDocument(); | ||
}); | ||
|
||
it('switch between tabs', () => { | ||
const onChange = jest.fn(); | ||
const { getByText } = render( | ||
<Tabs items={items} index={1} onChange={onChange} />, | ||
); | ||
|
||
userEvent.click(getByText('0')); | ||
|
||
expect(onChange).toBeCalledWith(0); | ||
}); | ||
|
||
it('does not switch to a disabled tab', () => { | ||
const onChange = jest.fn(); | ||
const { getByText } = render( | ||
<Tabs items={items} index={1} onChange={onChange} />, | ||
); | ||
|
||
userEvent.click(getByText('disabled')); | ||
|
||
expect(onChange).not.toBeCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import React, { useMemo, useCallback } from 'react'; | ||
|
||
import classNames from 'classnames'; | ||
|
||
import styles from './Tabs.module.css'; | ||
import { TabSize, TabType } from './Tabs.types'; | ||
import { Tab } from './components/Tab/Tab'; | ||
|
||
interface ITabItem { | ||
label: React.ReactNode; | ||
content: React.ReactNode; | ||
disabled?: boolean; | ||
dataActionId?: string; | ||
activeClassName?: string; | ||
} | ||
|
||
type TabsProps = { | ||
className?: string; | ||
contentClassName?: string; | ||
index: number; | ||
items: ITabItem[]; | ||
onChange?: (index: number) => void; | ||
type?: TabType; | ||
size?: TabSize; | ||
}; | ||
|
||
export const Tabs: React.FC<TabsProps> = ({ | ||
items, | ||
index, | ||
onChange, | ||
className = '', | ||
contentClassName, | ||
type = TabType.primary, | ||
size = TabSize.normal, | ||
}) => { | ||
const selectTab = useCallback( | ||
(item: ITabItem, index: number) => { | ||
if (!item.disabled) { | ||
onChange?.(index); | ||
} | ||
}, | ||
[onChange], | ||
); | ||
|
||
const content = useMemo(() => items[index]?.content, [index, items]); | ||
|
||
return ( | ||
<div className={classNames(styles.wrapper, className)}> | ||
<div className={classNames(styles.tabs, styles[type])}> | ||
{items.map((item, i) => ( | ||
<Tab | ||
key={i} | ||
active={index === i} | ||
index={i} | ||
disabled={item.disabled} | ||
onClick={() => selectTab(item, i)} | ||
content={item.label} | ||
dataActionId={item.dataActionId} | ||
type={type} | ||
size={size} | ||
activeIndex={index} | ||
activeClassName={item.activeClassName} | ||
/> | ||
))} | ||
</div> | ||
<div | ||
className={classNames(styles.content, styles[type], contentClassName)} | ||
> | ||
{content} | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export enum TabSize { | ||
small = 'small', | ||
normal = 'normal', | ||
} | ||
|
||
export enum TabType { | ||
primary = 'primary', | ||
secondary = 'secondary', | ||
} |
64 changes: 64 additions & 0 deletions
64
packages/ui/src/1_atoms/Tabs/components/Tab/Tab.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
.button { | ||
@apply cursor-pointer inline-flex box-border items-center justify-center text-center rounded cursor-pointer transition-colors relative; | ||
|
||
&.primary { | ||
@apply border-t-2 border-t-transparent border-b text-base text-gray-10/50 rounded-b-none h-12; | ||
min-width: 10rem; | ||
|
||
&.normal { | ||
@apply font-medium; | ||
} | ||
|
||
&:not(:disabled):not(.active):hover { | ||
@apply text-gray-10; | ||
} | ||
|
||
&.active { | ||
@apply text-gray-10 bg-gray-90 border-x border-x-gray-50 pb-0.5 border-b-transparent; | ||
} | ||
|
||
&:last-child { | ||
@apply border-r-0; | ||
} | ||
|
||
&:first-child { | ||
@apply border-l-0; | ||
} | ||
|
||
&:not(.active) { | ||
@apply bg-gray-80 border-b-gray-50 border-r border-r-gray-50 rounded-none border-t-0; | ||
|
||
&:last-child, | ||
&.noRightBorder { | ||
@apply border-r-0; | ||
} | ||
} | ||
} | ||
|
||
&.secondary { | ||
&.normal { | ||
@apply font-semibold text-xs px-0.5 py-2 mx-0.5; | ||
min-width: 3.5rem; | ||
} | ||
|
||
&.small { | ||
@apply font-semibold text-tiny px-0.5 py-1 mx-0.5; | ||
min-width: 2.5rem; | ||
} | ||
|
||
&.active { | ||
@apply bg-gray-70; | ||
} | ||
|
||
&:not(.active) { | ||
@apply text-gray-10/75; | ||
&:not(:disabled):hover { | ||
@apply text-gray-10; | ||
} | ||
} | ||
} | ||
|
||
&:disabled { | ||
@apply cursor-not-allowed; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import React from 'react'; | ||
|
||
import classNames from 'classnames'; | ||
|
||
import { TabSize, TabType } from '../../Tabs.types'; | ||
import styles from './Tab.module.css'; | ||
|
||
type TabProps = { | ||
content: React.ReactNode; | ||
disabled?: boolean; | ||
active: boolean; | ||
onClick: () => void; | ||
className?: string; | ||
dataActionId?: string; | ||
type: TabType; | ||
size: TabSize; | ||
activeClassName?: string; | ||
index: number; | ||
activeIndex: number; | ||
}; | ||
|
||
export const Tab: React.FC<TabProps> = ({ | ||
content, | ||
onClick, | ||
disabled, | ||
className, | ||
dataActionId, | ||
type, | ||
size, | ||
active, | ||
activeClassName = '', | ||
activeIndex, | ||
index, | ||
}) => ( | ||
<button | ||
type="button" | ||
className={classNames( | ||
{ | ||
[styles.active]: active, | ||
[activeClassName]: active, | ||
[styles.noRightBorder]: activeIndex - 1 === index, | ||
}, | ||
className, | ||
styles.button, | ||
styles[size], | ||
styles[type], | ||
)} | ||
onClick={onClick} | ||
data-action-id={dataActionId} | ||
disabled={disabled} | ||
> | ||
{content} | ||
</button> | ||
); |