Skip to content

Commit

Permalink
Improve Tabs component and its usage in DashboardContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszantas committed Apr 25, 2022
1 parent 21e3070 commit 2cc40a9
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 50 deletions.
34 changes: 34 additions & 0 deletions applications/launchpad_v2/src/components/Tabs/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { act, render, screen } from '@testing-library/react'
import { ThemeProvider } from 'styled-components'

import themes from '../../styles/themes'
import Tabs from './'

const tabs = [
{
id: 'first-tab',
content: <span>First tab</span>,
},
{
id: 'second-tab',
content: <span>Second tab</span>,
},
]

describe('Tabs', () => {
it('should render without crashing', async () => {
const selected = 'second-tab'
const onSelect = jest.fn()

await act(async () => {
render(
<ThemeProvider theme={themes.light}>
<Tabs tabs={tabs} selected={selected} onSelect={onSelect} />
</ThemeProvider>,
)
})

const firstTabText = screen.queryAllByText('First tab')
expect(firstTabText.length).toBeGreaterThan(0)
})
})
91 changes: 88 additions & 3 deletions applications/launchpad_v2/src/components/Tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,105 @@
import { TabsContainer, Tab, TabOptions } from './styles'
import { useEffect, useRef, useState } from 'react'
import { useSpring } from 'react-spring'

import Text from '../Text'

import {
TabsContainer,
Tab,
TabOptions,
TabContent,
TabSelectedBorder,
FontWeightCompensation,
} from './styles'
import { TabsProps } from './types'

/**
* Tabs component
*
* @TODO add details on how to compose the tab content to make the 'selected' styling work properly
*
* @param {TabProp[]} tabs - the list of tabs.
* @param {string} selected - the id of the selected tab. It has to match the `id` prop of the tab.
* @param {(val: string) => void} onSelect - on tab click.
*
* @typedef TabProp
* @param {string} id - unique identifier of the tab
* @param {ReactNode} content - the tab header content
*/
const Tabs = ({ tabs, selected, onSelect }: TabsProps) => {
const tabsRefs = useRef<(HTMLButtonElement | null)[]>([])
// Needs to re-render the component twice on the initial mount,
// because on first rendering, it adds tabs to the DOM, and on
// the second render, it can calculate correct values for bolded fonts.
// So the `initialized` is an integer incremented from 0 to 2
const [initialized, setInitialzed] = useState(0)

useEffect(() => {
tabsRefs.current = tabsRefs.current.slice(0, tabs.length)
}, [tabs])

useEffect(() => {
if (initialized < 2) {
setInitialzed(initialized + 1)
}
}, [initialized])

const selectedIndex = tabs.findIndex(t => t.id === selected)
let width = 0
let left = 0
let totalWidth = 0

if (selectedIndex > -1) {
if (
tabsRefs &&
tabsRefs.current &&
tabsRefs.current.length > selectedIndex
) {
tabsRefs.current.forEach((el, index) => {
if (el) {
if (index < selectedIndex) {
left = left + el.offsetWidth
} else if (index === selectedIndex) {
width = el.offsetWidth
}
totalWidth = totalWidth + el.offsetWidth
}
})
}
}

const activeBorder = useSpring({
to: { left: left, width: width },
config: { duration: 100 },
})

return (
<TabsContainer>
<TabOptions>
{tabs.map((tab, index) => (
<Tab
key={`tab-${index}`}
selected={selected === tab.id}
ref={el => (tabsRefs.current[index] = el)}
onClick={() => onSelect(tab.id)}
>
{tab.content}
<FontWeightCompensation>
<Text type='defaultHeavy'>{tab.content}</Text>
</FontWeightCompensation>
<TabContent selected={selected === tab.id}>
<Text
type={selected === tab.id ? 'defaultHeavy' : 'defaultMedium'}
>
{tab.content}
</Text>
</TabContent>
</Tab>
))}
</TabOptions>
<TabSelectedBorder
style={{
...activeBorder,
}}
/>
</TabsContainer>
)
}
Expand Down
48 changes: 35 additions & 13 deletions applications/launchpad_v2/src/components/Tabs/styles.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
import { animated } from 'react-spring'
import styled from 'styled-components'

export const TabsContainer = styled.div`
display: flex;
align-items: flex-start;
flex-direction: column;
position: relative;
`

export const Tab = styled.div<{ selected?: boolean }>`
export const TabOptions = styled.div`
display: flex;
padding: 12px;
border-bottom: ${({ selected }) =>
selected ? '4px solid #9330FF' : '4px solid #fff'};
align-items: center;
`

export const TabOptions = styled.div`
export const Tab = styled.button`
display: flex;
align-items: center;
padding: 8px 12px;
box-shadow: none;
border-width: 0px;
border-bottom: 4px solid transparent;
background: transparent;
box-sizing: border-box;
margin: 0px;
position: relative;
cursor: pointer;
`

export const TabsBorder = styled.div`
export const TabSelectedBorder = styled(animated.div)`
position: absolute;
height: 4px;
background: red;
width: 100%;
border-radius: 2px;
background: ${({ theme }) => theme.accent};
bottom: 0;
`

export const TabBorderSelection = styled.div`
height: 100%;
background: blue;
width: 50px;
export const FontWeightCompensation = styled.div`
visibility: hidden;
& > p {
margin: 0;
}
`

export const TabContent = styled.div<{ selected?: boolean }>`
position: absolute;
top: 12px;
left: 12px;
& > p {
margin: 0;
}
`
10 changes: 6 additions & 4 deletions applications/launchpad_v2/src/components/Tabs/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ReactNode } from 'react'

export interface TabProp {
id: string
content: ReactNode
}

export interface TabsProps {
tabs: {
id: string
content: ReactNode
}[]
tabs: TabProp[]
selected: string
onSelect: (id: string) => void
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useDispatch, useSelector } from 'react-redux'
import Tabs from '../../../../../components/Tabs'

import { setPage } from '../../../../../store/app'
import { ViewType } from '../../../../../store/app/types'
import { selectView } from '../../../../../store/app/selectors'
import { useEffect, useState } from 'react'

/**
* Renders Dasboard tabs
*/
const DashboardTabs = () => {
const dispatch = useDispatch()

const currentPage = useSelector(selectView)

const [tabs, setTabs] = useState([
{
id: 'MINING',
content: <span>Mining</span>,
},
{
id: 'BASE_NODE',
content: <span>Base Node</span>,
},
{
id: 'WALLET',
content: <span>Wallet</span>,
},
])

// useEffect(() => {}, [])

const setPageTab = (tabId: string) => {
dispatch(setPage(tabId as ViewType))
}

return (
<Tabs
tabs={tabs}
selected={currentPage || 'MINING'}
onSelect={setPageTab}
/>
)
}

export default DashboardTabs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { useDispatch, useSelector } from 'react-redux'
import { useSelector } from 'react-redux'

import { DashboardContent, DashboardLayout } from './styles'

import MiningContainer from '../../MiningContainer'
import BaseNodeContainer from '../../BaseNodeContainer'
import WalletContainer from '../../WalletContainer'

import DashboardTabs from './components/DashboardTabs'
import Footer from '../../../components/Footer'
import Tabs from '../../../components/Tabs'

import { setPage } from '../../../store/app'
import { ViewType } from '../../../store/app/types'
import { selectView } from '../../../store/app/selectors'

/**
Expand All @@ -21,29 +19,8 @@ import { selectView } from '../../../store/app/selectors'
* Dashboard view containing three main tabs: Mining, Wallet and BaseNode
*/
const DashboardContainer = () => {
const dispatch = useDispatch()

const currentPage = useSelector(selectView)

const pageTabs = [
{
id: 'MINING',
content: <span>Mining</span>,
},
{
id: 'BASE_NODE',
content: <span>Base Node</span>,
},
{
id: 'WALLET',
content: <span>Wallet</span>,
},
]

const setPageTab = (tabId: string) => {
dispatch(setPage(tabId as ViewType))
}

const renderPage = () => {
switch (currentPage) {
case 'MINING':
Expand All @@ -60,11 +37,7 @@ const DashboardContainer = () => {
return (
<DashboardLayout>
<DashboardContent>
<Tabs
tabs={pageTabs}
selected={currentPage || 'MINING'}
onSelect={setPageTab}
/>
<DashboardTabs />
{renderPage()}
</DashboardContent>

Expand Down

0 comments on commit 2cc40a9

Please sign in to comment.