From e0d7da8f40d623d031d806099c0f53911184c683 Mon Sep 17 00:00:00 2001 From: Linn-Devoteam Date: Thu, 14 Aug 2025 09:31:18 +0200 Subject: [PATCH 1/6] WIP add tabs --- src/App.tsx | 61 +++++++++++++++--- src/components/common/organisms/Tabs/Tabs.tsx | 62 +++++++++++++++++++ src/components/common/organisms/Tabs/index.ts | 3 + .../common/organisms/Tabs/styled.ts | 45 ++++++++++++++ src/components/common/styled.ts | 14 +++++ src/core/theme.ts | 2 +- src/index.css | 1 + 7 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 src/components/common/organisms/Tabs/Tabs.tsx create mode 100644 src/components/common/organisms/Tabs/index.ts create mode 100644 src/components/common/organisms/Tabs/styled.ts create mode 100644 src/components/common/styled.ts diff --git a/src/App.tsx b/src/App.tsx index c3b203f..02c991d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,61 @@ -import { Typography } from './core/typography' +import Tabs from './components/common/organisms/Tabs' +import { Main } from './components/common/styled' +import { Typography as T } from './core/typography' function App() { return ( <> - - Welcome to React Vite Template - - - This is a simple template to get you started with React and Vite. - +
+ + Welcome to React Vite Template + + + This is a simple template to get you started with React and Vite. + +
+ +
+ + Overview content goes here. Describe the template, goals, etc. + + ), + }, + { + key: 'components', + label: 'Components', + content: ( + + Components content goes here. Buttons, badges, modals… + + ), + }, + { + key: 'hooks', + label: 'Hooks', + content: ( + + Hooks content goes here. useToggle, useLocalStorage, + useCounter… + + ), + }, + ]} + /> +
) } diff --git a/src/components/common/organisms/Tabs/Tabs.tsx b/src/components/common/organisms/Tabs/Tabs.tsx new file mode 100644 index 0000000..a171759 --- /dev/null +++ b/src/components/common/organisms/Tabs/Tabs.tsx @@ -0,0 +1,62 @@ +import { useEffect, useMemo, useState } from 'react' +import * as S from './styled' + +type TabItem = { key: string; label: string; content: React.ReactNode } +type Props = { items: TabItem[]; defaultKey?: string } + +export default function Tabs({ items, defaultKey }: Props) { + const keys = useMemo(() => items.map((i) => i.key), [items]) + const [active, setActive] = useState(defaultKey ?? keys[0]) + + useEffect(() => { + if (!keys.includes(active)) setActive(keys[0]) + }, [keys, active]) + + const onKeyDown = (e: React.KeyboardEvent) => { + const i = keys.indexOf(active) + if (e.key === 'ArrowRight') setActive(keys[(i + 1) % keys.length]) + if (e.key === 'ArrowLeft') + setActive(keys[(i - 1 + keys.length) % keys.length]) + if (e.key === 'Home') setActive(keys[0]) + if (e.key === 'End') setActive(keys[keys.length - 1]) + } + + return ( + <> + + {items.map((t) => { + const isActive = t.key === active + return ( + setActive(t.key)} + > + {t.label} + + ) + })} + + + {items.map((t) => { + const isActive = t.key === active + return ( + + ) + })} + + ) +} diff --git a/src/components/common/organisms/Tabs/index.ts b/src/components/common/organisms/Tabs/index.ts new file mode 100644 index 0000000..d094ea3 --- /dev/null +++ b/src/components/common/organisms/Tabs/index.ts @@ -0,0 +1,3 @@ +import Tabs from './Tabs' + +export default Tabs diff --git a/src/components/common/organisms/Tabs/styled.ts b/src/components/common/organisms/Tabs/styled.ts new file mode 100644 index 0000000..9f9ba47 --- /dev/null +++ b/src/components/common/organisms/Tabs/styled.ts @@ -0,0 +1,45 @@ +import styled from '@emotion/styled' +import tokens from '../../../../core/tokens' + +const DIVIDER_W = `${tokens.size.BASELINE / 8}px` // ~1px +const INDICATOR_H = '2px' + +export const Bar = styled.nav` + display: flex; + align-items: flex-end; /* tabs sit on the divider */ + gap: ${tokens.gap.LARGE}px; + margin: ${tokens.spacing.BASELINE * 2}px 0 ${tokens.spacing.BASELINE}px; + border-bottom: ${DIVIDER_W} solid ${({ theme }) => theme.colors.scrollBar}; +` + +export const TabBtn = styled.button<{ active: boolean }>` + position: relative; + background: transparent; + border: none; + cursor: pointer; + color: ${(p) => + p.active ? p.theme.colors.primary : p.theme.colors.text.primary}; + font-size: ${tokens.text.fontSize.BASELINE}px; + font-weight: ${({ active }) => + active ? tokens.text.fontWeight.semiBold : tokens.text.fontWeight.normal}; + + padding: ${tokens.spacing.BASELINE}px ${tokens.spacing.BASELINE * 2}px; + padding-bottom: ${tokens.spacing.BASELINE * 1.5}px; + + &::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: calc(-1 * ${DIVIDER_W}); + height: ${INDICATOR_H}; + background: ${({ theme }) => theme.colors.primary}; + transform: ${({ active }) => (active ? 'scaleX(1)' : 'scaleX(0)')}; + transform-origin: left; + transition: transform 160ms ease; + } +` + +export const Panel = styled.section` + padding-top: ${tokens.spacing.BASELINE * 2}px; +` diff --git a/src/components/common/styled.ts b/src/components/common/styled.ts new file mode 100644 index 0000000..d02fab4 --- /dev/null +++ b/src/components/common/styled.ts @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' +import tokens from '../../core/tokens' + +export const Main = styled.nav` + padding: ${tokens.padding.BASELINE * 2}px; +` + +export const DividerBar = styled.hr` + width: 100%; + margin: 0; + border: 0; + border-top: ${tokens.size.BASELINE / 8}px solid + ${({ theme }) => theme.colors.scrollBar}; +` diff --git a/src/core/theme.ts b/src/core/theme.ts index 9147816..553d7eb 100644 --- a/src/core/theme.ts +++ b/src/core/theme.ts @@ -13,7 +13,7 @@ const brandColors = { } const background = { - primaryGrey: '#f4f4f4', + primaryGrey: '#EFEFE9', secondaryGrey: '#F5F5F5', tertiaryGrey: '#CCCCCC', } diff --git a/src/index.css b/src/index.css index 8b89f1d..7d028ba 100644 --- a/src/index.css +++ b/src/index.css @@ -26,4 +26,5 @@ select { body { margin: 0; + padding: 0; } From a8549493decd1be2451241246f387c3df34571de Mon Sep 17 00:00:00 2001 From: Linn-Devoteam Date: Thu, 14 Aug 2025 10:44:07 +0200 Subject: [PATCH 2/6] Refactor App.tsx --- src/App.tsx | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 02c991d..cd46ba2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,22 +5,12 @@ import { Typography as T } from './core/typography' function App() { return ( <> -
- - Welcome to React Vite Template - - - This is a simple template to get you started with React and Vite. - -
+ + Welcome to React Vite Template + + + This is a simple template to get you started with React and Vite. +
Date: Thu, 14 Aug 2025 11:40:36 +0200 Subject: [PATCH 3/6] Move intro text (header) and tabs to LibraryPage and wire routing --- src/App.tsx | 58 +++---------------- src/components/common/organisms/Tabs/Tabs.tsx | 7 ++- src/components/common/organisms/Tabs/index.ts | 5 +- src/main.tsx | 14 +++-- src/pages/LiberyPage/index.tsx | 22 +++++++ src/pages/LiberyPage/tabs.config.tsx | 11 ++++ .../LiberyPage/tabs/Components/index.tsx | 5 ++ .../Components/previews/ButtonPreview.tsx | 0 src/pages/LiberyPage/tabs/Hooks/index.tsx | 9 +++ .../LiberyPage/tabs/Overview/IntroCard.tsx | 0 src/pages/LiberyPage/tabs/Overview/index.tsx | 9 +++ 11 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 src/pages/LiberyPage/index.tsx create mode 100644 src/pages/LiberyPage/tabs.config.tsx create mode 100644 src/pages/LiberyPage/tabs/Components/index.tsx create mode 100644 src/pages/LiberyPage/tabs/Components/previews/ButtonPreview.tsx create mode 100644 src/pages/LiberyPage/tabs/Hooks/index.tsx create mode 100644 src/pages/LiberyPage/tabs/Overview/IntroCard.tsx create mode 100644 src/pages/LiberyPage/tabs/Overview/index.tsx diff --git a/src/App.tsx b/src/App.tsx index cd46ba2..cfd773a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,53 +1,13 @@ -import Tabs from './components/common/organisms/Tabs' -import { Main } from './components/common/styled' -import { Typography as T } from './core/typography' +// src/App.tsx (optional) +import { BrowserRouter, Routes, Route } from 'react-router-dom' +import LibraryPage from './pages/LiberyPage' -function App() { +export default function App() { return ( - <> - - Welcome to React Vite Template - - - This is a simple template to get you started with React and Vite. - - -
- - Overview content goes here. Describe the template, goals, etc. - - ), - }, - { - key: 'components', - label: 'Components', - content: ( - - Components content goes here. Buttons, badges, modals… - - ), - }, - { - key: 'hooks', - label: 'Hooks', - content: ( - - Hooks content goes here. useToggle, useLocalStorage, - useCounter… - - ), - }, - ]} - /> -
- + + + } /> + + ) } - -export default App diff --git a/src/components/common/organisms/Tabs/Tabs.tsx b/src/components/common/organisms/Tabs/Tabs.tsx index a171759..2c78eb9 100644 --- a/src/components/common/organisms/Tabs/Tabs.tsx +++ b/src/components/common/organisms/Tabs/Tabs.tsx @@ -1,7 +1,12 @@ import { useEffect, useMemo, useState } from 'react' import * as S from './styled' -type TabItem = { key: string; label: string; content: React.ReactNode } +export type TabItem = { + key: string + label: string + content: React.ReactNode +} + type Props = { items: TabItem[]; defaultKey?: string } export default function Tabs({ items, defaultKey }: Props) { diff --git a/src/components/common/organisms/Tabs/index.ts b/src/components/common/organisms/Tabs/index.ts index d094ea3..7cb9e7e 100644 --- a/src/components/common/organisms/Tabs/index.ts +++ b/src/components/common/organisms/Tabs/index.ts @@ -1,3 +1,2 @@ -import Tabs from './Tabs' - -export default Tabs +export { default } from './Tabs' +export type { TabItem } from './Tabs' diff --git a/src/main.tsx b/src/main.tsx index 284468d..7924446 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,18 +1,20 @@ +// src/main.tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' -import { BrowserRouter, Route, Routes } from 'react-router' +import { BrowserRouter, Routes, Route } from 'react-router-dom' import { ThemeProvider } from '@emotion/react' -import theme from './core/theme.ts' +import theme from './core/theme' +import './index.css' + +import LibraryPage from './pages/LiberyPage' createRoot(document.getElementById('root')!).render( - //TODO: Add more routes that takes you to different template pages - } /> + {/* One route that renders the whole page */} + } /> diff --git a/src/pages/LiberyPage/index.tsx b/src/pages/LiberyPage/index.tsx new file mode 100644 index 0000000..c5dbe43 --- /dev/null +++ b/src/pages/LiberyPage/index.tsx @@ -0,0 +1,22 @@ +// src/pages/LibraryPage/index.tsx +import { Typography as T } from '../../core/typography' +import Tabs from '../../components/common/organisms/Tabs' +import { Main } from '../../components/common/styled' +import { LIBRARY_TABS } from './tabs.config' + +export default function LibraryPage() { + return ( + <> + + Welcome to React Vite Template + + + This is a simple template to get you started with React and Vite. + + +
+ +
+ + ) +} diff --git a/src/pages/LiberyPage/tabs.config.tsx b/src/pages/LiberyPage/tabs.config.tsx new file mode 100644 index 0000000..21ebfb6 --- /dev/null +++ b/src/pages/LiberyPage/tabs.config.tsx @@ -0,0 +1,11 @@ +// src/pages/LibraryPage/tabs.config.tsx +import type { TabItem } from '../../components/common/organisms/Tabs' +import OverviewTab from './tabs/Overview' +import ComponentsTab from './tabs/Components' +import HooksTab from './tabs/Hooks' + +export const LIBRARY_TABS: TabItem[] = [ + { key: 'overview', label: 'Overview', content: }, + { key: 'components', label: 'Components', content: }, + { key: 'hooks', label: 'Hooks', content: }, +] diff --git a/src/pages/LiberyPage/tabs/Components/index.tsx b/src/pages/LiberyPage/tabs/Components/index.tsx new file mode 100644 index 0000000..a8c9f7c --- /dev/null +++ b/src/pages/LiberyPage/tabs/Components/index.tsx @@ -0,0 +1,5 @@ +import { Typography as T } from '../../../../core/typography' + +export default function ComponentsTab() { + return Components content… Buttons, badges, modals… +} diff --git a/src/pages/LiberyPage/tabs/Components/previews/ButtonPreview.tsx b/src/pages/LiberyPage/tabs/Components/previews/ButtonPreview.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/LiberyPage/tabs/Hooks/index.tsx b/src/pages/LiberyPage/tabs/Hooks/index.tsx new file mode 100644 index 0000000..2abd84c --- /dev/null +++ b/src/pages/LiberyPage/tabs/Hooks/index.tsx @@ -0,0 +1,9 @@ +import { Typography as T } from '../../../../core/typography' + +export default function HooksTab() { + return ( + + Hooks content… useToggle, useLocalStorage, useCounter… + + ) +} diff --git a/src/pages/LiberyPage/tabs/Overview/IntroCard.tsx b/src/pages/LiberyPage/tabs/Overview/IntroCard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/LiberyPage/tabs/Overview/index.tsx b/src/pages/LiberyPage/tabs/Overview/index.tsx new file mode 100644 index 0000000..9f4e33c --- /dev/null +++ b/src/pages/LiberyPage/tabs/Overview/index.tsx @@ -0,0 +1,9 @@ +import { Typography as T } from '../../../../core/typography' + +export default function OverviewTab() { + return ( + + Overview content goes here. Describe the template, goals, etc. + + ) +} From 94b48c68a6d461c0b36ffd95940231d2d741bd27 Mon Sep 17 00:00:00 2001 From: Linn-Devoteam Date: Thu, 14 Aug 2025 11:42:46 +0200 Subject: [PATCH 4/6] Refactor --- src/App.tsx | 1 - src/main.tsx | 3 --- src/pages/LiberyPage/index.tsx | 1 - src/pages/LiberyPage/tabs.config.tsx | 1 - 4 files changed, 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index cfd773a..22701ea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -// src/App.tsx (optional) import { BrowserRouter, Routes, Route } from 'react-router-dom' import LibraryPage from './pages/LiberyPage' diff --git a/src/main.tsx b/src/main.tsx index 7924446..e8798a1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,11 +1,9 @@ -// src/main.tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { BrowserRouter, Routes, Route } from 'react-router-dom' import { ThemeProvider } from '@emotion/react' import theme from './core/theme' import './index.css' - import LibraryPage from './pages/LiberyPage' createRoot(document.getElementById('root')!).render( @@ -13,7 +11,6 @@ createRoot(document.getElementById('root')!).render( - {/* One route that renders the whole page */} } /> diff --git a/src/pages/LiberyPage/index.tsx b/src/pages/LiberyPage/index.tsx index c5dbe43..ff82f86 100644 --- a/src/pages/LiberyPage/index.tsx +++ b/src/pages/LiberyPage/index.tsx @@ -1,4 +1,3 @@ -// src/pages/LibraryPage/index.tsx import { Typography as T } from '../../core/typography' import Tabs from '../../components/common/organisms/Tabs' import { Main } from '../../components/common/styled' diff --git a/src/pages/LiberyPage/tabs.config.tsx b/src/pages/LiberyPage/tabs.config.tsx index 21ebfb6..539bb11 100644 --- a/src/pages/LiberyPage/tabs.config.tsx +++ b/src/pages/LiberyPage/tabs.config.tsx @@ -1,4 +1,3 @@ -// src/pages/LibraryPage/tabs.config.tsx import type { TabItem } from '../../components/common/organisms/Tabs' import OverviewTab from './tabs/Overview' import ComponentsTab from './tabs/Components' From 892d7f21af5d580109efc216441d21a401ed2fd0 Mon Sep 17 00:00:00 2001 From: Linn-Devoteam Date: Thu, 21 Aug 2025 14:35:11 +0200 Subject: [PATCH 5/6] add focus-visible and aria-hidden for accessibility --- src/App.tsx | 4 +++- src/components/common/organisms/Tabs/Tabs.tsx | 1 + src/components/common/organisms/Tabs/styled.ts | 2 +- src/components/common/styled.ts | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 22701ea..84bd389 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' import LibraryPage from './pages/LiberyPage' -export default function App() { +function App() { return ( @@ -10,3 +10,5 @@ export default function App() { ) } + +export default App diff --git a/src/components/common/organisms/Tabs/Tabs.tsx b/src/components/common/organisms/Tabs/Tabs.tsx index 2c78eb9..2b973be 100644 --- a/src/components/common/organisms/Tabs/Tabs.tsx +++ b/src/components/common/organisms/Tabs/Tabs.tsx @@ -57,6 +57,7 @@ export default function Tabs({ items, defaultKey }: Props) { id={`${t.key}-panel`} aria-labelledby={`${t.key}-tab`} hidden={!isActive} + aria-hidden={!isActive} > {isActive && t.content} diff --git a/src/components/common/organisms/Tabs/styled.ts b/src/components/common/organisms/Tabs/styled.ts index 9f9ba47..0b50ff3 100644 --- a/src/components/common/organisms/Tabs/styled.ts +++ b/src/components/common/organisms/Tabs/styled.ts @@ -1,7 +1,7 @@ import styled from '@emotion/styled' import tokens from '../../../../core/tokens' -const DIVIDER_W = `${tokens.size.BASELINE / 8}px` // ~1px +const DIVIDER_W = `${tokens.size.BASELINE / 8}px` const INDICATOR_H = '2px' export const Bar = styled.nav` diff --git a/src/components/common/styled.ts b/src/components/common/styled.ts index d02fab4..55e0232 100644 --- a/src/components/common/styled.ts +++ b/src/components/common/styled.ts @@ -1,7 +1,7 @@ import styled from '@emotion/styled' import tokens from '../../core/tokens' -export const Main = styled.nav` +export const Main = styled.main` padding: ${tokens.padding.BASELINE * 2}px; ` From ce48a424a4df26cbff012fc57e4fdc29b51ca5a1 Mon Sep 17 00:00:00 2001 From: Linn-Devoteam Date: Wed, 27 Aug 2025 10:38:30 +0200 Subject: [PATCH 6/6] Refactor --- src/App.tsx | 2 +- src/main.tsx | 2 +- src/pages/LiberyPage/{index.tsx => LiberyPage.tsx} | 9 +++++---- src/pages/LiberyPage/tabs.config.tsx | 2 +- src/pages/LiberyPage/tabs/Overview/IntroCard.tsx | 0 5 files changed, 8 insertions(+), 7 deletions(-) rename src/pages/LiberyPage/{index.tsx => LiberyPage.tsx} (60%) delete mode 100644 src/pages/LiberyPage/tabs/Overview/IntroCard.tsx diff --git a/src/App.tsx b/src/App.tsx index 84bd389..a5a0e12 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' -import LibraryPage from './pages/LiberyPage' +import LibraryPage from './pages/LiberyPage/LiberyPage' function App() { return ( diff --git a/src/main.tsx b/src/main.tsx index e8798a1..b5147d0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,7 +4,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' import { ThemeProvider } from '@emotion/react' import theme from './core/theme' import './index.css' -import LibraryPage from './pages/LiberyPage' +import LibraryPage from './pages/LiberyPage/LiberyPage' createRoot(document.getElementById('root')!).render( diff --git a/src/pages/LiberyPage/index.tsx b/src/pages/LiberyPage/LiberyPage.tsx similarity index 60% rename from src/pages/LiberyPage/index.tsx rename to src/pages/LiberyPage/LiberyPage.tsx index ff82f86..a43bf0d 100644 --- a/src/pages/LiberyPage/index.tsx +++ b/src/pages/LiberyPage/LiberyPage.tsx @@ -1,20 +1,21 @@ import { Typography as T } from '../../core/typography' import Tabs from '../../components/common/organisms/Tabs' import { Main } from '../../components/common/styled' -import { LIBRARY_TABS } from './tabs.config' +import { LiberyTabs } from './tabs.config' +import tokens from '../../core/tokens' export default function LibraryPage() { return ( <> - + Welcome to React Vite Template - + This is a simple template to get you started with React and Vite.
- +
) diff --git a/src/pages/LiberyPage/tabs.config.tsx b/src/pages/LiberyPage/tabs.config.tsx index 539bb11..308524e 100644 --- a/src/pages/LiberyPage/tabs.config.tsx +++ b/src/pages/LiberyPage/tabs.config.tsx @@ -3,7 +3,7 @@ import OverviewTab from './tabs/Overview' import ComponentsTab from './tabs/Components' import HooksTab from './tabs/Hooks' -export const LIBRARY_TABS: TabItem[] = [ +export const LiberyTabs: TabItem[] = [ { key: 'overview', label: 'Overview', content: }, { key: 'components', label: 'Components', content: }, { key: 'hooks', label: 'Hooks', content: }, diff --git a/src/pages/LiberyPage/tabs/Overview/IntroCard.tsx b/src/pages/LiberyPage/tabs/Overview/IntroCard.tsx deleted file mode 100644 index e69de29..0000000