fix(service-registry): add ThemeProvider to fix styled-components theme error
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>
This commit is contained in:
parent
dcd49db1b2
commit
d4c2352762
27 changed files with 2489 additions and 17 deletions
349
@packages/@ui/ui-theme/README.md
Normal file
349
@packages/@ui/ui-theme/README.md
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
# @lilith/theme-provider
|
||||
|
||||
**Multi-theme system with semantic token normalization for cyberpunk-ui and luxe-ui.**
|
||||
|
||||
Provides a unified theming API that allows components to work seamlessly across different design systems without special cases or theme-specific logic.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ **Semantic Token Normalization** - Maps theme-specific tokens to universal semantics
|
||||
- ✅ **Runtime Theme Switching** - Change themes without page reload
|
||||
- ✅ **Type-Safe Theme Contract** - TypeScript enforces theme interface conformance
|
||||
- ✅ **Zero Special Cases** - Components unaware of which theme is active
|
||||
- ✅ **localStorage Persistence** - Theme preference persists across sessions
|
||||
- ✅ **Theme Extensions** - Optional theme-specific effects (neon glow, shadows, etc.)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @lilith/theme-provider @lilith/cyberpunk-ui-core @lilith/luxe-ui styled-components
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Wrap Your App
|
||||
|
||||
```typescript
|
||||
import { ThemeProvider } from '@lilith/theme-provider'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="cyberpunk">
|
||||
<YourApp />
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Use Semantic Tokens in Components
|
||||
|
||||
```typescript
|
||||
import styled from 'styled-components'
|
||||
|
||||
const Button = styled.button`
|
||||
/* ✅ These work for BOTH themes automatically */
|
||||
color: ${props => props.theme.colors.primary};
|
||||
background: ${props => props.theme.colors.surface};
|
||||
padding: ${props => props.theme.spacing.md};
|
||||
border-radius: ${props => props.theme.borderRadius.md};
|
||||
font-family: ${props => props.theme.typography.fontFamily.body};
|
||||
|
||||
&:hover {
|
||||
background: ${props => props.theme.colors.hover.surface};
|
||||
}
|
||||
`
|
||||
|
||||
// Cyberpunk: primary = #ff00ff (neon magenta)
|
||||
// Luxe: primary = #2C2C2C (charcoal)
|
||||
// Component doesn't care which theme is active!
|
||||
```
|
||||
|
||||
### 3. Switch Themes at Runtime
|
||||
|
||||
```typescript
|
||||
import { useTheme } from '@lilith/theme-provider'
|
||||
|
||||
function ThemeSwitcher() {
|
||||
const { themeName, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<button onClick={() => setTheme(themeName === 'cyberpunk' ? 'luxe' : 'cyberpunk')}>
|
||||
Current: {themeName}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Semantic Token Contract
|
||||
|
||||
All themes implement the `ThemeInterface` which defines semantic tokens:
|
||||
|
||||
### Colors
|
||||
|
||||
| Semantic Token | Cyberpunk Value | Luxe Value | Purpose |
|
||||
|---------------|-----------------|------------|---------|
|
||||
| `colors.primary` | `#ff00ff` (magenta) | `#2C2C2C` (charcoal) | Primary brand color |
|
||||
| `colors.secondary` | `#00ffff` (cyan) | `#D4AF37` (gold) | Secondary accent |
|
||||
| `colors.accent` | `#00ff00` (green) | `#C9ADA7` (rose) | Highlight color |
|
||||
| `colors.background` | `#000000` (black) | `#FFFFFF` (white) | Page background |
|
||||
| `colors.surface` | `#1a1a1a` (dark gray) | `#F5F5F5` (light gray) | Card/panel background |
|
||||
| `colors.text.primary` | `#ffffff` (white) | `#2C2C2C` (charcoal) | Primary text |
|
||||
| `colors.text.secondary` | `#b0b0b0` (light gray) | `#616161` (dark gray) | Secondary text |
|
||||
| `colors.text.muted` | `#666666` (gray) | `#9E9E9E` (medium gray) | Muted text |
|
||||
| `colors.border` | `#333333` (dark gray) | `#E0E0E0` (light gray) | Borders |
|
||||
| `colors.success` | `#00ff00` (green) | `#4CAF50` (green) | Success state |
|
||||
| `colors.warning` | `#ffaa00` (orange) | `#FFA726` (orange) | Warning state |
|
||||
| `colors.error` | `#ff4444` (red) | `#EF5350` (red) | Error state |
|
||||
| `colors.info` | `#00ffff` (cyan) | `#42A5F5` (blue) | Info state |
|
||||
|
||||
### Spacing
|
||||
|
||||
All themes use the same spacing scale:
|
||||
|
||||
```typescript
|
||||
spacing: {
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '16px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
xxl: '48px'
|
||||
}
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
```typescript
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: string, // Cyberpunk: monospace | Luxe: Playfair Display
|
||||
body: string, // Cyberpunk: sans-serif | Luxe: Inter
|
||||
mono: string // Both: Courier New / Fira Code
|
||||
},
|
||||
fontSize: {
|
||||
xs: string,
|
||||
sm: string,
|
||||
base: string,
|
||||
lg: string,
|
||||
xl: string,
|
||||
'2xl': string,
|
||||
'3xl': string
|
||||
},
|
||||
fontWeight: {
|
||||
light: 300,
|
||||
normal: 400,
|
||||
medium: 500,
|
||||
semibold: 600,
|
||||
bold: 700
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Other Tokens
|
||||
|
||||
- **shadows** - `none`, `sm`, `md`, `lg`, `xl`
|
||||
- **borderRadius** - `none`, `sm`, `md`, `lg`, `full`
|
||||
- **transitions** - `fast`, `normal`, `slow`
|
||||
- **zIndex** - `base`, `dropdown`, `sticky`, `fixed`, `modal`, `popover`, `tooltip`, `toast`
|
||||
- **breakpoints** - `xs`, `sm`, `md`, `lg`, `xl`, `2xl`
|
||||
|
||||
## Theme Extensions
|
||||
|
||||
For theme-specific effects that don't translate across themes:
|
||||
|
||||
```typescript
|
||||
const CyberpunkSpinner = styled.div`
|
||||
/* Use semantic tokens for base styling */
|
||||
border: 3px solid ${props => props.theme.colors.border};
|
||||
border-top-color: ${props => props.theme.colors.primary};
|
||||
|
||||
/* Optional: Add theme-specific enhancement */
|
||||
${props => props.theme.extensions?.cyberpunk && css`
|
||||
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.magenta};
|
||||
`}
|
||||
`
|
||||
```
|
||||
|
||||
### Available Extensions
|
||||
|
||||
**Cyberpunk Extensions:**
|
||||
- `neonGlow.magenta`, `neonGlow.cyan`, `neonGlow.green`, `neonGlow.large`
|
||||
- `scanlines` - Retro scanline effect
|
||||
- `glitchEffect` - Drop shadow glitch effect
|
||||
|
||||
**Luxe Extensions:**
|
||||
- `goldShimmer` - Gold gradient shimmer effect
|
||||
- `elegantShadow` - Soft, sophisticated shadow
|
||||
- `subtleGradient` - Subtle background gradient
|
||||
|
||||
## API Reference
|
||||
|
||||
### ThemeProvider
|
||||
|
||||
```typescript
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode
|
||||
defaultTheme?: 'cyberpunk' | 'luxe' // Default: 'cyberpunk'
|
||||
storageKey?: string // Default: 'app-theme'
|
||||
}
|
||||
```
|
||||
|
||||
### useTheme Hook
|
||||
|
||||
```typescript
|
||||
interface ThemeContextValue {
|
||||
theme: ThemeInterface // Current theme object
|
||||
themeName: 'cyberpunk' | 'luxe' // Active theme name
|
||||
setTheme: (name: ThemeName) => void // Switch theme
|
||||
}
|
||||
|
||||
const { theme, themeName, setTheme } = useTheme()
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Hardcoded Styles
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
const Card = styled.div`
|
||||
background: #1a1a1a;
|
||||
padding: 16px;
|
||||
border: 1px solid #333;
|
||||
color: #ffffff;
|
||||
`
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
const Card = styled.div`
|
||||
background: ${props => props.theme.colors.surface};
|
||||
padding: ${props => props.theme.spacing.md};
|
||||
border: 1px solid ${props => props.theme.colors.border};
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
`
|
||||
```
|
||||
|
||||
### From Old ThemeProvider
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
import { luxeTheme } from '@lilith/luxe-ui'
|
||||
|
||||
<ThemeProvider theme={luxeTheme}>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
import { ThemeProvider } from '@lilith/theme-provider'
|
||||
|
||||
<ThemeProvider defaultTheme="luxe">
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
|
||||
- Use semantic tokens (`theme.colors.primary`, not `theme.colors.electricMagenta`)
|
||||
- Use theme spacing (`theme.spacing.md`, not `'16px'`)
|
||||
- Use theme breakpoints (`theme.breakpoints.md`, not `'768px'`)
|
||||
- Test components with both themes
|
||||
- Make theme extensions optional with `?.` operator
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
- Don't check `themeName` to render different components
|
||||
- Don't hardcode colors, spacing, or font sizes
|
||||
- Don't access raw theme values outside semantic interface
|
||||
- Don't create theme-specific components
|
||||
|
||||
### When Theme-Specific Logic is Necessary
|
||||
|
||||
If you absolutely need theme-specific behavior:
|
||||
|
||||
```typescript
|
||||
import { useTheme } from '@lilith/theme-provider'
|
||||
|
||||
function SpecialComponent() {
|
||||
const { themeName } = useTheme()
|
||||
|
||||
// Use sparingly - prefer semantic tokens!
|
||||
if (themeName === 'cyberpunk') {
|
||||
return <CyberpunkSpecificFeature />
|
||||
}
|
||||
|
||||
return <LuxeSpecificFeature />
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Mock Theme for Tests
|
||||
|
||||
```typescript
|
||||
import { ThemeProvider } from '@lilith/theme-provider'
|
||||
import { cyberpunkAdapter } from '@lilith/theme-provider/adapters'
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
function renderWithTheme(component: React.ReactElement, theme = 'cyberpunk') {
|
||||
return render(
|
||||
<ThemeProvider defaultTheme={theme}>
|
||||
{component}
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
test('button renders with correct theme', () => {
|
||||
const { container } = renderWithTheme(<Button>Click me</Button>)
|
||||
// Theme is applied automatically
|
||||
})
|
||||
```
|
||||
|
||||
### Test Both Themes
|
||||
|
||||
```typescript
|
||||
describe('Button', () => {
|
||||
it('renders with cyberpunk theme', () => {
|
||||
const { getByText } = renderWithTheme(<Button>Test</Button>, 'cyberpunk')
|
||||
// assertions
|
||||
})
|
||||
|
||||
it('renders with luxe theme', () => {
|
||||
const { getByText } = renderWithTheme(<Button>Test</Button>, 'luxe')
|
||||
// assertions
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
packages/theme-provider/
|
||||
├── src/
|
||||
│ ├── types/
|
||||
│ │ └── ThemeInterface.ts # Semantic token contract
|
||||
│ ├── adapters/
|
||||
│ │ ├── cyberpunk-adapter.ts # Maps cyberpunk → ThemeInterface
|
||||
│ │ └── luxe-adapter.ts # Maps luxe → ThemeInterface
|
||||
│ ├── components/
|
||||
│ │ ├── ThemeProvider.tsx # Provider component
|
||||
│ │ └── useTheme.ts # Theme hook
|
||||
│ └── index.ts
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
- **@lilith/cyberpunk-ui-core** - Cyberpunk design system components
|
||||
- **@lilith/luxe-ui** - Luxe design system components
|
||||
- **@lilith/react-components** - Themed business components
|
||||
- **@lilith/react-layouts** - Themed layout components
|
||||
|
||||
## License
|
||||
|
||||
See project root LICENSE file.
|
||||
67
@packages/@ui/ui-theme/examples/01-basic-button.tsx
Normal file
67
@packages/@ui/ui-theme/examples/01-basic-button.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Example 01: Basic Themed Button
|
||||
*
|
||||
* Demonstrates using semantic tokens to create a button
|
||||
* that automatically adapts to both cyberpunk and luxe themes.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const ThemedButton = styled.button`
|
||||
/* Colors - semantic tokens automatically adapt to active theme */
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
background: ${props => props.theme.colors.primary};
|
||||
border: 2px solid ${props => props.theme.colors.primary};
|
||||
|
||||
/* Spacing - consistent across all themes */
|
||||
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
|
||||
|
||||
/* Typography - theme-appropriate fonts */
|
||||
font-family: ${props => props.theme.typography.fontFamily.body};
|
||||
font-size: ${props => props.theme.typography.fontSize.base};
|
||||
font-weight: ${props => props.theme.typography.fontWeight.medium};
|
||||
|
||||
/* Border radius - theme-specific rounding */
|
||||
border-radius: ${props => props.theme.borderRadius.md};
|
||||
|
||||
/* Transitions - smooth interactions */
|
||||
transition: ${props => props.theme.transitions.normal};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
/* Hover state - using semantic hover colors */
|
||||
&:hover {
|
||||
background: ${props => props.theme.colors.hover.primary};
|
||||
border-color: ${props => props.theme.colors.hover.primary};
|
||||
}
|
||||
|
||||
/* Active state */
|
||||
&:active {
|
||||
background: ${props => props.theme.colors.active.primary};
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
&:disabled {
|
||||
background: ${props => props.theme.colors.disabled.background};
|
||||
color: ${props => props.theme.colors.disabled.text};
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`
|
||||
|
||||
// Usage:
|
||||
export function ButtonExample() {
|
||||
return (
|
||||
<div>
|
||||
<ThemedButton>Click Me</ThemedButton>
|
||||
<ThemedButton disabled>Disabled</ThemedButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Result:
|
||||
* - Cyberpunk theme: Neon magenta button with white text
|
||||
* - Luxe theme: Charcoal button with white text
|
||||
* - No theme-specific code required!
|
||||
*/
|
||||
73
@packages/@ui/ui-theme/examples/02-theme-switcher.tsx
Normal file
73
@packages/@ui/ui-theme/examples/02-theme-switcher.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
88
@packages/@ui/ui-theme/examples/03-theme-extensions.tsx
Normal file
88
@packages/@ui/ui-theme/examples/03-theme-extensions.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Example 03: Theme Extensions
|
||||
*
|
||||
* Demonstrates using optional theme-specific effects
|
||||
* that enhance components when available.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
const Card = styled.div`
|
||||
/* Base styling with semantic tokens - works for all themes */
|
||||
background: ${props => props.theme.colors.surface};
|
||||
padding: ${props => props.theme.spacing.lg};
|
||||
border-radius: ${props => props.theme.borderRadius.lg};
|
||||
border: 1px solid ${props => props.theme.colors.border};
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
|
||||
/* Optional: Add cyberpunk-specific neon glow effect */
|
||||
${props => props.theme.extensions?.cyberpunk && css`
|
||||
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.magenta};
|
||||
|
||||
/* Add scanlines effect */
|
||||
background-image: ${props.theme.extensions.cyberpunk.scanlines};
|
||||
`}
|
||||
|
||||
/* Optional: Add luxe-specific elegant shadow */
|
||||
${props => props.theme.extensions?.luxe && css`
|
||||
box-shadow: ${props.theme.extensions.luxe.elegantShadow};
|
||||
`}
|
||||
`
|
||||
|
||||
const Heading = styled.h2`
|
||||
font-family: ${props => props.theme.typography.fontFamily.heading};
|
||||
font-size: ${props => props.theme.typography.fontSize.xl};
|
||||
font-weight: ${props => props.theme.typography.fontWeight.bold};
|
||||
color: ${props => props.theme.colors.primary};
|
||||
margin-bottom: ${props => props.theme.spacing.md};
|
||||
|
||||
/* Optional: Add cyberpunk glitch effect */
|
||||
${props => props.theme.extensions?.cyberpunk && css`
|
||||
filter: ${props.theme.extensions.cyberpunk.glitchEffect};
|
||||
`}
|
||||
`
|
||||
|
||||
const Badge = styled.span`
|
||||
display: inline-block;
|
||||
padding: ${props => props.theme.spacing.xs} ${props => props.theme.spacing.sm};
|
||||
background: ${props => props.theme.colors.secondary};
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
border-radius: ${props => props.theme.borderRadius.full};
|
||||
font-size: ${props => props.theme.typography.fontSize.xs};
|
||||
font-weight: ${props => props.theme.typography.fontWeight.bold};
|
||||
|
||||
/* Optional: Add luxe gold shimmer effect */
|
||||
${props => props.theme.extensions?.luxe && css`
|
||||
background: ${props.theme.extensions.luxe.goldShimmer};
|
||||
`}
|
||||
|
||||
/* Optional: Add cyberpunk cyan glow */
|
||||
${props => props.theme.extensions?.cyberpunk && css`
|
||||
box-shadow: ${props.theme.extensions.cyberpunk.neonGlow.cyan};
|
||||
`}
|
||||
`
|
||||
|
||||
export function ExtensionExample() {
|
||||
return (
|
||||
<Card>
|
||||
<Heading>Theme Extensions Example</Heading>
|
||||
<p>
|
||||
This card demonstrates optional theme-specific enhancements.
|
||||
<Badge>NEW</Badge>
|
||||
</p>
|
||||
<p>
|
||||
Base styling uses semantic tokens (works everywhere).
|
||||
Enhanced effects are added when theme supports them.
|
||||
</p>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Result:
|
||||
* - Cyberpunk theme: Card with neon glow, scanlines, and glitch effect
|
||||
* - Luxe theme: Card with elegant shadow and gold shimmer
|
||||
* - Component works in both themes
|
||||
* - Extensions are OPTIONAL - won't break if theme doesn't support them
|
||||
*/
|
||||
95
@packages/@ui/ui-theme/examples/04-responsive-layout.tsx
Normal file
95
@packages/@ui/ui-theme/examples/04-responsive-layout.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Example 04: Responsive Layout with Theme Breakpoints
|
||||
*
|
||||
* Demonstrates using theme breakpoints for responsive design.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ResponsiveGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: ${props => props.theme.spacing.lg};
|
||||
padding: ${props => props.theme.spacing.lg};
|
||||
|
||||
/* Tablet: 2 columns */
|
||||
@media (max-width: ${props => props.theme.breakpoints.lg}) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: ${props => props.theme.spacing.md};
|
||||
}
|
||||
|
||||
/* Mobile: 1 column */
|
||||
@media (max-width: ${props => props.theme.breakpoints.md}) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: ${props => props.theme.spacing.sm};
|
||||
padding: ${props => props.theme.spacing.md};
|
||||
}
|
||||
`
|
||||
|
||||
const GridItem = styled.div`
|
||||
background: ${props => props.theme.colors.surface};
|
||||
padding: ${props => props.theme.spacing.lg};
|
||||
border-radius: ${props => props.theme.borderRadius.lg};
|
||||
border: 1px solid ${props => props.theme.colors.border};
|
||||
|
||||
/* Responsive padding */
|
||||
@media (max-width: ${props => props.theme.breakpoints.md}) {
|
||||
padding: ${props => props.theme.spacing.md};
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled.h3`
|
||||
font-family: ${props => props.theme.typography.fontFamily.heading};
|
||||
font-size: ${props => props.theme.typography.fontSize.lg};
|
||||
color: ${props => props.theme.colors.primary};
|
||||
margin-bottom: ${props => props.theme.spacing.sm};
|
||||
|
||||
/* Responsive font size */
|
||||
@media (max-width: ${props => props.theme.breakpoints.md}) {
|
||||
font-size: ${props => props.theme.typography.fontSize.base};
|
||||
}
|
||||
`
|
||||
|
||||
const Description = styled.p`
|
||||
font-family: ${props => props.theme.typography.fontFamily.body};
|
||||
font-size: ${props => props.theme.typography.fontSize.base};
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
line-height: ${props => props.theme.typography.lineHeight.relaxed};
|
||||
|
||||
/* Responsive font size */
|
||||
@media (max-width: ${props => props.theme.breakpoints.md}) {
|
||||
font-size: ${props => props.theme.typography.fontSize.sm};
|
||||
}
|
||||
`
|
||||
|
||||
export function ResponsiveExample() {
|
||||
return (
|
||||
<ResponsiveGrid>
|
||||
{[1, 2, 3, 4, 5, 6].map(num => (
|
||||
<GridItem key={num}>
|
||||
<Title>Item {num}</Title>
|
||||
<Description>
|
||||
This grid adapts to screen size using theme breakpoints.
|
||||
Desktop: 3 columns | Tablet: 2 columns | Mobile: 1 column
|
||||
</Description>
|
||||
</GridItem>
|
||||
))}
|
||||
</ResponsiveGrid>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme Breakpoints:
|
||||
* - xs: 0px
|
||||
* - sm: 640px
|
||||
* - md: 768px
|
||||
* - lg: 1024px
|
||||
* - xl: 1280px
|
||||
* - 2xl: 1536px
|
||||
*
|
||||
* Result:
|
||||
* - Consistent responsive behavior across themes
|
||||
* - Breakpoints match theme system
|
||||
* - Spacing scales appropriately
|
||||
*/
|
||||
30
@packages/@ui/ui-theme/package.json
Normal file
30
@packages/@ui/ui-theme/package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@lilith/ui-theme",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "echo 'Skipping lint - theme-provider is a minimal package'",
|
||||
"test": "echo 'Skipping test - theme-provider has no tests yet'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lilith/design-tokens": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"styled-components": "^6.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"styled-components": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
151
@packages/@ui/ui-theme/src/adapters/corporate-adapter.ts
Normal file
151
@packages/@ui/ui-theme/src/adapters/corporate-adapter.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { gray } = colorPrimitives
|
||||
const { semantic } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Corporate Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for corporate/business pages.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Professional blue and gray palette
|
||||
* - Clean, trustworthy design
|
||||
* - Fixed typography sizing
|
||||
* - Subtle shadows
|
||||
* - Standard transitions
|
||||
*/
|
||||
export const corporateThemeAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: '#1a365d', // Deep professional blue
|
||||
secondary: '#2b6cb0', // Lighter blue accent
|
||||
accent: '#3182ce', // Bright blue for CTAs
|
||||
background: {
|
||||
primary: '#FFFFFF',
|
||||
secondary: gray[50],
|
||||
tertiary: gray[100],
|
||||
},
|
||||
surface: gray[50],
|
||||
text: {
|
||||
primary: gray[900],
|
||||
secondary: gray[600],
|
||||
muted: gray[500],
|
||||
tertiary: gray[400],
|
||||
},
|
||||
border: gray[200],
|
||||
success: semantic.success,
|
||||
warning: semantic.warning,
|
||||
error: semantic.error,
|
||||
info: semantic.info,
|
||||
hover: {
|
||||
primary: '#2c5282',
|
||||
secondary: '#2b6cb0',
|
||||
surface: gray[100],
|
||||
},
|
||||
active: {
|
||||
primary: '#1a365d',
|
||||
secondary: '#2b6cb0',
|
||||
},
|
||||
disabled: {
|
||||
background: gray[100],
|
||||
text: gray[400],
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xxxs: '1px',
|
||||
xxs: '2px',
|
||||
xs: spacing[1],
|
||||
sm: spacing[2],
|
||||
md: spacing[4],
|
||||
lg: spacing[6],
|
||||
xl: spacing[8],
|
||||
xxl: spacing[12],
|
||||
xxxl: spacing[16],
|
||||
xxxxl: spacing[20],
|
||||
xxxxxl: spacing[24],
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.body.luxe,
|
||||
body: typography.fonts.body.luxe,
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizes.xs,
|
||||
sm: typography.fontSizes.sm,
|
||||
md: typography.fontSizes.sm,
|
||||
base: typography.fontSizes.base,
|
||||
lg: typography.fontSizes.md,
|
||||
xl: typography.fontSizes.lg,
|
||||
'2xl': typography.fontSizes.xl,
|
||||
'3xl': typography.fontSizes['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
letterSpacing: {
|
||||
tight: '-0.025em',
|
||||
normal: '0',
|
||||
wide: '0.025em',
|
||||
},
|
||||
borderWidth: {
|
||||
thin: '1px',
|
||||
medium: '2px',
|
||||
thick: '3px',
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
xs: borderRadius.xs,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
xl: borderRadius.xl,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast,
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
extensions: {},
|
||||
}
|
||||
157
@packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts
Normal file
157
@packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { luxe, gray } = colorPrimitives
|
||||
const { semantic } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Creator Portal Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for creator-facing UIs.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Warm, inviting palette with gold accents
|
||||
* - Professional yet approachable design
|
||||
* - Fluid responsive typography
|
||||
* - Soft, elegant shadows
|
||||
* - Smooth transitions
|
||||
*/
|
||||
export const creatorPortalThemeAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: luxe.charcoal,
|
||||
secondary: luxe.gold,
|
||||
accent: luxe.rose,
|
||||
background: {
|
||||
primary: luxe.white,
|
||||
secondary: luxe.cream,
|
||||
tertiary: luxe.lightGray,
|
||||
},
|
||||
surface: luxe.cream,
|
||||
text: {
|
||||
primary: luxe.charcoal,
|
||||
secondary: luxe.darkGray,
|
||||
muted: luxe.mediumGray,
|
||||
tertiary: gray[400],
|
||||
},
|
||||
border: luxe.gray,
|
||||
success: semantic.success,
|
||||
warning: semantic.warning,
|
||||
error: semantic.error,
|
||||
info: semantic.info,
|
||||
hover: {
|
||||
primary: 'rgba(44, 44, 44, 0.08)',
|
||||
secondary: 'rgba(212, 175, 55, 0.15)',
|
||||
surface: luxe.lightGray,
|
||||
},
|
||||
active: {
|
||||
primary: 'rgba(44, 44, 44, 0.12)',
|
||||
secondary: 'rgba(212, 175, 55, 0.25)',
|
||||
},
|
||||
disabled: {
|
||||
background: luxe.lightGray,
|
||||
text: luxe.mediumGray,
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xxxs: '1px',
|
||||
xxs: '2px',
|
||||
xs: spacing[1],
|
||||
sm: spacing[2],
|
||||
md: spacing[4],
|
||||
lg: spacing[6],
|
||||
xl: spacing[8],
|
||||
xxl: spacing[12],
|
||||
xxxl: spacing[16],
|
||||
xxxxl: spacing[20],
|
||||
xxxxxl: spacing[24],
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.heading.luxe,
|
||||
body: typography.fonts.body.luxe,
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizesFluid.xs,
|
||||
sm: typography.fontSizesFluid.sm,
|
||||
md: typography.fontSizesFluid.sm,
|
||||
base: typography.fontSizesFluid.base,
|
||||
lg: typography.fontSizesFluid.md,
|
||||
xl: typography.fontSizesFluid.lg,
|
||||
'2xl': typography.fontSizesFluid.xl,
|
||||
'3xl': typography.fontSizesFluid['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
letterSpacing: {
|
||||
tight: '-0.025em',
|
||||
normal: '0',
|
||||
wide: '0.025em',
|
||||
},
|
||||
borderWidth: {
|
||||
thin: '1px',
|
||||
medium: '2px',
|
||||
thick: '3px',
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
xs: borderRadius.xs,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
xl: borderRadius.xl,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast,
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
extensions: {
|
||||
luxe: {
|
||||
goldShimmer: `linear-gradient(135deg, ${luxe.gold} 0%, ${luxe.gold} 50%, ${luxe.gold} 100%)`,
|
||||
elegantShadow: '0 10px 40px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04)',
|
||||
subtleGradient: `linear-gradient(to bottom, ${luxe.white}, ${luxe.cream})`,
|
||||
},
|
||||
},
|
||||
}
|
||||
162
@packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts
Normal file
162
@packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { cyberpunk } = colorPrimitives
|
||||
const { gray } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Cyberpunk Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for the Cyberpunk aesthetic.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Electric neon palette (magenta, cyan, green)
|
||||
* - Dark backgrounds with high contrast
|
||||
* - Fixed typography sizing
|
||||
* - Deep, dramatic shadows with neon glows
|
||||
* - Quick, snappy transitions
|
||||
*/
|
||||
export const cyberpunkAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: cyberpunk.electricMagenta,
|
||||
secondary: cyberpunk.neonCyan,
|
||||
accent: cyberpunk.neonGreen,
|
||||
background: {
|
||||
primary: cyberpunk.black,
|
||||
secondary: cyberpunk.darkBg,
|
||||
tertiary: gray[800],
|
||||
},
|
||||
surface: cyberpunk.darkBg,
|
||||
text: {
|
||||
primary: cyberpunk.white,
|
||||
secondary: gray[400],
|
||||
muted: gray[500],
|
||||
tertiary: gray[600],
|
||||
},
|
||||
border: gray[700],
|
||||
success: cyberpunk.neonGreen,
|
||||
warning: cyberpunk.electricOrange,
|
||||
error: cyberpunk.neonRed,
|
||||
info: cyberpunk.neonCyan,
|
||||
hover: {
|
||||
primary: cyberpunk.magentaLight, // #ff66ff
|
||||
secondary: cyberpunk.cyanLight, // #66ffff
|
||||
surface: cyberpunk.darkBg, // #1a1a1a
|
||||
},
|
||||
active: {
|
||||
primary: cyberpunk.magentaDark, // #cc00cc
|
||||
secondary: cyberpunk.cyanDark, // #00cccc
|
||||
},
|
||||
disabled: {
|
||||
background: gray[800], // #222222
|
||||
text: gray[500], // #666666
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xxxs: '1px',
|
||||
xxs: '2px',
|
||||
xs: spacing[1], // 4px
|
||||
sm: spacing[2], // 8px
|
||||
md: spacing[4], // 16px
|
||||
lg: spacing[6], // 24px
|
||||
xl: spacing[8], // 32px
|
||||
xxl: spacing[12], // 48px
|
||||
xxxl: spacing[16], // 64px
|
||||
xxxxl: spacing[20], // 80px
|
||||
xxxxxl: spacing[24], // 96px
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.heading.cyberpunk,
|
||||
body: typography.fonts.body.cyberpunk,
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizes.xs,
|
||||
sm: typography.fontSizes.sm,
|
||||
md: typography.fontSizes.sm,
|
||||
base: typography.fontSizes.base,
|
||||
lg: typography.fontSizes.md,
|
||||
xl: typography.fontSizes.lg,
|
||||
'2xl': typography.fontSizes.xl,
|
||||
'3xl': typography.fontSizes['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
letterSpacing: {
|
||||
tight: '-0.025em',
|
||||
normal: '0',
|
||||
wide: '0.025em',
|
||||
},
|
||||
borderWidth: {
|
||||
thin: '1px',
|
||||
medium: '2px',
|
||||
thick: '3px',
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.dark.none,
|
||||
sm: shadows.dark.sm,
|
||||
md: shadows.dark.md,
|
||||
lg: shadows.dark.lg,
|
||||
xl: shadows.dark.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
xs: borderRadius.xs,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
xl: borderRadius.xl,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.cyberpunk.fast,
|
||||
normal: transitions.cyberpunk.base,
|
||||
slow: transitions.cyberpunk.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
extensions: {
|
||||
cyberpunk: {
|
||||
neonGlow: {
|
||||
magenta: shadows.neon.magenta,
|
||||
cyan: shadows.neon.cyan,
|
||||
green: shadows.neon.green,
|
||||
large: shadows.neon.large,
|
||||
},
|
||||
scanlines: 'repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15) 1px, transparent 1px, transparent 2px)',
|
||||
glitchEffect: 'drop-shadow(2px 0 0 #ff00ff) drop-shadow(-2px 0 0 #00ffff)',
|
||||
},
|
||||
},
|
||||
}
|
||||
7
@packages/@ui/ui-theme/src/adapters/index.ts
Normal file
7
@packages/@ui/ui-theme/src/adapters/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export { cyberpunkAdapter } from './cyberpunk-adapter'
|
||||
export { luxeAdapter } from './luxe-adapter'
|
||||
export { lilithAdapter } from './lilith-adapter'
|
||||
export { pitchDeckAdapter, pitchDeckLightAdapter } from './pitch-deck-adapter'
|
||||
export { neutralThemeAdapter } from './neutral-adapter'
|
||||
export { creatorPortalThemeAdapter } from './creator-portal-adapter'
|
||||
export { corporateThemeAdapter } from './corporate-adapter'
|
||||
206
@packages/@ui/ui-theme/src/adapters/lilith-adapter.ts
Normal file
206
@packages/@ui/ui-theme/src/adapters/lilith-adapter.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { lilith } = colorPrimitives
|
||||
const { semantic } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Lilith Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for the Lilith brand aesthetic.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Power, sexuality, and mystique (deep reds, purples, blacks)
|
||||
* - Creator empowerment through gold/bronze accents
|
||||
* - Warm, inviting neutrals (not cold corporate grays)
|
||||
* - Bold, commanding presence
|
||||
* - Dramatic shadows and transitions
|
||||
*
|
||||
* Brand Identity:
|
||||
* - Defiant and empowering
|
||||
* - Unapologetically sexual
|
||||
* - Mystical and enigmatic
|
||||
* - Premium and powerful
|
||||
*/
|
||||
export const lilithAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
// Primary - Deep crimson red (power, passion, defiance)
|
||||
primary: lilith.crimson,
|
||||
|
||||
// Secondary - Creator gold (wealth, value, success)
|
||||
secondary: lilith.creatorGold,
|
||||
|
||||
// Accent - Royal purple (mystique, occult, feminine power)
|
||||
accent: lilith.royalPurple,
|
||||
|
||||
background: {
|
||||
// Warm off-white for main background
|
||||
primary: lilith.offWhite,
|
||||
// Warm cream for secondary surfaces
|
||||
secondary: lilith.warmCream,
|
||||
// Deep black for high contrast sections
|
||||
tertiary: lilith.deepBlack,
|
||||
},
|
||||
|
||||
// Surface color for cards and elevated elements
|
||||
surface: lilith.warmCream,
|
||||
|
||||
text: {
|
||||
// Deep black for primary text (high contrast on warm backgrounds)
|
||||
primary: lilith.deepBlack,
|
||||
// Charcoal gray for secondary text
|
||||
secondary: lilith.charcoalGray,
|
||||
// Warm gray for muted text
|
||||
muted: lilith.warmGray,
|
||||
// Warm gray for tertiary text
|
||||
tertiary: lilith.warmGray,
|
||||
},
|
||||
|
||||
// Border color - warm gray
|
||||
border: lilith.warmGray,
|
||||
|
||||
// Semantic colors
|
||||
success: semantic.success,
|
||||
warning: semantic.warning,
|
||||
error: semantic.error,
|
||||
info: semantic.info,
|
||||
|
||||
hover: {
|
||||
// Crimson with opacity for primary hover
|
||||
primary: 'rgba(220, 20, 60, 0.12)',
|
||||
// Gold with opacity for secondary hover
|
||||
secondary: 'rgba(212, 175, 55, 0.15)',
|
||||
// Subtle warm hover for surfaces
|
||||
surface: 'rgba(54, 52, 47, 0.04)',
|
||||
},
|
||||
|
||||
active: {
|
||||
// Crimson with higher opacity for active state
|
||||
primary: 'rgba(220, 20, 60, 0.20)',
|
||||
// Gold with higher opacity for active state
|
||||
secondary: 'rgba(212, 175, 55, 0.25)',
|
||||
},
|
||||
|
||||
disabled: {
|
||||
// Warm gray for disabled backgrounds
|
||||
background: lilith.warmGray,
|
||||
// Lighter warm gray for disabled text
|
||||
text: lilith.warmGray,
|
||||
},
|
||||
},
|
||||
|
||||
spacing: {
|
||||
xs: spacing[1], // 4px
|
||||
sm: spacing[2], // 8px
|
||||
md: spacing[4], // 16px
|
||||
lg: spacing[6], // 24px
|
||||
xl: spacing[8], // 32px
|
||||
xxl: spacing[12], // 48px
|
||||
},
|
||||
|
||||
typography: {
|
||||
fontFamily: {
|
||||
// Bold, commanding serif for headings
|
||||
heading: typography.fonts.heading.lilith,
|
||||
// Clean, readable sans-serif for body
|
||||
body: typography.fonts.body.lilith,
|
||||
// Monospace for technical content
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
// Use fluid font sizes for responsive design
|
||||
xs: typography.fontSizesFluid.xs,
|
||||
sm: typography.fontSizesFluid.sm,
|
||||
md: typography.fontSizesFluid.sm,
|
||||
base: typography.fontSizesFluid.base,
|
||||
lg: typography.fontSizesFluid.md,
|
||||
xl: typography.fontSizesFluid.lg,
|
||||
'2xl': typography.fontSizesFluid.xl,
|
||||
'3xl': typography.fontSizesFluid['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
|
||||
shadows: {
|
||||
// Use light shadows for warm background aesthetic
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
sm: borderRadius.xs,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
|
||||
transitions: {
|
||||
// Lilith transitions - dramatic and powerful
|
||||
fast: transitions.lilith.fast,
|
||||
normal: transitions.lilith.base,
|
||||
slow: transitions.lilith.slow,
|
||||
},
|
||||
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
|
||||
// Lilith-specific extensions
|
||||
extensions: {
|
||||
lilith: {
|
||||
// Mystical gradient effects
|
||||
crimsonGradient: `linear-gradient(135deg, ${lilith.burgundy} 0%, ${lilith.crimson} 50%, ${lilith.deepRed} 100%)`,
|
||||
purpleGradient: `linear-gradient(135deg, ${lilith.darkPurple} 0%, ${lilith.royalPurple} 50%, ${lilith.plum} 100%)`,
|
||||
goldShimmer: `linear-gradient(135deg, ${lilith.bronze} 0%, ${lilith.creatorGold} 50%, ${lilith.amberGold} 100%)`,
|
||||
|
||||
// Mystical glow shadows
|
||||
crimsonGlow: shadows.mystical.crimson,
|
||||
purpleGlow: shadows.mystical.purple,
|
||||
goldGlow: shadows.mystical.gold,
|
||||
|
||||
// Warm background gradient
|
||||
warmBackground: `linear-gradient(to bottom, ${lilith.offWhite}, ${lilith.warmCream})`,
|
||||
|
||||
// Dark mode variant colors (for future dark mode support)
|
||||
darkVariant: {
|
||||
background: lilith.deepBlack,
|
||||
surface: lilith.charcoalGray,
|
||||
text: lilith.offWhite,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
139
@packages/@ui/ui-theme/src/adapters/luxe-adapter.ts
Normal file
139
@packages/@ui/ui-theme/src/adapters/luxe-adapter.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { luxe } = colorPrimitives
|
||||
const { semantic } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Luxe Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for the Luxe aesthetic.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Neutral, sophisticated palette (charcoal, gold, rose)
|
||||
* - Light backgrounds with generous whitespace
|
||||
* - Fluid responsive typography
|
||||
* - Soft, subtle shadows
|
||||
* - Elegant, slower transitions
|
||||
*/
|
||||
export const luxeAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: luxe.charcoal,
|
||||
secondary: luxe.gold,
|
||||
accent: luxe.rose,
|
||||
background: {
|
||||
primary: luxe.white,
|
||||
secondary: luxe.lightGray,
|
||||
tertiary: luxe.cream,
|
||||
},
|
||||
surface: luxe.lightGray,
|
||||
text: {
|
||||
primary: luxe.charcoal,
|
||||
secondary: luxe.darkGray,
|
||||
muted: luxe.mediumGray,
|
||||
tertiary: luxe.darkGray,
|
||||
},
|
||||
border: luxe.gray,
|
||||
success: semantic.success,
|
||||
warning: semantic.warning,
|
||||
error: semantic.error,
|
||||
info: semantic.info,
|
||||
hover: {
|
||||
primary: 'rgba(44, 44, 44, 0.12)', // charcoal with 12% opacity
|
||||
secondary: 'rgba(212, 175, 55, 0.12)', // gold with 12% opacity
|
||||
surface: 'rgba(44, 44, 44, 0.04)', // subtle surface hover
|
||||
},
|
||||
active: {
|
||||
primary: 'rgba(44, 44, 44, 0.08)', // charcoal with 8% opacity
|
||||
secondary: 'rgba(212, 175, 55, 0.20)', // gold with 20% opacity
|
||||
},
|
||||
disabled: {
|
||||
background: luxe.gray,
|
||||
text: luxe.mediumGray,
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xs: spacing[1], // 4px
|
||||
sm: spacing[2], // 8px
|
||||
md: spacing[4], // 16px
|
||||
lg: spacing[6], // 24px
|
||||
xl: spacing[8], // 32px
|
||||
xxl: spacing[12], // 48px
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.heading.luxe,
|
||||
body: typography.fonts.body.luxe,
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
xs: typography.fontSizesFluid.xs,
|
||||
sm: typography.fontSizesFluid.sm,
|
||||
md: typography.fontSizesFluid.sm,
|
||||
base: typography.fontSizesFluid.base,
|
||||
lg: typography.fontSizesFluid.md,
|
||||
xl: typography.fontSizesFluid.lg,
|
||||
'2xl': typography.fontSizesFluid.xl,
|
||||
'3xl': typography.fontSizesFluid['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
sm: borderRadius.xs,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast,
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
extensions: {
|
||||
luxe: {
|
||||
goldShimmer: `linear-gradient(135deg, ${luxe.gold} 0%, ${luxe.gold} 50%, ${luxe.gold} 100%)`,
|
||||
elegantShadow: '0 10px 40px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04)',
|
||||
subtleGradient: `linear-gradient(to bottom, ${luxe.white}, ${luxe.cream})`,
|
||||
},
|
||||
},
|
||||
}
|
||||
151
@packages/@ui/ui-theme/src/adapters/neutral-adapter.ts
Normal file
151
@packages/@ui/ui-theme/src/adapters/neutral-adapter.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { gray } = colorPrimitives
|
||||
const { semantic } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Neutral Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for a clean, neutral aesthetic.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Clean gray palette with minimal accent colors
|
||||
* - Professional, accessible design
|
||||
* - Fixed typography sizing
|
||||
* - Subtle shadows
|
||||
* - Standard transitions
|
||||
*/
|
||||
export const neutralThemeAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: gray[900],
|
||||
secondary: gray[600],
|
||||
accent: semantic.info,
|
||||
background: {
|
||||
primary: '#FFFFFF',
|
||||
secondary: gray[50],
|
||||
tertiary: gray[100],
|
||||
},
|
||||
surface: gray[50],
|
||||
text: {
|
||||
primary: gray[900],
|
||||
secondary: gray[600],
|
||||
muted: gray[500],
|
||||
tertiary: gray[400],
|
||||
},
|
||||
border: gray[200],
|
||||
success: semantic.success,
|
||||
warning: semantic.warning,
|
||||
error: semantic.error,
|
||||
info: semantic.info,
|
||||
hover: {
|
||||
primary: gray[800],
|
||||
secondary: gray[500],
|
||||
surface: gray[100],
|
||||
},
|
||||
active: {
|
||||
primary: gray[700],
|
||||
secondary: gray[400],
|
||||
},
|
||||
disabled: {
|
||||
background: gray[100],
|
||||
text: gray[400],
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xxxs: '1px',
|
||||
xxs: '2px',
|
||||
xs: spacing[1],
|
||||
sm: spacing[2],
|
||||
md: spacing[4],
|
||||
lg: spacing[6],
|
||||
xl: spacing[8],
|
||||
xxl: spacing[12],
|
||||
xxxl: spacing[16],
|
||||
xxxxl: spacing[20],
|
||||
xxxxxl: spacing[24],
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.body.luxe,
|
||||
body: typography.fonts.body.luxe,
|
||||
mono: typography.fonts.mono,
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizes.xs,
|
||||
sm: typography.fontSizes.sm,
|
||||
md: typography.fontSizes.sm,
|
||||
base: typography.fontSizes.base,
|
||||
lg: typography.fontSizes.md,
|
||||
xl: typography.fontSizes.lg,
|
||||
'2xl': typography.fontSizes.xl,
|
||||
'3xl': typography.fontSizes['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
letterSpacing: {
|
||||
tight: '-0.025em',
|
||||
normal: '0',
|
||||
wide: '0.025em',
|
||||
},
|
||||
borderWidth: {
|
||||
thin: '1px',
|
||||
medium: '2px',
|
||||
thick: '3px',
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
xs: borderRadius.xs,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
xl: borderRadius.xl,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast,
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
extensions: {},
|
||||
}
|
||||
333
@packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts
Normal file
333
@packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
import { colorPrimitives, typography, spacing, borderRadius, shadows, transitions, zIndices, breakpoints } from '@lilith/design-tokens'
|
||||
import type { ThemeInterface } from '../types/ThemeInterface'
|
||||
|
||||
const { gray } = colorPrimitives
|
||||
|
||||
/**
|
||||
* Pitch Deck Theme Adapter
|
||||
*
|
||||
* Maps base design tokens to semantic ThemeInterface for investor presentations.
|
||||
*
|
||||
* Philosophy:
|
||||
* - Professional dark background with high contrast
|
||||
* - Purple/cyan accent palette for visual interest
|
||||
* - Clean, readable typography for data-heavy slides
|
||||
* - Smooth, professional transitions
|
||||
* - Minimal decorative effects to maintain focus
|
||||
*/
|
||||
|
||||
// Custom colors for pitch deck - professional luxe investor presentation palette
|
||||
const pitchDeckColors = {
|
||||
// Primary purple - sophisticated, trustworthy (slightly warmer)
|
||||
primary: '#9333ea',
|
||||
primaryDark: '#7e22ce',
|
||||
primaryLight: '#a855f7',
|
||||
|
||||
// Accent gold - premium, luxury, success
|
||||
accent: '#d4af37',
|
||||
accentDark: '#b8941f',
|
||||
accentLight: '#f0d574',
|
||||
|
||||
// Secondary rose gold - elegance, refinement
|
||||
secondary: '#e0b0a8',
|
||||
secondaryDark: '#c5948c',
|
||||
secondaryLight: '#f5cfc4',
|
||||
|
||||
// Dark luxe backgrounds
|
||||
background: '#0a0a0a',
|
||||
surface: '#161616',
|
||||
surfaceLight: '#1f1f1f',
|
||||
|
||||
// Text colors - higher contrast for readability
|
||||
white: '#ffffff',
|
||||
textMuted: '#b8b8b8',
|
||||
textSubtle: '#808080',
|
||||
}
|
||||
|
||||
// Light theme colors - professional clean investor presentation palette
|
||||
const pitchDeckLightColors = {
|
||||
// Primary purple - consistent with dark mode
|
||||
primary: '#9333ea',
|
||||
primaryDark: '#7e22ce',
|
||||
primaryLight: '#a855f7',
|
||||
|
||||
// Accent gold - consistent with dark mode
|
||||
accent: '#d4af37',
|
||||
accentDark: '#b8941f',
|
||||
accentLight: '#f0d574',
|
||||
|
||||
// Secondary rose gold - consistent with dark mode
|
||||
secondary: '#e0b0a8',
|
||||
secondaryDark: '#c5948c',
|
||||
secondaryLight: '#f5cfc4',
|
||||
|
||||
// Light backgrounds
|
||||
background: '#ffffff',
|
||||
surface: '#f8f8f8',
|
||||
surfaceLight: '#f0f0f0',
|
||||
|
||||
// Text colors - dark text for light background
|
||||
textPrimary: '#1a1a1a',
|
||||
textSecondary: '#4a4a4a',
|
||||
textMuted: '#6a6a6a',
|
||||
}
|
||||
|
||||
export const pitchDeckAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: pitchDeckColors.primary,
|
||||
secondary: pitchDeckColors.secondary,
|
||||
accent: pitchDeckColors.accent,
|
||||
background: {
|
||||
primary: pitchDeckColors.background,
|
||||
secondary: pitchDeckColors.surface,
|
||||
tertiary: pitchDeckColors.surfaceLight,
|
||||
},
|
||||
surface: pitchDeckColors.surface,
|
||||
text: {
|
||||
primary: pitchDeckColors.white,
|
||||
secondary: pitchDeckColors.textMuted,
|
||||
muted: pitchDeckColors.textSubtle,
|
||||
tertiary: gray[400],
|
||||
},
|
||||
border: gray[700],
|
||||
success: '#22c55e',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444',
|
||||
info: pitchDeckColors.accent,
|
||||
hover: {
|
||||
primary: pitchDeckColors.primaryLight,
|
||||
secondary: pitchDeckColors.secondaryLight,
|
||||
surface: pitchDeckColors.surfaceLight,
|
||||
},
|
||||
active: {
|
||||
primary: pitchDeckColors.primaryDark,
|
||||
secondary: pitchDeckColors.secondaryDark,
|
||||
},
|
||||
disabled: {
|
||||
background: gray[800],
|
||||
text: gray[500],
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xs: spacing[1], // 4px
|
||||
sm: spacing[2], // 8px
|
||||
md: spacing[4], // 16px
|
||||
lg: spacing[6], // 24px
|
||||
xl: spacing[8], // 32px
|
||||
xxl: spacing[12], // 48px
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.body.luxe, // Inter for clean headings
|
||||
body: typography.fonts.body.luxe, // Inter for body
|
||||
mono: typography.fonts.mono, // Fira Code for code/data
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizes.xs,
|
||||
sm: typography.fontSizes.sm,
|
||||
md: typography.fontSizes.sm,
|
||||
base: typography.fontSizes.base,
|
||||
lg: typography.fontSizes.md,
|
||||
xl: typography.fontSizes.lg,
|
||||
'2xl': typography.fontSizes.xl,
|
||||
'3xl': typography.fontSizes['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.dark.none,
|
||||
sm: shadows.dark.sm,
|
||||
md: shadows.dark.md,
|
||||
lg: shadows.dark.lg,
|
||||
xl: shadows.dark.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast, // Smooth professional transitions
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
// Custom pitch deck extensions for special effects
|
||||
extensions: {
|
||||
// Optional: subtle glow effects for highlights
|
||||
cyberpunk: {
|
||||
neonGlow: {
|
||||
magenta: `0 0 10px ${pitchDeckColors.secondary}`,
|
||||
cyan: `0 0 10px ${pitchDeckColors.accent}`,
|
||||
green: '0 0 10px #22c55e',
|
||||
large: '0 0 20px',
|
||||
},
|
||||
scanlines: 'none',
|
||||
glitchEffect: 'none',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const pitchDeckLightAdapter: ThemeInterface = {
|
||||
colors: {
|
||||
primary: pitchDeckLightColors.primary,
|
||||
secondary: pitchDeckLightColors.secondary,
|
||||
accent: pitchDeckLightColors.accent,
|
||||
background: {
|
||||
primary: pitchDeckLightColors.background,
|
||||
secondary: pitchDeckLightColors.surface,
|
||||
tertiary: pitchDeckLightColors.surfaceLight,
|
||||
},
|
||||
surface: pitchDeckLightColors.surface,
|
||||
text: {
|
||||
primary: pitchDeckLightColors.textPrimary,
|
||||
secondary: pitchDeckLightColors.textSecondary,
|
||||
muted: pitchDeckLightColors.textMuted,
|
||||
tertiary: gray[500],
|
||||
},
|
||||
border: gray[300],
|
||||
success: '#22c55e',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444',
|
||||
info: pitchDeckLightColors.accent,
|
||||
hover: {
|
||||
primary: pitchDeckLightColors.primaryLight,
|
||||
secondary: pitchDeckLightColors.secondaryLight,
|
||||
surface: pitchDeckLightColors.surfaceLight,
|
||||
},
|
||||
active: {
|
||||
primary: pitchDeckLightColors.primaryDark,
|
||||
secondary: pitchDeckLightColors.secondaryDark,
|
||||
},
|
||||
disabled: {
|
||||
background: gray[200],
|
||||
text: gray[400],
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xs: spacing[1], // 4px
|
||||
sm: spacing[2], // 8px
|
||||
md: spacing[4], // 16px
|
||||
lg: spacing[6], // 24px
|
||||
xl: spacing[8], // 32px
|
||||
xxl: spacing[12], // 48px
|
||||
},
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: typography.fonts.body.luxe, // Inter for clean headings
|
||||
body: typography.fonts.body.luxe, // Inter for body
|
||||
mono: typography.fonts.mono, // Fira Code for code/data
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '10px',
|
||||
xs: typography.fontSizes.xs,
|
||||
sm: typography.fontSizes.sm,
|
||||
md: typography.fontSizes.sm,
|
||||
base: typography.fontSizes.base,
|
||||
lg: typography.fontSizes.md,
|
||||
xl: typography.fontSizes.lg,
|
||||
'2xl': typography.fontSizes.xl,
|
||||
'3xl': typography.fontSizes['2xl'],
|
||||
'4xl': '36px', // 2.25rem - large headings
|
||||
'5xl': '48px', // 3rem - display headings
|
||||
},
|
||||
fontWeight: {
|
||||
light: typography.fontWeights.light,
|
||||
normal: typography.fontWeights.regular,
|
||||
medium: typography.fontWeights.medium,
|
||||
semibold: typography.fontWeights.semibold,
|
||||
bold: typography.fontWeights.bold,
|
||||
},
|
||||
lineHeight: {
|
||||
tight: typography.lineHeights.tight,
|
||||
normal: typography.lineHeights.base,
|
||||
relaxed: typography.lineHeights.relaxed,
|
||||
loose: typography.lineHeights.loose,
|
||||
},
|
||||
},
|
||||
shadows: {
|
||||
none: shadows.light.none,
|
||||
sm: shadows.light.sm,
|
||||
md: shadows.light.md,
|
||||
lg: shadows.light.lg,
|
||||
xl: shadows.light.xl,
|
||||
},
|
||||
borderRadius: {
|
||||
none: borderRadius.none,
|
||||
sm: borderRadius.sm,
|
||||
md: borderRadius.md,
|
||||
lg: borderRadius.lg,
|
||||
full: borderRadius.full,
|
||||
},
|
||||
transitions: {
|
||||
fast: transitions.luxe.fast, // Smooth professional transitions
|
||||
normal: transitions.luxe.base,
|
||||
slow: transitions.luxe.slow,
|
||||
},
|
||||
zIndex: {
|
||||
base: zIndices.base,
|
||||
dropdown: zIndices.dropdown,
|
||||
sticky: zIndices.sticky,
|
||||
fixed: zIndices.fixed,
|
||||
modal: zIndices.modal,
|
||||
popover: zIndices.popover,
|
||||
tooltip: zIndices.tooltip,
|
||||
toast: zIndices.toast,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: breakpoints.xs,
|
||||
sm: breakpoints.sm,
|
||||
md: breakpoints.md,
|
||||
lg: breakpoints.lg,
|
||||
xl: breakpoints.xl,
|
||||
'2xl': breakpoints['2xl'],
|
||||
},
|
||||
// Custom pitch deck extensions for special effects
|
||||
extensions: {
|
||||
// Optional: subtle effects for light mode highlights
|
||||
cyberpunk: {
|
||||
neonGlow: {
|
||||
magenta: `0 0 10px ${pitchDeckLightColors.secondary}`,
|
||||
cyan: `0 0 10px ${pitchDeckLightColors.accent}`,
|
||||
green: '0 0 10px #22c55e',
|
||||
large: '0 0 20px',
|
||||
},
|
||||
scanlines: 'none',
|
||||
glitchEffect: 'none',
|
||||
},
|
||||
},
|
||||
}
|
||||
54
@packages/@ui/ui-theme/src/components/ThemeProvider.tsx
Normal file
54
@packages/@ui/ui-theme/src/components/ThemeProvider.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React, { createContext, useState, useEffect } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { ThemeProvider as SCThemeProvider } from 'styled-components'
|
||||
import { cyberpunkAdapter } from '../adapters/cyberpunk-adapter'
|
||||
import { luxeAdapter } from '../adapters/luxe-adapter'
|
||||
import type { ThemeInterface, ThemeName, ThemeContextValue } from '../types/ThemeInterface'
|
||||
|
||||
/**
|
||||
* Theme context - internal, use useTheme() hook instead
|
||||
*/
|
||||
export const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode
|
||||
defaultTheme?: ThemeName
|
||||
storageKey?: string
|
||||
}
|
||||
|
||||
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
children,
|
||||
defaultTheme = 'cyberpunk',
|
||||
storageKey = 'app-theme',
|
||||
}) => {
|
||||
const [themeName, setThemeName] = useState<ThemeName>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem(storageKey)
|
||||
if (stored === 'cyberpunk' || stored === 'luxe') {
|
||||
return stored
|
||||
}
|
||||
}
|
||||
return defaultTheme
|
||||
})
|
||||
|
||||
const theme: ThemeInterface = themeName === 'cyberpunk' ? cyberpunkAdapter : luxeAdapter
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(storageKey, themeName)
|
||||
}
|
||||
}, [themeName, storageKey])
|
||||
|
||||
|
||||
const contextValue: ThemeContextValue = {
|
||||
theme,
|
||||
themeName,
|
||||
setTheme: setThemeName,
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={contextValue}>
|
||||
<SCThemeProvider theme={theme as any}>{children}</SCThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
2
@packages/@ui/ui-theme/src/components/index.ts
Normal file
2
@packages/@ui/ui-theme/src/components/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { ThemeProvider } from './ThemeProvider'
|
||||
export { useTheme } from './useTheme'
|
||||
26
@packages/@ui/ui-theme/src/components/useTheme.ts
Normal file
26
@packages/@ui/ui-theme/src/components/useTheme.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useContext } from 'react'
|
||||
import { ThemeContext } from './ThemeProvider'
|
||||
import type { ThemeContextValue } from '../types/ThemeInterface'
|
||||
|
||||
/**
|
||||
* Hook to access theme context
|
||||
*
|
||||
* @throws Error if used outside of ThemeProvider
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function MyComponent() {
|
||||
* const { theme, themeName, setTheme } = useTheme();
|
||||
* return <button onClick={() => setTheme('luxe')}>Switch Theme</button>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useTheme(): ThemeContextValue {
|
||||
const context = useContext(ThemeContext)
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
11
@packages/@ui/ui-theme/src/index.ts
Normal file
11
@packages/@ui/ui-theme/src/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Styled-components type augmentation
|
||||
import './styled.d.ts'
|
||||
|
||||
// Provider and hook
|
||||
export { ThemeProvider, useTheme } from './components'
|
||||
|
||||
// Theme adapters
|
||||
export { cyberpunkAdapter, luxeAdapter, pitchDeckAdapter, pitchDeckLightAdapter, neutralThemeAdapter, creatorPortalThemeAdapter, corporateThemeAdapter } from './adapters'
|
||||
|
||||
// Types
|
||||
export type { ThemeInterface, ThemeName, ThemeContextValue } from './types'
|
||||
13
@packages/@ui/ui-theme/src/styled.d.ts
vendored
Normal file
13
@packages/@ui/ui-theme/src/styled.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Styled Components Type Augmentation
|
||||
*
|
||||
* Extends styled-components DefaultTheme with our ThemeInterface.
|
||||
* This allows TypeScript to recognize theme properties in styled components.
|
||||
*/
|
||||
|
||||
import 'styled-components';
|
||||
import type { ThemeInterface } from '@lilith/ui-theme';
|
||||
|
||||
declare module 'styled-components' {
|
||||
export interface DefaultTheme extends ThemeInterface {}
|
||||
}
|
||||
171
@packages/@ui/ui-theme/src/types/ThemeInterface.ts
Normal file
171
@packages/@ui/ui-theme/src/types/ThemeInterface.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
export interface ThemeInterface {
|
||||
colors: {
|
||||
primary: string
|
||||
secondary: string
|
||||
accent: string
|
||||
background: {
|
||||
primary: string
|
||||
secondary: string
|
||||
tertiary: string
|
||||
}
|
||||
surface: string
|
||||
text: {
|
||||
primary: string
|
||||
secondary: string
|
||||
tertiary: string
|
||||
muted: string
|
||||
}
|
||||
border: string
|
||||
success: string
|
||||
warning: string
|
||||
error: string
|
||||
info: string
|
||||
hover: {
|
||||
primary: string
|
||||
secondary: string
|
||||
surface: string
|
||||
}
|
||||
active: {
|
||||
primary: string
|
||||
secondary: string
|
||||
}
|
||||
disabled: {
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
spacing: {
|
||||
xxxs?: string // ~1px
|
||||
xxs?: string // ~2px
|
||||
xs: string // 4px
|
||||
sm: string // 8px
|
||||
md: string // 16px
|
||||
lg: string // 24px
|
||||
xl: string // 32px
|
||||
xxl: string // 48px
|
||||
xxxl?: string // 64px
|
||||
xxxxl?: string // 80px
|
||||
xxxxxl?: string // 96px
|
||||
}
|
||||
typography: {
|
||||
fontFamily: {
|
||||
heading: string
|
||||
body: string
|
||||
mono: string
|
||||
}
|
||||
fontSize: {
|
||||
xxs?: string // 10px
|
||||
xs: string // 12px
|
||||
sm: string // 14px
|
||||
md: string // 16px
|
||||
base: string // 16px
|
||||
lg: string // 18px
|
||||
xl: string // 20px
|
||||
'2xl': string // 24px
|
||||
'3xl': string // 30px
|
||||
'4xl': string // 36px
|
||||
'5xl': string // 48px
|
||||
}
|
||||
fontWeight: {
|
||||
light: number
|
||||
normal: number
|
||||
medium: number
|
||||
semibold: number
|
||||
bold: number
|
||||
}
|
||||
lineHeight: {
|
||||
tight: number
|
||||
normal: number
|
||||
relaxed: number
|
||||
loose: number
|
||||
}
|
||||
}
|
||||
letterSpacing?: {
|
||||
tight?: string
|
||||
normal?: string
|
||||
wide?: string
|
||||
}
|
||||
borderWidth?: {
|
||||
thin?: string // 1px
|
||||
medium?: string // 2px
|
||||
thick?: string // 3px
|
||||
}
|
||||
shadows: {
|
||||
none: string
|
||||
sm: string
|
||||
md: string
|
||||
lg: string
|
||||
xl: string
|
||||
}
|
||||
borderRadius: {
|
||||
none: string
|
||||
xs?: string // 2px
|
||||
sm: string // 4px
|
||||
md: string // 6px
|
||||
lg: string // 8px
|
||||
xl?: string // 12px
|
||||
full: string
|
||||
}
|
||||
transitions: {
|
||||
fast: string
|
||||
normal: string
|
||||
slow: string
|
||||
}
|
||||
zIndex: {
|
||||
base: number
|
||||
dropdown: number
|
||||
sticky: number
|
||||
fixed: number
|
||||
modal: number
|
||||
popover: number
|
||||
tooltip: number
|
||||
toast: number
|
||||
}
|
||||
breakpoints: {
|
||||
xs: string
|
||||
sm: string
|
||||
md: string
|
||||
lg: string
|
||||
xl: string
|
||||
'2xl': string
|
||||
}
|
||||
extensions?: {
|
||||
cyberpunk?: {
|
||||
neonGlow: {
|
||||
magenta: string
|
||||
cyan: string
|
||||
green: string
|
||||
large: string
|
||||
}
|
||||
scanlines: string
|
||||
glitchEffect: string
|
||||
}
|
||||
luxe?: {
|
||||
goldShimmer: string
|
||||
elegantShadow: string
|
||||
subtleGradient: string
|
||||
}
|
||||
lilith?: {
|
||||
crimsonGradient: string
|
||||
purpleGradient: string
|
||||
goldShimmer: string
|
||||
crimsonGlow: string
|
||||
purpleGlow: string
|
||||
goldGlow: string
|
||||
warmBackground: string
|
||||
darkVariant: {
|
||||
background: string
|
||||
surface: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ThemeName = 'cyberpunk' | 'luxe' | 'lilith'
|
||||
|
||||
export interface ThemeContextValue {
|
||||
theme: ThemeInterface
|
||||
themeName: ThemeName
|
||||
setTheme: (name: ThemeName) => void
|
||||
}
|
||||
1
@packages/@ui/ui-theme/src/types/index.ts
Normal file
1
@packages/@ui/ui-theme/src/types/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type { ThemeInterface, ThemeName, ThemeContextValue } from './ThemeInterface'
|
||||
11
@packages/@ui/ui-theme/tsconfig.json
Normal file
11
@packages/@ui/ui-theme/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"jsx": "react-jsx",
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "examples"]
|
||||
}
|
||||
|
|
@ -1,33 +1,195 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const ALL_ROUTES = [
|
||||
{ path: '/', name: 'Overview' },
|
||||
{ path: '/services', name: 'Services' },
|
||||
{ path: '/architecture', name: 'Architecture' },
|
||||
{ path: '/health', name: 'Health' },
|
||||
{ path: '/ports', name: 'Ports' },
|
||||
{ path: '/metrics', name: 'Metrics' },
|
||||
{ path: '/settings', name: 'Settings' },
|
||||
];
|
||||
|
||||
async function captureConsoleErrors(page: Page): Promise<string[]> {
|
||||
const errors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
page.on('pageerror', (error) => {
|
||||
errors.push(error.message);
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
|
||||
test.describe('Service Registry Dashboard', () => {
|
||||
test('loads homepage successfully', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Verify the page title or header
|
||||
await expect(page).toHaveTitle(/service registry/i);
|
||||
});
|
||||
|
||||
test('navigation works', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for navigation to be visible
|
||||
await page.waitForSelector('nav', { timeout: 5000 });
|
||||
|
||||
// Check that navigation links are present
|
||||
const servicesLink = page.getByRole('link', { name: /services/i });
|
||||
await expect(servicesLink).toBeVisible();
|
||||
});
|
||||
|
||||
test('dashboard renders with UI library components', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Verify UI components are rendered with glassmorphism styling
|
||||
// This tests the integration of service-local components and @lilith/ui-primitives
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check for presence of styled components from the UI library
|
||||
const bodyElement = await page.locator('body');
|
||||
const bodyElement = page.locator('body');
|
||||
await expect(bodyElement).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('All Routes Render Without Errors', () => {
|
||||
for (const route of ALL_ROUTES) {
|
||||
test(`${route.name} page (${route.path}) loads without console errors`, async ({ page }) => {
|
||||
const errors = await captureConsoleErrors(page);
|
||||
|
||||
await page.goto(route.path);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Wait for React to hydrate and render
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check no React errors in console
|
||||
const reactErrors = errors.filter(e =>
|
||||
e.includes('React') ||
|
||||
e.includes('TypeError') ||
|
||||
e.includes('undefined') ||
|
||||
e.includes('theme')
|
||||
);
|
||||
|
||||
expect(reactErrors, `Console errors on ${route.path}: ${reactErrors.join(', ')}`).toHaveLength(0);
|
||||
});
|
||||
|
||||
test(`${route.name} page (${route.path}) renders content`, async ({ page }) => {
|
||||
await page.goto(route.path);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Page should have visible content (not blank)
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
// Check there's no error boundary message
|
||||
const errorBoundary = page.locator('text=/something went wrong/i');
|
||||
await expect(errorBoundary).not.toBeVisible();
|
||||
|
||||
// Check React Router didn't catch an error
|
||||
const routerError = page.locator('text=/error during render/i');
|
||||
await expect(routerError).not.toBeVisible();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('UI Components Integration', () => {
|
||||
test('buttons render with proper theme styling', async ({ page }) => {
|
||||
const errors = await captureConsoleErrors(page);
|
||||
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Find any button element
|
||||
const buttons = page.locator('button');
|
||||
const buttonCount = await buttons.count();
|
||||
|
||||
if (buttonCount > 0) {
|
||||
const firstButton = buttons.first();
|
||||
await expect(firstButton).toBeVisible();
|
||||
|
||||
// Verify button has computed styles (not broken theme)
|
||||
const buttonStyles = await firstButton.evaluate((el) => {
|
||||
const styles = window.getComputedStyle(el);
|
||||
return {
|
||||
display: styles.display,
|
||||
padding: styles.padding,
|
||||
};
|
||||
});
|
||||
|
||||
// Button should have layout (not collapsed due to undefined theme)
|
||||
expect(buttonStyles.display).not.toBe('');
|
||||
}
|
||||
|
||||
// No theme-related errors
|
||||
const themeErrors = errors.filter(e => e.includes('spacing') || e.includes('theme'));
|
||||
expect(themeErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('styled-components theme is available', async ({ page }) => {
|
||||
const errors = await captureConsoleErrors(page);
|
||||
|
||||
await page.goto('/services');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for any dynamic content
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check for theme-related TypeErrors
|
||||
const undefinedErrors = errors.filter(e =>
|
||||
e.includes("can't access property") ||
|
||||
e.includes('undefined') ||
|
||||
e.includes('theme.spacing')
|
||||
);
|
||||
|
||||
expect(undefinedErrors, `Theme errors: ${undefinedErrors.join(', ')}`).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Form Components', () => {
|
||||
test('forms on services page render without errors', async ({ page }) => {
|
||||
const errors = await captureConsoleErrors(page);
|
||||
|
||||
await page.goto('/services');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for form elements
|
||||
const forms = page.locator('form');
|
||||
const formCount = await forms.count();
|
||||
|
||||
if (formCount > 0) {
|
||||
// If there are forms, they should be visible
|
||||
await expect(forms.first()).toBeVisible();
|
||||
}
|
||||
|
||||
// No render errors
|
||||
const renderErrors = errors.filter(e =>
|
||||
e.includes('React') || e.includes('render')
|
||||
);
|
||||
expect(renderErrors).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Navigation Flow', () => {
|
||||
test('can navigate through all pages without crashes', async ({ page }) => {
|
||||
const errors = await captureConsoleErrors(page);
|
||||
|
||||
// Start at home
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Navigate to each route via links
|
||||
for (const route of ALL_ROUTES.slice(1)) {
|
||||
const link = page.getByRole('link', { name: new RegExp(route.name, 'i') });
|
||||
const isVisible = await link.isVisible().catch(() => false);
|
||||
|
||||
if (isVisible) {
|
||||
await link.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify we're on the right page
|
||||
await expect(page).toHaveURL(new RegExp(route.path));
|
||||
}
|
||||
}
|
||||
|
||||
// No fatal errors during navigation
|
||||
const fatalErrors = errors.filter(e =>
|
||||
e.includes('TypeError') ||
|
||||
e.includes('ReferenceError') ||
|
||||
e.includes('undefined')
|
||||
);
|
||||
expect(fatalErrors, `Navigation errors: ${fatalErrors.join(', ')}`).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"@lilith/ui-primitives": "workspace:*",
|
||||
"@lilith/ui-data": "workspace:*",
|
||||
"@lilith/ui-layout": "workspace:*",
|
||||
"@lilith/ui-theme": "workspace:*",
|
||||
"lucide-react": "^0.462.0",
|
||||
"architecture-viz": "*",
|
||||
"axios": "^1.7.5",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import React from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { ThemeProvider } from '@lilith/ui-theme';
|
||||
import { router } from './router';
|
||||
|
||||
export default function App() {
|
||||
return <RouterProvider router={router} />;
|
||||
return (
|
||||
<ThemeProvider defaultTheme="cyberpunk" storageKey="service-registry-theme">
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -21,7 +21,11 @@
|
|||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@service-registry/types": ["../../packages/@service-registry/types/src"],
|
||||
"@service-registry/client": ["../../packages/@service-registry/client/src"]
|
||||
"@service-registry/client": ["../../packages/@service-registry/client/src"],
|
||||
"@lilith/ui-theme": ["../../../../@packages/@ui/ui-theme/src"],
|
||||
"@lilith/ui-primitives": ["../../../../@packages/@ui/ui-primitives/src"],
|
||||
"@lilith/ui-data": ["../../../../@packages/@ui/ui-data/src"],
|
||||
"@lilith/ui-layout": ["../../../../@packages/@ui/ui-layout/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ export default defineConfig({
|
|||
'@': resolve(__dirname, './src'),
|
||||
'@service-registry/types': resolve(__dirname, '../../packages/@service-registry/types/src'),
|
||||
'@service-registry/client': resolve(__dirname, '../../packages/@service-registry/client/src'),
|
||||
'architecture-viz': resolve(__dirname, '../../packages/architecture-viz/src')
|
||||
'architecture-viz': resolve(__dirname, '../../packages/architecture-viz/src'),
|
||||
'@lilith/ui-theme': resolve(__dirname, '../../../../@packages/@ui/ui-theme/src'),
|
||||
'@lilith/ui-primitives': resolve(__dirname, '../../../../@packages/@ui/ui-primitives/src'),
|
||||
'@lilith/ui-data': resolve(__dirname, '../../../../@packages/@ui/ui-data/src'),
|
||||
'@lilith/ui-layout': resolve(__dirname, '../../../../@packages/@ui/ui-layout/src')
|
||||
}
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue