diff --git a/applications/launchpad_v2/src/components/Tabs/Tabs.test.tsx b/applications/launchpad_v2/src/components/Tabs/Tabs.test.tsx
new file mode 100644
index 00000000000..7583c74a983
--- /dev/null
+++ b/applications/launchpad_v2/src/components/Tabs/Tabs.test.tsx
@@ -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: First tab,
+ },
+ {
+ id: 'second-tab',
+ content: Second tab,
+ },
+]
+
+describe('Tabs', () => {
+ it('should render without crashing', async () => {
+ const selected = 'second-tab'
+ const onSelect = jest.fn()
+
+ await act(async () => {
+ render(
+
+
+ ,
+ )
+ })
+
+ const firstTabText = screen.queryAllByText('First tab')
+ expect(firstTabText.length).toBeGreaterThan(0)
+ })
+})
diff --git a/applications/launchpad_v2/src/components/Tabs/index.tsx b/applications/launchpad_v2/src/components/Tabs/index.tsx
index 8246f50227c..fb774a6ffd0 100644
--- a/applications/launchpad_v2/src/components/Tabs/index.tsx
+++ b/applications/launchpad_v2/src/components/Tabs/index.tsx
@@ -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 (
{tabs.map((tab, index) => (
(tabsRefs.current[index] = el)}
onClick={() => onSelect(tab.id)}
>
- {tab.content}
+
+ {tab.content}
+
+
+
+ {tab.content}
+
+
))}
+
)
}
diff --git a/applications/launchpad_v2/src/components/Tabs/styles.ts b/applications/launchpad_v2/src/components/Tabs/styles.ts
index d9814319933..b21193bddec 100644
--- a/applications/launchpad_v2/src/components/Tabs/styles.ts
+++ b/applications/launchpad_v2/src/components/Tabs/styles.ts
@@ -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;
+ }
`
diff --git a/applications/launchpad_v2/src/components/Tabs/types.ts b/applications/launchpad_v2/src/components/Tabs/types.ts
index 6573b93969d..419cd485586 100644
--- a/applications/launchpad_v2/src/components/Tabs/types.ts
+++ b/applications/launchpad_v2/src/components/Tabs/types.ts
@@ -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
}
diff --git a/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/components/DashboardTabs/index.tsx b/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/components/DashboardTabs/index.tsx
new file mode 100644
index 00000000000..8d1b0026128
--- /dev/null
+++ b/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/components/DashboardTabs/index.tsx
@@ -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: Mining,
+ },
+ {
+ id: 'BASE_NODE',
+ content: Base Node,
+ },
+ {
+ id: 'WALLET',
+ content: Wallet,
+ },
+ ])
+
+ // useEffect(() => {}, [])
+
+ const setPageTab = (tabId: string) => {
+ dispatch(setPage(tabId as ViewType))
+ }
+
+ return (
+
+ )
+}
+
+export default DashboardTabs
diff --git a/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/index.tsx b/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/index.tsx
index 50048a86df2..cdbc42cbfe6 100644
--- a/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/index.tsx
+++ b/applications/launchpad_v2/src/containers/Dashboard/DashboardContainer/index.tsx
@@ -1,4 +1,4 @@
-import { useDispatch, useSelector } from 'react-redux'
+import { useSelector } from 'react-redux'
import { DashboardContent, DashboardLayout } from './styles'
@@ -6,11 +6,9 @@ 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'
/**
@@ -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: Mining,
- },
- {
- id: 'BASE_NODE',
- content: Base Node,
- },
- {
- id: 'WALLET',
- content: Wallet,
- },
- ]
-
- const setPageTab = (tabId: string) => {
- dispatch(setPage(tabId as ViewType))
- }
-
const renderPage = () => {
switch (currentPage) {
case 'MINING':
@@ -60,11 +37,7 @@ const DashboardContainer = () => {
return (
-
+
{renderPage()}