The dashboard was crashing with "TypeError: can't access property 'sm', e.theme.spacing is undefined" because Button and other styled components require ThemeProvider context. Changes: - Add ThemeProvider wrapper in App.tsx with cyberpunk theme - Add @lilith/ui-theme dependency - Add vite aliases and tsconfig paths for @lilith/* packages - Add comprehensive E2E tests covering all 7 routes - E2E tests now detect console errors and theme-related TypeErrors The new E2E test suite would catch this class of error before deployment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
/**
|
|
* Example 02: Theme Switcher Component
|
|
*
|
|
* Demonstrates runtime theme switching with the useTheme hook.
|
|
*/
|
|
|
|
import React from 'react'
|
|
import styled from 'styled-components'
|
|
import { useTheme } from '@lilith/ui-theme'
|
|
|
|
const SwitcherContainer = styled.div`
|
|
display: flex;
|
|
align-items: center;
|
|
gap: ${props => props.theme.spacing.md};
|
|
padding: ${props => props.theme.spacing.md};
|
|
background: ${props => props.theme.colors.surface};
|
|
border-radius: ${props => props.theme.borderRadius.lg};
|
|
border: 1px solid ${props => props.theme.colors.border};
|
|
`
|
|
|
|
const SwitcherButton = styled.button<{ $active: boolean }>`
|
|
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
|
|
background: ${props => props.$active ? props.theme.colors.primary : 'transparent'};
|
|
color: ${props => props.$active ? props.theme.colors.text.primary : props.theme.colors.text.secondary};
|
|
border: 2px solid ${props => props.$active ? props.theme.colors.primary : props.theme.colors.border};
|
|
border-radius: ${props => props.theme.borderRadius.md};
|
|
font-family: ${props => props.theme.typography.fontFamily.body};
|
|
font-size: ${props => props.theme.typography.fontSize.sm};
|
|
font-weight: ${props => props.theme.typography.fontWeight.medium};
|
|
cursor: pointer;
|
|
transition: ${props => props.theme.transitions.fast};
|
|
|
|
&:hover {
|
|
border-color: ${props => props.theme.colors.primary};
|
|
}
|
|
`
|
|
|
|
const Label = styled.span`
|
|
font-family: ${props => props.theme.typography.fontFamily.body};
|
|
font-size: ${props => props.theme.typography.fontSize.sm};
|
|
color: ${props => props.theme.colors.text.primary};
|
|
font-weight: ${props => props.theme.typography.fontWeight.semibold};
|
|
`
|
|
|
|
export function ThemeSwitcher() {
|
|
const { themeName, setTheme } = useTheme()
|
|
|
|
return (
|
|
<SwitcherContainer>
|
|
<Label>Theme:</Label>
|
|
<SwitcherButton
|
|
$active={themeName === 'cyberpunk'}
|
|
onClick={() => setTheme('cyberpunk')}
|
|
>
|
|
🌃 Cyberpunk
|
|
</SwitcherButton>
|
|
<SwitcherButton
|
|
$active={themeName === 'luxe'}
|
|
onClick={() => setTheme('luxe')}
|
|
>
|
|
✨ Luxe
|
|
</SwitcherButton>
|
|
</SwitcherContainer>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Result:
|
|
* - Displays current active theme
|
|
* - Allows switching between themes
|
|
* - Theme preference persists in localStorage
|
|
* - All components update immediately
|
|
*/
|