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 (
+
+ {isActive && t.content}
+
+ )
+ })}
+ >
+ )
+}
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.
+
+ )
+}