diff --git a/bun.lock b/bun.lock index 838a47c..1775e5d 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "calmhn", diff --git a/index.html b/index.html index 7eaaf3b..59537db 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ calmhn +
diff --git a/src/App.tsx b/src/App.tsx index 77b045d..6aeb346 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' import { ArrowSquareOut, ChatCircle, ArrowUp, Clock } from '@phosphor-icons/react' +import ThemeChooser from './ThemeChooser' interface AlgoliaStory { objectID: string @@ -110,12 +111,15 @@ function App() { return (
-
+
-

- Calm HN -

+
+

+ Calm HN +

+ +

Top stories from the last three months

@@ -132,7 +136,7 @@ function App() { aria-label={story.title} />
- + {index + 1}

diff --git a/src/ThemeChooser.tsx b/src/ThemeChooser.tsx new file mode 100644 index 0000000..3ec1a65 --- /dev/null +++ b/src/ThemeChooser.tsx @@ -0,0 +1,30 @@ +import { useTheme, type Theme } from './ThemeContext' + +const themes: { name: Theme; color: string }[] = [ + { name: 'purple', color: '#a855f7' }, + { name: 'red', color: '#ef4444' }, + { name: 'blue', color: '#3b82f6' }, + { name: 'green', color: '#22c55e' }, +] + +export default function ThemeChooser() { + const { theme, setTheme } = useTheme() + + return ( +
+ {themes.map((t) => ( +
+ ) +} diff --git a/src/ThemeContext.tsx b/src/ThemeContext.tsx new file mode 100644 index 0000000..6f3f642 --- /dev/null +++ b/src/ThemeContext.tsx @@ -0,0 +1,40 @@ +import { createContext, useContext, useState, useEffect, type ReactNode } from 'react' + +export type Theme = 'purple' | 'red' | 'blue' | 'green' + +interface ThemeContextType { + theme: Theme + setTheme: (theme: Theme) => void +} + +const ThemeContext = createContext(undefined) + +const STORAGE_KEY = 'calmhn-theme' +const VALID_THEMES: Theme[] = ['purple', 'red', 'blue', 'green'] + +function getInitialTheme(): Theme { + const saved = localStorage.getItem(STORAGE_KEY) + if (saved && VALID_THEMES.includes(saved as Theme)) return saved as Theme + return 'purple' +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setThemeState] = useState(getInitialTheme) + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme) + localStorage.setItem(STORAGE_KEY, theme) + }, [theme]) + + return ( + + {children} + + ) +} + +export function useTheme() { + const ctx = useContext(ThemeContext) + if (!ctx) throw new Error('useTheme must be used within ThemeProvider') + return ctx +} diff --git a/src/index.css b/src/index.css index b329b2a..011c686 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,25 @@ @import "tailwindcss"; +@theme { + --color-accent-200: #e9d5ff; +} + +[data-theme="purple"] { + --color-accent-200: #e9d5ff; +} + +[data-theme="red"] { + --color-accent-200: #fecaca; +} + +[data-theme="blue"] { + --color-accent-200: #bfdbfe; +} + +[data-theme="green"] { + --color-accent-200: #bbf7d0; +} + @font-face { font-family: InterVariable; font-style: normal; diff --git a/src/main.tsx b/src/main.tsx index bef5202..aa6c31c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,9 +2,12 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' +import { ThemeProvider } from './ThemeContext.tsx' createRoot(document.getElementById('root')!).render( - + + + , )