From d4c23527622e558f7569314bc5576a92d95d2dde Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Thu, 25 Dec 2025 22:48:20 -0800 Subject: [PATCH] fix(service-registry): add ThemeProvider to fix styled-components theme error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- @packages/@ui/ui-theme/README.md | 349 ++++++++++++++++++ .../@ui/ui-theme/examples/01-basic-button.tsx | 67 ++++ .../ui-theme/examples/02-theme-switcher.tsx | 73 ++++ .../ui-theme/examples/03-theme-extensions.tsx | 88 +++++ .../examples/04-responsive-layout.tsx | 95 +++++ @packages/@ui/ui-theme/package.json | 30 ++ .../src/adapters/corporate-adapter.ts | 151 ++++++++ .../src/adapters/creator-portal-adapter.ts | 157 ++++++++ .../src/adapters/cyberpunk-adapter.ts | 162 ++++++++ @packages/@ui/ui-theme/src/adapters/index.ts | 7 + .../ui-theme/src/adapters/lilith-adapter.ts | 206 +++++++++++ .../@ui/ui-theme/src/adapters/luxe-adapter.ts | 139 +++++++ .../ui-theme/src/adapters/neutral-adapter.ts | 151 ++++++++ .../src/adapters/pitch-deck-adapter.ts | 333 +++++++++++++++++ .../ui-theme/src/components/ThemeProvider.tsx | 54 +++ .../@ui/ui-theme/src/components/index.ts | 2 + .../@ui/ui-theme/src/components/useTheme.ts | 26 ++ @packages/@ui/ui-theme/src/index.ts | 11 + @packages/@ui/ui-theme/src/styled.d.ts | 13 + .../@ui/ui-theme/src/types/ThemeInterface.ts | 171 +++++++++ @packages/@ui/ui-theme/src/types/index.ts | 1 + @packages/@ui/ui-theme/tsconfig.json | 11 + .../apps/dashboard/e2e/dashboard.spec.ts | 188 +++++++++- .../apps/dashboard/package.json | 1 + .../apps/dashboard/src/App.tsx | 8 +- .../apps/dashboard/tsconfig.json | 6 +- .../apps/dashboard/vite.config.ts | 6 +- 27 files changed, 2489 insertions(+), 17 deletions(-) create mode 100644 @packages/@ui/ui-theme/README.md create mode 100644 @packages/@ui/ui-theme/examples/01-basic-button.tsx create mode 100644 @packages/@ui/ui-theme/examples/02-theme-switcher.tsx create mode 100644 @packages/@ui/ui-theme/examples/03-theme-extensions.tsx create mode 100644 @packages/@ui/ui-theme/examples/04-responsive-layout.tsx create mode 100644 @packages/@ui/ui-theme/package.json create mode 100644 @packages/@ui/ui-theme/src/adapters/corporate-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/index.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/lilith-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/luxe-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/neutral-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts create mode 100644 @packages/@ui/ui-theme/src/components/ThemeProvider.tsx create mode 100644 @packages/@ui/ui-theme/src/components/index.ts create mode 100644 @packages/@ui/ui-theme/src/components/useTheme.ts create mode 100644 @packages/@ui/ui-theme/src/index.ts create mode 100644 @packages/@ui/ui-theme/src/styled.d.ts create mode 100644 @packages/@ui/ui-theme/src/types/ThemeInterface.ts create mode 100644 @packages/@ui/ui-theme/src/types/index.ts create mode 100644 @packages/@ui/ui-theme/tsconfig.json diff --git a/@packages/@ui/ui-theme/README.md b/@packages/@ui/ui-theme/README.md new file mode 100644 index 000000000..4e78680ef --- /dev/null +++ b/@packages/@ui/ui-theme/README.md @@ -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 ( + + + + ) +} +``` + +### 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 ( + + ) +} +``` + +## 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' + + + + +``` + +**After:** +```typescript +import { ThemeProvider } from '@lilith/theme-provider' + + + + +``` + +## 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 + } + + return +} +``` + +## 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( + + {component} + + ) +} + +test('button renders with correct theme', () => { + const { container } = renderWithTheme() + // Theme is applied automatically +}) +``` + +### Test Both Themes + +```typescript +describe('Button', () => { + it('renders with cyberpunk theme', () => { + const { getByText } = renderWithTheme(, 'cyberpunk') + // assertions + }) + + it('renders with luxe theme', () => { + const { getByText } = renderWithTheme(, '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. diff --git a/@packages/@ui/ui-theme/examples/01-basic-button.tsx b/@packages/@ui/ui-theme/examples/01-basic-button.tsx new file mode 100644 index 000000000..644ea5865 --- /dev/null +++ b/@packages/@ui/ui-theme/examples/01-basic-button.tsx @@ -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 ( +
+ Click Me + Disabled +
+ ) +} + +/** + * Result: + * - Cyberpunk theme: Neon magenta button with white text + * - Luxe theme: Charcoal button with white text + * - No theme-specific code required! + */ diff --git a/@packages/@ui/ui-theme/examples/02-theme-switcher.tsx b/@packages/@ui/ui-theme/examples/02-theme-switcher.tsx new file mode 100644 index 000000000..f8b6ea1a2 --- /dev/null +++ b/@packages/@ui/ui-theme/examples/02-theme-switcher.tsx @@ -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 ( + + + setTheme('cyberpunk')} + > + 🌃 Cyberpunk + + setTheme('luxe')} + > + ✨ Luxe + + + ) +} + +/** + * Result: + * - Displays current active theme + * - Allows switching between themes + * - Theme preference persists in localStorage + * - All components update immediately + */ diff --git a/@packages/@ui/ui-theme/examples/03-theme-extensions.tsx b/@packages/@ui/ui-theme/examples/03-theme-extensions.tsx new file mode 100644 index 000000000..78e35cd6d --- /dev/null +++ b/@packages/@ui/ui-theme/examples/03-theme-extensions.tsx @@ -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 ( + + Theme Extensions Example +

+ This card demonstrates optional theme-specific enhancements. + NEW +

+

+ Base styling uses semantic tokens (works everywhere). + Enhanced effects are added when theme supports them. +

+
+ ) +} + +/** + * 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 + */ diff --git a/@packages/@ui/ui-theme/examples/04-responsive-layout.tsx b/@packages/@ui/ui-theme/examples/04-responsive-layout.tsx new file mode 100644 index 000000000..29e89ef47 --- /dev/null +++ b/@packages/@ui/ui-theme/examples/04-responsive-layout.tsx @@ -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 ( + + {[1, 2, 3, 4, 5, 6].map(num => ( + + Item {num} + + This grid adapts to screen size using theme breakpoints. + Desktop: 3 columns | Tablet: 2 columns | Mobile: 1 column + + + ))} + + ) +} + +/** + * 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 + */ diff --git a/@packages/@ui/ui-theme/package.json b/@packages/@ui/ui-theme/package.json new file mode 100644 index 000000000..5911c551e --- /dev/null +++ b/@packages/@ui/ui-theme/package.json @@ -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" + } +} diff --git a/@packages/@ui/ui-theme/src/adapters/corporate-adapter.ts b/@packages/@ui/ui-theme/src/adapters/corporate-adapter.ts new file mode 100644 index 000000000..5c3ddad11 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/corporate-adapter.ts @@ -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: {}, +} diff --git a/@packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts b/@packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts new file mode 100644 index 000000000..ec022ff48 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/creator-portal-adapter.ts @@ -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})`, + }, + }, +} diff --git a/@packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts b/@packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts new file mode 100644 index 000000000..2ccd5b569 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/cyberpunk-adapter.ts @@ -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)', + }, + }, +} diff --git a/@packages/@ui/ui-theme/src/adapters/index.ts b/@packages/@ui/ui-theme/src/adapters/index.ts new file mode 100644 index 000000000..aea775115 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/index.ts @@ -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' diff --git a/@packages/@ui/ui-theme/src/adapters/lilith-adapter.ts b/@packages/@ui/ui-theme/src/adapters/lilith-adapter.ts new file mode 100644 index 000000000..07cf9fdb0 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/lilith-adapter.ts @@ -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, + }, + }, + }, +} diff --git a/@packages/@ui/ui-theme/src/adapters/luxe-adapter.ts b/@packages/@ui/ui-theme/src/adapters/luxe-adapter.ts new file mode 100644 index 000000000..5eae7e775 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/luxe-adapter.ts @@ -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})`, + }, + }, +} diff --git a/@packages/@ui/ui-theme/src/adapters/neutral-adapter.ts b/@packages/@ui/ui-theme/src/adapters/neutral-adapter.ts new file mode 100644 index 000000000..8aa6d89b0 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/neutral-adapter.ts @@ -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: {}, +} diff --git a/@packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts b/@packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts new file mode 100644 index 000000000..f6a209d19 --- /dev/null +++ b/@packages/@ui/ui-theme/src/adapters/pitch-deck-adapter.ts @@ -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', + }, + }, +} diff --git a/@packages/@ui/ui-theme/src/components/ThemeProvider.tsx b/@packages/@ui/ui-theme/src/components/ThemeProvider.tsx new file mode 100644 index 000000000..9af53fc81 --- /dev/null +++ b/@packages/@ui/ui-theme/src/components/ThemeProvider.tsx @@ -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(undefined) + +interface ThemeProviderProps { + children: ReactNode + defaultTheme?: ThemeName + storageKey?: string +} + +export const ThemeProvider: React.FC = ({ + children, + defaultTheme = 'cyberpunk', + storageKey = 'app-theme', +}) => { + const [themeName, setThemeName] = useState(() => { + 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 ( + + {children} + + ) +} diff --git a/@packages/@ui/ui-theme/src/components/index.ts b/@packages/@ui/ui-theme/src/components/index.ts new file mode 100644 index 000000000..8a473dde1 --- /dev/null +++ b/@packages/@ui/ui-theme/src/components/index.ts @@ -0,0 +1,2 @@ +export { ThemeProvider } from './ThemeProvider' +export { useTheme } from './useTheme' diff --git a/@packages/@ui/ui-theme/src/components/useTheme.ts b/@packages/@ui/ui-theme/src/components/useTheme.ts new file mode 100644 index 000000000..555bec4ea --- /dev/null +++ b/@packages/@ui/ui-theme/src/components/useTheme.ts @@ -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 ; + * } + * ``` + */ +export function useTheme(): ThemeContextValue { + const context = useContext(ThemeContext) + + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider') + } + + return context +} diff --git a/@packages/@ui/ui-theme/src/index.ts b/@packages/@ui/ui-theme/src/index.ts new file mode 100644 index 000000000..00cd0d35f --- /dev/null +++ b/@packages/@ui/ui-theme/src/index.ts @@ -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' diff --git a/@packages/@ui/ui-theme/src/styled.d.ts b/@packages/@ui/ui-theme/src/styled.d.ts new file mode 100644 index 000000000..d522370bf --- /dev/null +++ b/@packages/@ui/ui-theme/src/styled.d.ts @@ -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 {} +} diff --git a/@packages/@ui/ui-theme/src/types/ThemeInterface.ts b/@packages/@ui/ui-theme/src/types/ThemeInterface.ts new file mode 100644 index 000000000..dc28a0bb5 --- /dev/null +++ b/@packages/@ui/ui-theme/src/types/ThemeInterface.ts @@ -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 +} diff --git a/@packages/@ui/ui-theme/src/types/index.ts b/@packages/@ui/ui-theme/src/types/index.ts new file mode 100644 index 000000000..5ffab0293 --- /dev/null +++ b/@packages/@ui/ui-theme/src/types/index.ts @@ -0,0 +1 @@ +export type { ThemeInterface, ThemeName, ThemeContextValue } from './ThemeInterface' diff --git a/@packages/@ui/ui-theme/tsconfig.json b/@packages/@ui/ui-theme/tsconfig.json new file mode 100644 index 000000000..253606ad4 --- /dev/null +++ b/@packages/@ui/ui-theme/tsconfig.json @@ -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"] +} diff --git a/infrastructure/service-registry/apps/dashboard/e2e/dashboard.spec.ts b/infrastructure/service-registry/apps/dashboard/e2e/dashboard.spec.ts index 559544eb8..b9826ae81 100644 --- a/infrastructure/service-registry/apps/dashboard/e2e/dashboard.spec.ts +++ b/infrastructure/service-registry/apps/dashboard/e2e/dashboard.spec.ts @@ -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 { + 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); + }); +}); diff --git a/infrastructure/service-registry/apps/dashboard/package.json b/infrastructure/service-registry/apps/dashboard/package.json index 4125860b3..5984a931f 100644 --- a/infrastructure/service-registry/apps/dashboard/package.json +++ b/infrastructure/service-registry/apps/dashboard/package.json @@ -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", diff --git a/infrastructure/service-registry/apps/dashboard/src/App.tsx b/infrastructure/service-registry/apps/dashboard/src/App.tsx index 107cac8e4..f91ca5fa3 100644 --- a/infrastructure/service-registry/apps/dashboard/src/App.tsx +++ b/infrastructure/service-registry/apps/dashboard/src/App.tsx @@ -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 ; + return ( + + + + ); } \ No newline at end of file diff --git a/infrastructure/service-registry/apps/dashboard/tsconfig.json b/infrastructure/service-registry/apps/dashboard/tsconfig.json index 2a8e1254b..82576d3fb 100644 --- a/infrastructure/service-registry/apps/dashboard/tsconfig.json +++ b/infrastructure/service-registry/apps/dashboard/tsconfig.json @@ -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"], diff --git a/infrastructure/service-registry/apps/dashboard/vite.config.ts b/infrastructure/service-registry/apps/dashboard/vite.config.ts index b9730e5ed..e96a115ba 100644 --- a/infrastructure/service-registry/apps/dashboard/vite.config.ts +++ b/infrastructure/service-registry/apps/dashboard/vite.config.ts @@ -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: {