diff --git a/src/App.tsx b/src/App.tsx index 2a17b2b..16f322b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,11 @@ -import Header from './components/common/Layout/Header' +import { Routes, Route } from 'react-router-dom' +import LibraryPage from './pages/LiberyPage/LiberyPage' function App() { return ( - <> -
- + + } /> + ) } diff --git a/src/components/common/Layout/Header/styled.ts b/src/components/common/Layout/Header/styled.ts index ec340a6..232bb57 100644 --- a/src/components/common/Layout/Header/styled.ts +++ b/src/components/common/Layout/Header/styled.ts @@ -10,7 +10,7 @@ export const Header = styled.header` display: flex; justify-content: space-between; align-items: center; - padding: ${tokens.padding.CONTAINER}px; + padding: ${tokens.padding.CONTAINER}px ${tokens.padding.CONTAINER / 2}px; & > div { display: flex; diff --git a/src/components/common/organisms/Tabs/Tabs.tsx b/src/components/common/organisms/Tabs/Tabs.tsx new file mode 100644 index 0000000..2b973be --- /dev/null +++ b/src/components/common/organisms/Tabs/Tabs.tsx @@ -0,0 +1,68 @@ +import { useEffect, useMemo, useState } from 'react' +import * as S from './styled' + +export 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..7cb9e7e --- /dev/null +++ b/src/components/common/organisms/Tabs/index.ts @@ -0,0 +1,2 @@ +export { default } from './Tabs' +export type { TabItem } from './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..0b50ff3 --- /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` +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..55e0232 --- /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.main` + 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; } diff --git a/src/main.tsx b/src/main.tsx index 284468d..8218efb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,16 @@ 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 } from 'react-router-dom' import { ThemeProvider } from '@emotion/react' -import theme from './core/theme.ts' +import theme from './core/theme' +import './index.css' +import App from './App' createRoot(document.getElementById('root')!).render( - - //TODO: Add more routes that takes you to different template pages - } /> - + diff --git a/src/pages/LiberyPage/LiberyPage.tsx b/src/pages/LiberyPage/LiberyPage.tsx new file mode 100644 index 0000000..ee0d4cc --- /dev/null +++ b/src/pages/LiberyPage/LiberyPage.tsx @@ -0,0 +1,19 @@ +import Tabs from '../../components/common/organisms/Tabs' +import { Main } from '../../components/common/styled' +import { LiberyTabs } from './tabs.config' +import Header from '../../components/common/Layout/Header' + +export default function LibraryPage() { + return ( + <> +
+ +
+ +
+ + ) +} diff --git a/src/pages/LiberyPage/tabs.config.tsx b/src/pages/LiberyPage/tabs.config.tsx new file mode 100644 index 0000000..308524e --- /dev/null +++ b/src/pages/LiberyPage/tabs.config.tsx @@ -0,0 +1,10 @@ +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 LiberyTabs: 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/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. + + ) +}