MAESTRO: Create ThemeProvider component for web interface

- Add src/web/components/ThemeProvider.tsx with React context for theming
- Provide useTheme and useThemeColors hooks for child components
- Include default theme (Dracula) for initial render before WebSocket connection
- Update tsconfig.json to include src/web and src/shared directories
This commit is contained in:
Pedram Amini
2025-11-27 03:11:02 -06:00
parent 98aebe06f3
commit 2ce759d8a0
4 changed files with 164 additions and 1 deletions

View File

@@ -0,0 +1,141 @@
/**
* ThemeProvider component for Maestro web interface
*
* Provides theme context to web components. Accepts theme via props
* (typically received from WebSocket connection to desktop app).
*/
import React, { createContext, useContext, useMemo } from 'react';
import type { Theme, ThemeColors } from '../../shared/theme-types';
/**
* Context value containing the current theme and utility functions
*/
interface ThemeContextValue {
/** Current theme object */
theme: Theme;
/** Whether the theme is a light theme */
isLight: boolean;
/** Whether the theme is a dark theme */
isDark: boolean;
/** Whether the theme is a vibe theme */
isVibe: boolean;
}
/**
* Default theme used when no theme is provided
* Matches the Dracula theme from the desktop app
*/
const defaultTheme: Theme = {
id: 'dracula',
name: 'Dracula',
mode: 'dark',
colors: {
bgMain: '#0b0b0d',
bgSidebar: '#111113',
bgActivity: '#1c1c1f',
border: '#27272a',
textMain: '#e4e4e7',
textDim: '#a1a1aa',
accent: '#6366f1',
accentDim: 'rgba(99, 102, 241, 0.2)',
accentText: '#a5b4fc',
success: '#22c55e',
warning: '#eab308',
error: '#ef4444',
},
};
const ThemeContext = createContext<ThemeContextValue | null>(null);
export interface ThemeProviderProps {
/** Theme object to provide to children. If not provided, uses default theme. */
theme?: Theme;
/** Children components that will have access to the theme */
children: React.ReactNode;
}
/**
* ThemeProvider component that provides theme context to the component tree
*
* @example
* ```tsx
* // With theme from WebSocket
* <ThemeProvider theme={themeFromServer}>
* <App />
* </ThemeProvider>
*
* // Using the context in a child component
* const { theme, isDark } = useTheme();
* ```
*/
export function ThemeProvider({ theme = defaultTheme, children }: ThemeProviderProps) {
const contextValue = useMemo<ThemeContextValue>(
() => ({
theme,
isLight: theme.mode === 'light',
isDark: theme.mode === 'dark',
isVibe: theme.mode === 'vibe',
}),
[theme]
);
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
}
/**
* Hook to access the current theme context
*
* @throws Error if used outside of a ThemeProvider
*
* @example
* ```tsx
* function MyComponent() {
* const { theme, isDark } = useTheme();
* return (
* <div style={{ backgroundColor: theme.colors.bgMain }}>
* {isDark ? 'Dark mode' : 'Light mode'}
* </div>
* );
* }
* ```
*/
export function useTheme(): ThemeContextValue {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
/**
* Hook to access just the theme colors for convenience
*
* @throws Error if used outside of a ThemeProvider
*
* @example
* ```tsx
* function Button() {
* const colors = useThemeColors();
* return (
* <button style={{
* backgroundColor: colors.accent,
* color: colors.accentText
* }}>
* Click me
* </button>
* );
* }
* ```
*/
export function useThemeColors(): ThemeColors {
const { theme } = useTheme();
return theme.colors;
}
export { ThemeContext };
export type { ThemeContextValue };

View File

@@ -0,0 +1,13 @@
/**
* Web interface components for Maestro
*
* Shared components used by both mobile and desktop web interfaces.
*/
export {
ThemeProvider,
useTheme,
useThemeColors,
ThemeContext,
} from './ThemeProvider';
export type { ThemeProviderProps, ThemeContextValue } from './ThemeProvider';

9
src/web/index.ts Normal file
View File

@@ -0,0 +1,9 @@
/**
* Maestro Web Interface
*
* This module contains shared components, hooks, and utilities
* for the Maestro web interface (both mobile and desktop web).
*/
// Components
export * from './components';

View File

@@ -15,5 +15,5 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/renderer"]
"include": ["src/renderer", "src/web", "src/shared"]
}