Add desktop (Electron) and mobile (Expo) showcase apps

New workspace packages for demonstrating design system components:
- Desktop: Electron + Vite app with brand/theme switching
- Mobile: Expo React Native app with brand switching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-04 10:35:19 -08:00
parent 852a3af854
commit da84736b4b
100 changed files with 15874 additions and 326 deletions

View File

@@ -0,0 +1,42 @@
import React, { createContext, useContext, useState, useMemo, ReactNode } from 'react';
import { brands, themes } from '@dangerousthings/tokens';
import type { ThemeBrand, BrandTokens } from '@dangerousthings/tokens';
import { buildThemeFromBrand } from './buildTheme';
import type { DTExtendedTheme } from '@dangerousthings/react-native';
interface BrandContextValue {
brandId: ThemeBrand;
brand: BrandTokens;
theme: DTExtendedTheme;
setBrand: (id: ThemeBrand) => void;
availableBrands: typeof themes;
}
const BrandContext = createContext<BrandContextValue>(null!);
export function useBrand() {
return useContext(BrandContext);
}
interface BrandProviderProps {
children: ReactNode;
initialBrand?: ThemeBrand;
}
export function BrandProvider({ children, initialBrand = 'dt' }: BrandProviderProps) {
const [brandId, setBrandId] = useState<ThemeBrand>(initialBrand);
const value = useMemo(() => {
const brand = brands[brandId] as BrandTokens;
const theme = buildThemeFromBrand(brand);
return {
brandId,
brand,
theme,
setBrand: setBrandId,
availableBrands: themes,
};
}, [brandId]);
return <BrandContext.Provider value={value}>{children}</BrandContext.Provider>;
}

View File

@@ -0,0 +1,68 @@
import { MD3DarkTheme } from 'react-native-paper';
import type { BrandTokens } from '@dangerousthings/tokens';
import type { DTExtendedTheme } from '@dangerousthings/react-native';
export function buildThemeFromBrand(brand: BrandTokens): DTExtendedTheme {
const colors = brand.dark;
return {
...MD3DarkTheme,
dark: true,
roundness: brand.shape.radiusSm === '0' || brand.shape.radiusSm === '0px' ? 0 : 4,
fonts: MD3DarkTheme.fonts,
colors: {
...MD3DarkTheme.colors,
primary: colors.primary,
onPrimary: colors.bg,
primaryContainer: colors.primaryDim,
onPrimaryContainer: colors.bg,
secondary: colors.secondary,
onSecondary: colors.bg,
secondaryContainer: colors.secondary,
onSecondaryContainer: colors.bg,
tertiary: colors.other,
onTertiary: colors.bg,
tertiaryContainer: colors.other,
onTertiaryContainer: colors.textPrimary,
error: colors.error,
onError: colors.bg,
errorContainer: colors.error,
onErrorContainer: colors.textPrimary,
background: colors.bg,
onBackground: colors.textPrimary,
surface: colors.surface,
onSurface: colors.textPrimary,
surfaceVariant: colors.surface,
onSurfaceVariant: colors.textPrimary,
outline: colors.primary,
outlineVariant: colors.border,
inverseSurface: colors.textPrimary,
inverseOnSurface: colors.bg,
inversePrimary: colors.primaryDim,
shadow: colors.bg,
scrim: 'rgba(0,0,0,0.7)',
elevation: {
level0: 'transparent',
level1: colors.surface,
level2: colors.surfaceHover,
level3: colors.surfaceHover,
level4: colors.surfaceHover,
level5: colors.surfaceHover,
},
surfaceDisabled: 'rgba(255,255,255,0.1)',
onSurfaceDisabled: 'rgba(255,255,255,0.3)',
backdrop: 'rgba(0,0,0,0.7)',
},
custom: {
success: colors.success,
warning: colors.warning,
modeNormal: colors.primary,
modeEmphasis: colors.secondary,
modeWarning: colors.error,
modeSuccess: colors.success,
modeOther: colors.other,
border: colors.border,
borderEmphasis: colors.secondary,
},
};
}