-
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.
SOV-580: vertical tabs component (#30)
* feat(vertical-tabs): desktop component for vertical tabs * feat(vertical-tabs): change indicator * feat(vertical-tabs): add test cases * chore: move Tabs to molecules and update layout id (#31) * chore(actions): automation for package releases SOV-863 (#33) * chore: configure changesets (#34) * chore(versions): configure changesets * chore(versions): add default changeset for ui package * chore: prevent duplicated tests * Version Packages (#36) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: review comments * chore: add changeset * fix: remove leadings * fix: add hover state * fix: modify storybook controls * fix: selectedIndex in the storybook Co-authored-by: soulBit <[email protected]> Co-authored-by: Pietro Maximoff <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
274b915
commit 32ac879
Showing
15 changed files
with
501 additions
and
5 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sovryn/ui': patch | ||
--- | ||
|
||
SOV-580: add vertical tabs component |
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 |
---|---|---|
@@ -1,4 +1,6 @@ | ||
build/ | ||
node_modules/ | ||
package-lock.json | ||
yarn.lock | ||
yarn.lock | ||
**/build | ||
**/dist |
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,6 @@ | ||
module.exports = { | ||
root: true, | ||
// This tells ESLint to load the config from the package `eslint-config-custom` | ||
extends: ['@sovryn/eslint-config-custom'], | ||
ignorePatterns: ['build/', 'dist/'], | ||
}; |
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
25 changes: 25 additions & 0 deletions
25
packages/ui/src/2_molecules/VerticalTabs/VerticalTabs.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,25 @@ | ||
.container { | ||
@apply w-full min-h-full flex flex-row justify-center items-start relative; | ||
} | ||
|
||
.aside { | ||
@apply h-full min-h-full bg-gray-80 p-[1.25rem] flex-shrink-0 flex-grow-0 w-[19.25rem] relative | ||
flex flex-col justify-between items-start; | ||
transition: clip-path 0.3s ease-in-out; | ||
} | ||
|
||
.header { | ||
@apply pt-[2.875rem] pb-[4.5rem] w-full relative; | ||
} | ||
|
||
.footer { | ||
@apply pt-[2.875rem] w-full relative; | ||
} | ||
|
||
.tabs { | ||
@apply w-full flex flex-col justify-center items-center gap-y-10; | ||
} | ||
|
||
.content { | ||
@apply w-full h-full p-[3.25rem] relative overflow-y-auto; | ||
} |
116 changes: 116 additions & 0 deletions
116
packages/ui/src/2_molecules/VerticalTabs/VerticalTabs.stories.tsx
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,116 @@ | ||
import { useArgs } from '@storybook/client-api'; | ||
import { Story } from '@storybook/react'; | ||
|
||
import { ComponentProps, useCallback, useReducer, useState } from 'react'; | ||
|
||
import { Button, Heading } from '../../1_atoms'; | ||
import { Dialog } from '../Dialog/Dialog'; | ||
import { DialogSize } from '../Dialog/Dialog.types'; | ||
import { VerticalTabs } from './VerticalTabs'; | ||
|
||
const EXCLUDED_CONTROLS = ['header', 'footer', 'onChange']; | ||
|
||
export default { | ||
title: 'Molecule/VerticalTabs', | ||
component: VerticalTabs, | ||
parameters: { | ||
layout: 'fullscreen', | ||
controls: { | ||
exclude: EXCLUDED_CONTROLS, | ||
}, | ||
}, | ||
}; | ||
|
||
const Template: Story<ComponentProps<typeof VerticalTabs>> = args => { | ||
const [, updateArgs] = useArgs(); | ||
const handleOnChange = useCallback( | ||
(index: number) => updateArgs({ selectedIndex: index }), | ||
[updateArgs], | ||
); | ||
|
||
return <VerticalTabs {...args} onChange={handleOnChange} />; | ||
}; | ||
|
||
const DialogTemplate: Story<ComponentProps<typeof VerticalTabs>> = args => { | ||
const [selectedIndex, setSelectedIndex] = useState(0); | ||
const [isDialogOpen, toggle] = useReducer(a => !a, false); | ||
return ( | ||
<> | ||
<Button onClick={toggle} text="Open Dialog" /> | ||
<Dialog isOpen={isDialogOpen} width={DialogSize.xl2}> | ||
<VerticalTabs | ||
{...args} | ||
selectedIndex={selectedIndex} | ||
onChange={setSelectedIndex} | ||
footer={() => ( | ||
<button onClick={toggle} className="text-primary-20 text-xs"> | ||
Close | ||
</button> | ||
)} | ||
/> | ||
</Dialog> | ||
</> | ||
); | ||
}; | ||
|
||
export const Basic = Template.bind({}); | ||
Basic.args = { | ||
items: [ | ||
{ label: 'Tab 1', content: 'Tab 1 Content' }, | ||
{ label: 'Tab 2', content: 'Tab 2 Content' }, | ||
{ label: 'Tab 3', content: 'Tab 3 Content' }, | ||
{ | ||
label: 'Tab4 ', | ||
infoText: 'Example with long content', | ||
content: ( | ||
<div> | ||
<Heading>Long List</Heading> | ||
<ol> | ||
{new Array(100).fill('Row').map((item, index) => ( | ||
<li key={index}>{item}</li> | ||
))} | ||
</ol> | ||
</div> | ||
), | ||
}, | ||
], | ||
selectedIndex: 0, | ||
header: props => ( | ||
<Heading>Selected: {props.items[props.selectedIndex].label}</Heading> | ||
), | ||
footer: () => <div>Footer</div>, | ||
className: '', | ||
tabsClassName: '', | ||
contentClassName: '', | ||
}; | ||
|
||
export const InDialog = DialogTemplate.bind({}); | ||
InDialog.args = { | ||
items: [ | ||
{ | ||
label: 'Hardware Wallet', | ||
infoText: 'Select the hardware wallet you want to connect', | ||
content: 'Content of HW tab', | ||
}, | ||
{ | ||
label: 'Browser Wallet', | ||
infoText: 'Select the web3 wallet you want to connect', | ||
content: 'Tab 2 Content', | ||
}, | ||
{ | ||
label: "Don't have a wallet", | ||
infoText: 'Read the following instructions', | ||
content: 'Tab 3 Content', | ||
}, | ||
], | ||
header: () => <Heading>Connect Wallet</Heading>, | ||
tabsClassName: 'rounded-l-lg', | ||
className: 'rounded-lg', | ||
}; | ||
|
||
InDialog.parameters = { | ||
layout: 'centered', | ||
controls: { | ||
exclude: [...EXCLUDED_CONTROLS, 'selectedIndex'], | ||
}, | ||
}; |
160 changes: 160 additions & 0 deletions
160
packages/ui/src/2_molecules/VerticalTabs/VerticalTabs.test.tsx
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,160 @@ | ||
import { render } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
import React, { useState } from 'react'; | ||
|
||
import { VerticalTabs } from './VerticalTabs'; | ||
import { VerticalTabsItem } from './VerticalTabs.types'; | ||
|
||
const INITIAL_TAB = 2; | ||
const DISABLED_TAB = 1; | ||
const TAB_ITEMS: VerticalTabsItem[] = [ | ||
{ | ||
label: 'Tab 1', | ||
infoText: 'Info text 1', | ||
content: 'Content 1', | ||
}, | ||
{ | ||
label: 'Tab 2', | ||
disabled: true, | ||
content: 'Content 2', | ||
}, | ||
{ | ||
label: 'Tab 3', | ||
content: 'Content 3', | ||
}, | ||
]; | ||
|
||
const TestComponent = () => { | ||
const [index, setIndex] = useState(INITIAL_TAB); | ||
return ( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={index} | ||
onChange={setIndex} | ||
header={() => <h1>This is header</h1>} | ||
footer={() => <p>This is footer</p>} | ||
/> | ||
); | ||
}; | ||
|
||
describe('VerticalTabs', () => { | ||
it('renders all of the tabs', () => { | ||
const { getByText } = render(<TestComponent />); | ||
|
||
TAB_ITEMS.forEach(item => { | ||
expect(getByText(item.label as string)).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('renders initial content', () => { | ||
const { getByText } = render(<TestComponent />); | ||
expect( | ||
getByText(TAB_ITEMS[INITIAL_TAB].content as string), | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders header', () => { | ||
const { getByText } = render(<TestComponent />); | ||
expect(getByText('This is header')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders footer', () => { | ||
const { getByText } = render(<TestComponent />); | ||
expect(getByText('This is footer')).toBeInTheDocument(); | ||
}); | ||
|
||
it('does not render non selected tab content', () => { | ||
const { getByText } = render(<TestComponent />); | ||
expect(() => getByText(TAB_ITEMS[0].content as string)).toThrow(); | ||
}); | ||
|
||
it('switches tab content when clicked', () => { | ||
const { getByText } = render(<TestComponent />); | ||
|
||
userEvent.click(getByText(TAB_ITEMS[0].label as string)); | ||
expect(getByText(TAB_ITEMS[0].content as string)).toBeInTheDocument(); | ||
}); | ||
|
||
it('does not switch to content of disabled tab', () => { | ||
const { getByText } = render(<TestComponent />); | ||
|
||
userEvent.click(getByText(TAB_ITEMS[DISABLED_TAB].label as string)); | ||
expect( | ||
getByText(TAB_ITEMS[INITIAL_TAB].content as string), | ||
).toBeInTheDocument(); | ||
expect(() => | ||
getByText(TAB_ITEMS[DISABLED_TAB].content as string), | ||
).toThrow(); | ||
}); | ||
|
||
it('triggers onChange callback when tab item is clicked', () => { | ||
const mockFunction = jest.fn(); | ||
|
||
const { getByText } = render( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={0} | ||
onChange={mockFunction} | ||
/>, | ||
); | ||
|
||
userEvent.click(getByText(TAB_ITEMS[0].label as string)); | ||
expect(mockFunction).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('does not trigger onChange callback when disabled tab item is clicked', () => { | ||
const mockFunction = jest.fn(); | ||
|
||
const { getByText } = render( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={0} | ||
onChange={mockFunction} | ||
/>, | ||
); | ||
|
||
userEvent.click(getByText(TAB_ITEMS[DISABLED_TAB].label as string)); | ||
expect(mockFunction).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('adds className to vertical tabs root wrapper', () => { | ||
const { container } = render( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={0} | ||
className="test-class" | ||
/>, | ||
); | ||
// adding "container" to make sure correct element is found | ||
expect(container.firstChild).toHaveClass('container test-class'); | ||
}); | ||
|
||
it('adds className to tab list wrapper', () => { | ||
const { container } = render( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={0} | ||
tabsClassName="test-class" | ||
/>, | ||
); | ||
|
||
// adding "aside" to make sure correct element is found | ||
expect(container.firstChild?.childNodes[0]).toHaveClass('aside test-class'); | ||
}); | ||
|
||
it('adds className to tab content wrapper', () => { | ||
const { container } = render( | ||
<VerticalTabs | ||
items={TAB_ITEMS} | ||
selectedIndex={0} | ||
contentClassName="test-class" | ||
/>, | ||
); | ||
|
||
// adding "content" to make sure correct element is found | ||
expect(container.firstChild?.childNodes[1]).toHaveClass( | ||
'content test-class', | ||
); | ||
}); | ||
}); |
Oops, something went wrong.