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,
},
};
}

View File

@@ -0,0 +1,70 @@
import React from 'react';
import { View, Pressable, StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
import { useBrand } from '../brand/BrandContext';
import type { ThemeBrand } from '@dangerousthings/tokens';
const brandLabels: Record<ThemeBrand, string> = {
dt: 'DT',
classic: 'CLASSIC',
};
const brandColors: Record<ThemeBrand, string> = {
dt: '#00FFFF',
classic: '#FF00FF',
};
export function BrandSwitcher() {
const { brandId, setBrand, availableBrands } = useBrand();
return (
<View style={styles.container}>
{availableBrands.map((def) => {
const isActive = def.id === brandId;
const color = brandColors[def.id];
return (
<Pressable
key={def.id}
onPress={() => setBrand(def.id)}
style={[
styles.chip,
{
borderColor: color,
backgroundColor: isActive ? color : 'transparent',
},
]}
>
<Text
style={[
styles.chipText,
{ color: isActive ? '#000000' : color },
]}
>
{brandLabels[def.id]}
</Text>
</Pressable>
);
})}
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'center',
gap: 12,
marginVertical: 16,
},
chip: {
paddingHorizontal: 16,
paddingVertical: 8,
borderWidth: 2,
},
chipText: {
fontSize: 12,
fontWeight: '700',
letterSpacing: 1,
},
});

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
interface CodeLabelProps {
text: string;
}
export function CodeLabel({ text }: CodeLabelProps) {
return <Text style={styles.code}>{text}</Text>;
}
const styles = StyleSheet.create({
code: {
color: 'rgba(255, 255, 255, 0.35)',
fontSize: 10,
fontFamily: 'monospace',
marginTop: 4,
marginBottom: 4,
},
});

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
interface DemoRowProps {
children: React.ReactNode;
}
export function DemoRow({ children }: DemoRowProps) {
return <View style={styles.row}>{children}</View>;
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 12,
alignItems: 'center',
},
});

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
import { DTLabel } from '@dangerousthings/react-native';
import type { DTVariant } from '@dangerousthings/react-native';
interface DemoSectionProps {
title: string;
description?: string;
variant?: DTVariant;
children: React.ReactNode;
}
export function DemoSection({ title, description, variant = 'normal', children }: DemoSectionProps) {
return (
<View style={styles.container}>
<DTLabel
primaryText={title}
mode={variant}
size="medium"
animated={false}
showIndicator={false}
/>
{description ? (
<Text variant="bodySmall" style={styles.description}>
{description}
</Text>
) : null}
<View style={styles.content}>{children}</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginBottom: 32,
},
description: {
color: 'rgba(255,255,255,0.6)',
marginTop: 8,
},
content: {
marginTop: 16,
},
});

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { ScrollView, StyleProp, ViewStyle } from 'react-native';
import { useDTTheme } from '@dangerousthings/react-native';
interface ScreenContainerProps {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
}
export function ScreenContainer({ children, style }: ScreenContainerProps) {
const theme = useDTTheme();
return (
<ScrollView
style={{ flex: 1, backgroundColor: theme.colors.background }}
contentContainerStyle={[{ padding: 24, paddingBottom: 48 }, style]}
showsVerticalScrollIndicator={false}
>
{children}
</ScrollView>
);
}

View File

@@ -0,0 +1,48 @@
export const galleryItems = [
{ id: '1', uri: 'https://picsum.photos/seed/dt-gallery1/800/800', alt: 'Sample image 1' },
{ id: '2', uri: 'https://picsum.photos/seed/dt-gallery2/800/800', alt: 'Sample image 2' },
{ id: '3', uri: 'https://picsum.photos/seed/dt-gallery3/800/800', alt: 'Sample image 3' },
{ id: '4', uri: 'https://picsum.photos/seed/dt-gallery4/800/800', alt: 'Sample image 4' },
{ id: '5', uri: 'https://picsum.photos/seed/dt-gallery5/800/800', alt: 'Sample image 5' },
];
export const menuItems = [
{
id: 'home',
title: 'Home',
isActive: true,
onPress: () => {},
},
{
id: 'products',
title: 'Products',
items: [
{ id: 'nfc', title: 'NFC Implants', onPress: () => {} },
{ id: 'rfid', title: 'RFID Implants', onPress: () => {} },
{
id: 'accessories',
title: 'Accessories',
items: [
{ id: 'readers', title: 'Readers', onPress: () => {} },
{ id: 'tools', title: 'Tools', onPress: () => {} },
],
},
],
},
{ id: 'about', title: 'About', onPress: () => {} },
{ id: 'contact', title: 'Contact', onPress: () => {} },
];
export const horizontalMenuItems = [
{ id: 'h1', title: 'Home', onPress: () => {}, isActive: true },
{ id: 'h2', title: 'Shop', onPress: () => {} },
{ id: 'h3', title: 'Blog', onPress: () => {} },
{ id: 'h4', title: 'FAQ', onPress: () => {} },
];
export const dropdownItems = [
{ id: 'd1', title: 'Edit', onPress: () => {} },
{ id: 'd2', title: 'Duplicate', onPress: () => {} },
{ id: 'd3', title: 'Archive', onPress: () => {} },
{ id: 'd4', title: 'Delete', onPress: () => {} },
];

View File

@@ -0,0 +1,67 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useDTTheme } from '@dangerousthings/react-native';
import type { RootStackParamList } from './types';
import { HomeScreen } from '../screens/HomeScreen';
import { ButtonsScreen } from '../screens/ButtonsScreen';
import { CardsScreen } from '../screens/CardsScreen';
import { FormsScreen } from '../screens/FormsScreen';
import { FeedbackScreen } from '../screens/FeedbackScreen';
import { OverlaysScreen } from '../screens/OverlaysScreen';
import { ThemeScreen } from '../screens/ThemeScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
export function RootNavigator() {
const theme = useDTTheme();
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: theme.colors.background },
headerTintColor: theme.custom.modeNormal,
headerTitleStyle: { fontWeight: '600' },
contentStyle: { backgroundColor: theme.colors.background },
animation: 'slide_from_right',
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Buttons"
component={ButtonsScreen}
options={{ title: 'BUTTONS & ACTIONS' }}
/>
<Stack.Screen
name="Cards"
component={CardsScreen}
options={{ title: 'CARDS & LABELS' }}
/>
<Stack.Screen
name="Forms"
component={FormsScreen}
options={{ title: 'FORM CONTROLS' }}
/>
<Stack.Screen
name="Feedback"
component={FeedbackScreen}
options={{ title: 'PROGRESS & FEEDBACK' }}
/>
<Stack.Screen
name="Overlays"
component={OverlaysScreen}
options={{ title: 'OVERLAYS & NAV' }}
/>
<Stack.Screen
name="Theme"
component={ThemeScreen}
options={{ title: 'THEME REFERENCE' }}
/>
</Stack.Navigator>
);
}

View File

@@ -0,0 +1,21 @@
import { DefaultTheme } from '@react-navigation/native';
import { useBrand } from '../brand/BrandContext';
export function useNavigationTheme() {
const { brand } = useBrand();
const colors = brand.dark;
return {
...DefaultTheme,
dark: true,
colors: {
...DefaultTheme.colors,
primary: colors.primary,
background: colors.bg,
card: colors.bg,
text: colors.textPrimary,
border: colors.primary,
notification: colors.secondary,
},
};
}

View File

@@ -0,0 +1,9 @@
export type RootStackParamList = {
Home: undefined;
Buttons: undefined;
Cards: undefined;
Forms: undefined;
Feedback: undefined;
Overlays: undefined;
Theme: undefined;
};

View File

@@ -0,0 +1,178 @@
import React from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import { DTButton, DTChip, DTHexagon, useDTTheme } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { DemoRow } from '../components/DemoRow';
import { CodeLabel } from '../components/CodeLabel';
export function ButtonsScreen() {
const theme = useDTTheme();
return (
<ScreenContainer>
{/* DTButton - Outlined */}
<DemoSection
title="DTButton - Outlined"
variant="normal"
description="Border with transparent background. Default mode."
>
<View style={{ gap: 12 }}>
<DTButton variant="normal" onPress={() => {}}>NORMAL</DTButton>
<CodeLabel text='variant="normal"' />
<DTButton variant="emphasis" onPress={() => {}}>EMPHASIS</DTButton>
<CodeLabel text='variant="emphasis"' />
<DTButton variant="warning" onPress={() => {}}>WARNING</DTButton>
<CodeLabel text='variant="warning"' />
<DTButton variant="success" onPress={() => {}}>SUCCESS</DTButton>
<CodeLabel text='variant="success"' />
<DTButton variant="other" onPress={() => {}}>OTHER</DTButton>
<CodeLabel text='variant="other"' />
</View>
</DemoSection>
{/* DTButton - Contained */}
<DemoSection
title="DTButton - Contained"
variant="emphasis"
description="Filled background with dark text."
>
<View style={{ gap: 12 }}>
<DTButton variant="normal" mode="contained" onPress={() => {}}>
CONTAINED NORMAL
</DTButton>
<DTButton variant="emphasis" mode="contained" onPress={() => {}}>
CONTAINED EMPHASIS
</DTButton>
<DTButton variant="warning" mode="contained" onPress={() => {}}>
CONTAINED WARNING
</DTButton>
<DTButton variant="success" mode="contained" onPress={() => {}}>
CONTAINED SUCCESS
</DTButton>
<DTButton variant="other" mode="contained" onPress={() => {}}>
CONTAINED OTHER
</DTButton>
</View>
</DemoSection>
{/* DTButton - States */}
<DemoSection
title="DTButton - States"
variant="warning"
description="Disabled and custom bevel sizes."
>
<View style={{ gap: 12 }}>
<DTButton variant="normal" disabled onPress={() => {}}>
DISABLED
</DTButton>
<CodeLabel text="disabled={true}" />
<DTButton variant="emphasis" bevelSize={16} onPress={() => {}}>
LARGE BEVEL (16px)
</DTButton>
<CodeLabel text="bevelSize={16}" />
<DTButton variant="success" bevelSize={0} onPress={() => {}}>
NO BEVEL (0px)
</DTButton>
<CodeLabel text="bevelSize={0}" />
<DTButton variant="other" borderWidth={4} onPress={() => {}}>
THICK BORDER (4px)
</DTButton>
<CodeLabel text="borderWidth={4}" />
</View>
</DemoSection>
{/* DTChip */}
<DemoSection
title="DTChip"
variant="normal"
description="Chips for tags and statuses. Wraps RNP Chip."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
ALL VARIANTS:
</Text>
<DemoRow>
<DTChip variant="normal">NTAG215</DTChip>
<DTChip variant="emphasis">FEATURED</DTChip>
<DTChip variant="warning">DEPRECATED</DTChip>
<DTChip variant="success">ACTIVE</DTChip>
<DTChip variant="other">BETA</DTChip>
</DemoRow>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 16, marginBottom: 8 }}>
SELECTED:
</Text>
<DemoRow>
<DTChip variant="normal" selected>SELECTED</DTChip>
<DTChip variant="emphasis" selected>SELECTED</DTChip>
<DTChip variant="success" selected>SELECTED</DTChip>
</DemoRow>
</DemoSection>
{/* DTHexagon */}
<DemoSection
title="DTHexagon"
variant="other"
description="Decorative hexagon shapes with optional animations."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
FILLED:
</Text>
<DemoRow>
<DTHexagon size={60} variant="normal" />
<DTHexagon size={60} variant="emphasis" />
<DTHexagon size={60} variant="warning" />
<DTHexagon size={60} variant="success" />
<DTHexagon size={60} variant="other" />
</DemoRow>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 20, marginBottom: 8 }}>
OUTLINE:
</Text>
<DemoRow>
<DTHexagon size={60} variant="normal" filled={false} />
<DTHexagon size={60} variant="emphasis" filled={false} borderWidth={3} />
<DTHexagon size={60} variant="warning" filled={false} />
</DemoRow>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 20, marginBottom: 8 }}>
ANIMATED:
</Text>
<DemoRow>
<View style={{ alignItems: 'center' }}>
<DTHexagon size={50} variant="normal" animated animationType="rotate" animationDuration={3000} />
<CodeLabel text="rotate" />
</View>
<View style={{ alignItems: 'center' }}>
<DTHexagon size={50} variant="emphasis" animated animationType="pulse" animationDuration={2000} />
<CodeLabel text="pulse" />
</View>
<View style={{ alignItems: 'center' }}>
<DTHexagon size={50} variant="other" filled={false} animated animationType="rotate" animationDuration={4000} />
<CodeLabel text="rotate outline" />
</View>
</DemoRow>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 20, marginBottom: 8 }}>
WITH CONTENT:
</Text>
<DemoRow>
<DTHexagon size={80} variant="normal">
<Text style={{ color: theme.colors.background, fontWeight: '700', fontSize: 16 }}>DT</Text>
</DTHexagon>
<DTHexagon size={80} variant="emphasis" animated animationType="pulse">
<Text style={{ color: theme.colors.background, fontWeight: '700', fontSize: 12 }}>SCAN</Text>
</DTHexagon>
</DemoRow>
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,180 @@
import React from 'react';
import { View, Image, Alert } from 'react-native';
import { Text } from 'react-native-paper';
import { DTCard, DTLabel, DTMediaFrame, useDTTheme } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
export function CardsScreen() {
const theme = useDTTheme();
return (
<ScreenContainer>
{/* DTCard - All Modes */}
<DemoSection
title="DTCard - Modes"
variant="normal"
description="Beveled card with dual bottom bevels. Uses DTVariant for mode coloring."
>
<DTCard mode="normal" title="NORMAL MODE" style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Primary border with beveled bottom-right and bottom-left corners.
</Text>
</DTCard>
<DTCard mode="emphasis" title="EMPHASIS MODE" style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Secondary border. Used for highlighted or important content.
</Text>
</DTCard>
<DTCard mode="warning" title="WARNING MODE" style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Error border. Used for errors and destructive actions.
</Text>
</DTCard>
<DTCard mode="success" title="SUCCESS MODE" style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Success border. Used for confirmations and success states.
</Text>
</DTCard>
<DTCard mode="other" title="OTHER MODE">
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Tertiary border. Used for secondary or miscellaneous info.
</Text>
</DTCard>
</DemoSection>
{/* DTCard - Variations */}
<DemoSection
title="DTCard - Variations"
variant="emphasis"
description="Cards without titles, pressable cards, custom bevels."
>
<DTCard mode="normal" style={{ marginBottom: 16 }}>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Card without title (no header bar).
</Text>
</DTCard>
<DTCard
mode="emphasis"
title="PRESSABLE CARD"
onPress={() => Alert.alert('Card Pressed', 'You tapped the card!')}
style={{ marginBottom: 16 }}
>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
Tap me! Cards with onPress get press feedback.
</Text>
</DTCard>
<DTCard
mode="other"
title="CUSTOM BEVELS"
bevelSize={48}
bevelSizeSmall={24}
borderWidth={4}
>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface }}>
bevelSize=48, bevelSizeSmall=24, borderWidth=4
</Text>
</DTCard>
</DemoSection>
{/* DTLabel - Sizes and Modes */}
<DemoSection
title="DTLabel"
variant="normal"
description="Beveled top-right corner labels with animated ping indicator."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
SIZES:
</Text>
<View style={{ gap: 8, marginBottom: 16 }}>
<DTLabel primaryText="Small Label" size="small" mode="normal" />
<DTLabel primaryText="Medium Label" size="medium" mode="normal" />
<DTLabel primaryText="Large Label" size="large" mode="normal" />
</View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
WITH SECONDARY TEXT:
</Text>
<View style={{ gap: 8, marginBottom: 16 }}>
<DTLabel primaryText="Detected" secondaryText="NTAG215" mode="success" />
<DTLabel primaryText="Status" secondaryText="READY TO SCAN" mode="emphasis" />
<DTLabel primaryText="Error" secondaryText="CONNECTION LOST" mode="warning" />
</View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
ALL VARIANTS:
</Text>
<View style={{ gap: 8, marginBottom: 16 }}>
<DTLabel primaryText="Normal" mode="normal" />
<DTLabel primaryText="Emphasis" mode="emphasis" />
<DTLabel primaryText="Warning" mode="warning" />
<DTLabel primaryText="Success" mode="success" />
<DTLabel primaryText="Other" mode="other" />
</View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
FULL WIDTH:
</Text>
<View style={{ gap: 8, marginBottom: 16 }}>
<DTLabel primaryText="Full Width Label" secondaryText="STRETCHES" mode="emphasis" fullWidth />
</View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
OPTIONS:
</Text>
<View style={{ gap: 8 }}>
<DTLabel primaryText="No Ping Indicator" mode="normal" showIndicator={false} />
<DTLabel primaryText="Not Animated" mode="other" animated={false} />
</View>
</DemoSection>
{/* DTMediaFrame */}
<DemoSection
title="DTMediaFrame"
variant="other"
description="Beveled media wrapper with diagonal symmetry (top-left + bottom-right)."
>
<DTMediaFrame variant="normal" aspectRatio={16 / 9} style={{ marginBottom: 8 }}>
<Image
source={{ uri: 'https://picsum.photos/seed/dt-media1/800/450' }}
style={{ flex: 1 }}
resizeMode="cover"
/>
</DTMediaFrame>
<CodeLabel text='variant="normal" aspectRatio={16/9}' />
<DTMediaFrame variant="emphasis" aspectRatio={1} bevelSize={24} style={{ marginTop: 16, marginBottom: 8 }}>
<Image
source={{ uri: 'https://picsum.photos/seed/dt-media2/400/400' }}
style={{ flex: 1 }}
resizeMode="cover"
/>
</DTMediaFrame>
<CodeLabel text='variant="emphasis" aspectRatio={1} bevelSize={24}' />
<DTMediaFrame variant="warning" aspectRatio={4 / 3} borderWidth={4} style={{ marginTop: 16, marginBottom: 8 }}>
<Image
source={{ uri: 'https://picsum.photos/seed/dt-media3/600/450' }}
style={{ flex: 1 }}
resizeMode="cover"
/>
</DTMediaFrame>
<CodeLabel text='variant="warning" aspectRatio={4/3} borderWidth={4}' />
<DTMediaFrame variant="success" aspectRatio={16 / 9} animated={false} style={{ marginTop: 16, marginBottom: 8 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0a0a0a' }}>
<Text style={{ color: theme.custom.modeSuccess }}>PLACEHOLDER CONTENT</Text>
</View>
</DTMediaFrame>
<CodeLabel text='animated={false} — custom View children' />
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,139 @@
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import { DTProgressBar, DTSearchInput, useDTTheme } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
export function FeedbackScreen() {
const theme = useDTTheme();
const [progress, setProgress] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
const [loadingQuery, setLoadingQuery] = useState('NFC implants');
// Animate progress bar
useEffect(() => {
const interval = setInterval(() => {
setProgress((prev) => (prev >= 1 ? 0 : prev + 0.01));
}, 100);
return () => clearInterval(interval);
}, []);
return (
<ScreenContainer>
{/* DTProgressBar - Horizontal */}
<DemoSection
title="DTProgressBar - Horizontal"
variant="normal"
description="Wraps RNP ProgressBar with angular styling."
>
<View style={{ gap: 20 }}>
<View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
25%
</Text>
<DTProgressBar value={0.25} variant="normal" />
</View>
<View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
50% WITH LABEL
</Text>
<DTProgressBar value={0.5} variant="emphasis" showLabel height={8} />
<CodeLabel text='showLabel height={8}' />
</View>
<View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
75%
</Text>
<DTProgressBar value={0.75} variant="success" height={6} />
</View>
<View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
100% (WARNING)
</Text>
<DTProgressBar value={1.0} variant="warning" showLabel />
</View>
<View>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
ANIMATED ({Math.round(progress * 100)}%)
</Text>
<DTProgressBar value={progress} variant="normal" showLabel height={8} />
<CodeLabel text="Live cycling 0% → 100% → repeat" />
</View>
</View>
</DemoSection>
{/* DTProgressBar - Vertical */}
<DemoSection
title="DTProgressBar - Vertical"
variant="emphasis"
description="Vertical orientation with height-based fill."
>
<View style={{ flexDirection: 'row', justifyContent: 'space-around', height: 150, marginBottom: 8 }}>
<DTProgressBar value={0.3} variant="normal" orientation="vertical" height={8} showLabel />
<DTProgressBar value={0.5} variant="emphasis" orientation="vertical" height={8} showLabel />
<DTProgressBar value={0.75} variant="success" orientation="vertical" height={8} showLabel />
<DTProgressBar value={1.0} variant="warning" orientation="vertical" height={8} showLabel />
<DTProgressBar value={0.15} variant="other" orientation="vertical" height={12} showLabel />
</View>
<CodeLabel text='orientation="vertical"' />
</DemoSection>
{/* DTSearchInput */}
<DemoSection
title="DTSearchInput"
variant="normal"
description="Wraps RNP Searchbar with angular styling."
>
<View style={{ gap: 16 }}>
<View>
<DTSearchInput
value={searchQuery}
onChangeText={setSearchQuery}
placeholder="Search components..."
variant="normal"
/>
<CodeLabel text='variant="normal"' />
</View>
<View>
<DTSearchInput
value={loadingQuery}
onChangeText={setLoadingQuery}
loading
variant="emphasis"
placeholder="Searching..."
/>
<CodeLabel text='loading={true} variant="emphasis"' />
</View>
<View>
<DTSearchInput
value=""
onChangeText={() => {}}
variant="warning"
placeholder="Disabled search"
disabled
/>
<CodeLabel text='disabled={true} variant="warning"' />
</View>
<View>
<DTSearchInput
value=""
onChangeText={() => {}}
variant="other"
placeholder="Other variant..."
/>
<CodeLabel text='variant="other"' />
</View>
</View>
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,290 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import {
DTTextInput,
DTCheckbox,
DTSwitch,
DTRadioGroup,
DTRadioOption,
DTQuantityStepper,
useDTTheme,
} from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
export function FormsScreen() {
const theme = useDTTheme();
// TextInput state
const [username, setUsername] = useState('');
const [search, setSearch] = useState('');
const [errorField, setErrorField] = useState('');
// Checkbox state
const [check1, setCheck1] = useState(false);
const [check2, setCheck2] = useState(true);
const [check3, setCheck3] = useState(false);
// Switch state
const [switch1, setSwitch1] = useState(false);
const [switch2, setSwitch2] = useState(true);
const [switch3, setSwitch3] = useState(false);
// Radio state
const [radio1, setRadio1] = useState<string | null>('nfc');
const [radio2, setRadio2] = useState<string | null>(null);
// Stepper state
const [qty1, setQty1] = useState(1);
const [qty2, setQty2] = useState(5);
const [qty3, setQty3] = useState(0);
return (
<ScreenContainer>
{/* DTTextInput */}
<DemoSection
title="DTTextInput"
variant="normal"
description="Angular text input wrapping RNP TextInput. Focus glow bar animation."
>
<View style={{ gap: 16 }}>
<View>
<DTTextInput
variant="normal"
label="Username"
value={username}
onChangeText={setUsername}
placeholder="Enter username"
/>
<CodeLabel text='variant="normal"' />
</View>
<View>
<DTTextInput
variant="emphasis"
label="Search Query"
value={search}
onChangeText={setSearch}
placeholder="Search..."
/>
<CodeLabel text='variant="emphasis"' />
</View>
<View>
<DTTextInput
variant="warning"
error
errorMessage="This field is required"
label="Email"
value={errorField}
onChangeText={setErrorField}
placeholder="user@example.com"
/>
<CodeLabel text='error={true} errorMessage="..."' />
</View>
<View>
<DTTextInput
variant="success"
label="Verified Field"
value="verified@example.com"
onChangeText={() => {}}
/>
<CodeLabel text='variant="success"' />
</View>
</View>
</DemoSection>
{/* DTCheckbox */}
<DemoSection
title="DTCheckbox"
variant="normal"
description="Angular square checkbox with SVG checkmark. Custom-built (RNP uses rounded)."
>
<View style={{ gap: 4 }}>
<DTCheckbox
checked={check1}
onPress={() => setCheck1(!check1)}
label="Accept terms and conditions"
variant="normal"
/>
<DTCheckbox
checked={check2}
onPress={() => setCheck2(!check2)}
label="Remember me"
variant="success"
/>
<DTCheckbox
checked={check3}
onPress={() => setCheck3(!check3)}
label="Enable notifications"
variant="emphasis"
/>
<DTCheckbox
checked={true}
onPress={() => {}}
label="Disabled checked"
variant="normal"
disabled
/>
<DTCheckbox
checked={false}
onPress={() => {}}
label="Disabled unchecked"
variant="normal"
disabled
/>
<DTCheckbox
checked={true}
onPress={() => {}}
label="Large checkbox (size=32)"
variant="warning"
size={32}
/>
</View>
</DemoSection>
{/* DTSwitch */}
<DemoSection
title="DTSwitch"
variant="normal"
description="Angular toggle switch. Custom-built with animated thumb."
>
<View style={{ gap: 4 }}>
<DTSwitch
value={switch1}
onValueChange={setSwitch1}
label="Enable NFC"
variant="normal"
/>
<DTSwitch
value={switch2}
onValueChange={setSwitch2}
label="Auto-detect"
variant="success"
/>
<DTSwitch
value={switch3}
onValueChange={setSwitch3}
label="Dark mode"
variant="emphasis"
/>
<DTSwitch
value={true}
onValueChange={() => {}}
label="Disabled (on)"
variant="normal"
disabled
/>
<DTSwitch
value={false}
onValueChange={() => {}}
label="Disabled (off)"
variant="normal"
disabled
/>
</View>
</DemoSection>
{/* DTRadioGroup */}
<DemoSection
title="DTRadioGroup"
variant="normal"
description="Angular square radio indicators with selection highlight."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
BASIC:
</Text>
<DTRadioGroup value={radio1} onValueChange={setRadio1} variant="normal">
<DTRadioOption value="nfc" label="NFC (Near Field Communication)" />
<DTRadioOption value="rfid" label="RFID (Radio Frequency ID)" />
<DTRadioOption value="ble" label="BLE (Bluetooth Low Energy)" />
</DTRadioGroup>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 24, marginBottom: 8 }}>
WITH DESCRIPTIONS:
</Text>
<DTRadioGroup value={radio2} onValueChange={setRadio2} variant="emphasis">
<DTRadioOption
value="xNT"
label="xNT"
description="NTAG216 implant, 888 bytes, NFC Type 2"
/>
<DTRadioOption
value="xDF2"
label="xDF2"
description="DESFire EV2 implant, 8K bytes, NFC Type 4"
/>
<DTRadioOption
value="xEM"
label="xEM"
description="T5577 implant, LF 125kHz"
disabled
/>
</DTRadioGroup>
</DemoSection>
{/* DTQuantityStepper */}
<DemoSection
title="DTQuantityStepper"
variant="emphasis"
description="Increment/decrement with beveled +/- buttons."
>
<View style={{ gap: 24 }}>
<View>
<DTQuantityStepper
value={qty1}
onValueChange={setQty1}
label="Quantity"
min={1}
max={10}
variant="normal"
size="medium"
/>
<CodeLabel text='size="medium" min={1} max={10}' />
</View>
<View>
<DTQuantityStepper
value={qty2}
onValueChange={setQty2}
label="Small Stepper"
min={0}
max={99}
variant="emphasis"
size="small"
/>
<CodeLabel text='size="small" min={0} max={99}' />
</View>
<View>
<DTQuantityStepper
value={qty3}
onValueChange={setQty3}
label="Large Stepper"
min={0}
max={20}
step={5}
variant="success"
size="large"
/>
<CodeLabel text='size="large" step={5}' />
</View>
<View>
<DTQuantityStepper
value={3}
onValueChange={() => {}}
label="Disabled"
variant="normal"
disabled
/>
<CodeLabel text="disabled={true}" />
</View>
</View>
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,160 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { DTCard, DTHexagon, useDTTheme } from '@dangerousthings/react-native';
import type { DTVariant } from '@dangerousthings/react-native';
import type { RootStackParamList } from '../navigation/types';
import { ScreenContainer } from '../components/ScreenContainer';
import { BrandSwitcher } from '../components/BrandSwitcher';
import { useBrand } from '../brand/BrandContext';
type HomeScreenProps = {
navigation: NativeStackNavigationProp<RootStackParamList, 'Home'>;
};
const categories: {
title: string;
subtitle: string;
mode: DTVariant;
route: keyof RootStackParamList;
count: number;
}[] = [
{
title: 'BUTTONS & ACTIONS',
subtitle: 'DTButton, DTChip, DTHexagon',
mode: 'normal',
route: 'Buttons',
count: 3,
},
{
title: 'CARDS & LABELS',
subtitle: 'DTCard, DTLabel, DTMediaFrame',
mode: 'emphasis',
route: 'Cards',
count: 3,
},
{
title: 'FORM CONTROLS',
subtitle: 'DTTextInput, DTCheckbox, DTSwitch, DTRadioGroup, DTQuantityStepper',
mode: 'success',
route: 'Forms',
count: 5,
},
{
title: 'PROGRESS & FEEDBACK',
subtitle: 'DTProgressBar, DTSearchInput',
mode: 'other',
route: 'Feedback',
count: 2,
},
{
title: 'OVERLAYS & NAVIGATION',
subtitle: 'DTModal, DTDrawer, DTAccordion, DTMenu, DTGallery',
mode: 'warning',
route: 'Overlays',
count: 5,
},
{
title: 'THEME REFERENCE',
subtitle: 'Colors, Typography, Spacing',
mode: 'normal',
route: 'Theme',
count: 0,
},
];
export function HomeScreen({ navigation }: HomeScreenProps) {
const insets = useSafeAreaInsets();
const theme = useDTTheme();
const { brand } = useBrand();
return (
<ScreenContainer style={{ paddingTop: insets.top + 24 }}>
<View style={styles.header}>
<Text variant="displaySmall" style={[styles.title, { color: theme.custom.modeNormal }]}>
DANGEROUS THINGS
</Text>
<Text variant="headlineSmall" style={[styles.subtitle, { color: theme.custom.modeEmphasis }]}>
DESIGN SYSTEM
</Text>
<Text variant="bodySmall" style={styles.version}>
{brand.name} theme
</Text>
</View>
<BrandSwitcher />
<View style={styles.hexRow}>
<DTHexagon size={18} variant="normal" opacity={0.4} />
<DTHexagon size={18} variant="emphasis" opacity={0.6} />
<DTHexagon size={18} variant="warning" opacity={0.4} />
<DTHexagon size={18} variant="success" opacity={0.6} />
<DTHexagon size={18} variant="other" opacity={0.4} />
</View>
<Text variant="labelSmall" style={styles.sectionLabel}>
COMPONENT CATALOG
</Text>
{categories.map((cat) => (
<DTCard
key={cat.route}
mode={cat.mode}
title={cat.title}
onPress={() => navigation.navigate(cat.route)}
style={styles.card}
>
<Text variant="bodySmall" style={styles.cardSubtitle}>
{cat.subtitle}
</Text>
{cat.count > 0 && (
<Text variant="labelSmall" style={styles.cardCount}>
{cat.count} components
</Text>
)}
</DTCard>
))}
</ScreenContainer>
);
}
const styles = StyleSheet.create({
header: {
alignItems: 'center',
marginBottom: 8,
},
title: {
letterSpacing: 2,
},
subtitle: {
letterSpacing: 3,
marginTop: 4,
},
version: {
color: 'rgba(255,255,255,0.4)',
marginTop: 8,
},
hexRow: {
flexDirection: 'row',
justifyContent: 'center',
gap: 10,
marginVertical: 24,
},
sectionLabel: {
color: 'rgba(255,255,255,0.5)',
letterSpacing: 2,
marginBottom: 16,
},
card: {
marginBottom: 16,
},
cardSubtitle: {
color: 'rgba(255,255,255,0.7)',
},
cardCount: {
color: 'rgba(255,255,255,0.4)',
marginTop: 8,
},
});

View File

@@ -0,0 +1,298 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import {
DTButton,
DTCard,
DTChip,
DTModal,
DTDrawer,
DTAccordion,
DTMenu,
DTMenuDropdown,
DTGallery,
useDTTheme,
} from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { CodeLabel } from '../components/CodeLabel';
import { galleryItems, menuItems, horizontalMenuItems, dropdownItems } from '../data/sampleData';
export function OverlaysScreen() {
const theme = useDTTheme();
// Modal state
const [modalVisible, setModalVisible] = useState(false);
const [warningModalVisible, setWarningModalVisible] = useState(false);
// Drawer state
const [rightDrawer, setRightDrawer] = useState(false);
const [leftDrawer, setLeftDrawer] = useState(false);
// Menu dropdown state
const [menuDropdownVisible, setMenuDropdownVisible] = useState(false);
// Gallery state
const [galleryIndex, setGalleryIndex] = useState(0);
return (
<ScreenContainer>
{/* DTModal */}
<DemoSection
title="DTModal"
variant="normal"
description="Wraps RNP Portal + Modal. Content rendered inside DTCard."
>
<View style={{ gap: 12 }}>
<DTButton variant="normal" onPress={() => setModalVisible(true)}>
OPEN NORMAL MODAL
</DTButton>
<DTButton variant="warning" onPress={() => setWarningModalVisible(true)}>
OPEN WARNING MODAL
</DTButton>
</View>
<DTModal
visible={modalVisible}
onDismiss={() => setModalVisible(false)}
title="CONFIRM ACTION"
variant="normal"
>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface, marginBottom: 16 }}>
Are you sure you want to proceed with this action?
</Text>
<View style={{ flexDirection: 'row', gap: 12 }}>
<DTButton
variant="normal"
mode="contained"
onPress={() => setModalVisible(false)}
style={{ flex: 1 }}
>
CONFIRM
</DTButton>
<DTButton
variant="normal"
onPress={() => setModalVisible(false)}
style={{ flex: 1 }}
>
CANCEL
</DTButton>
</View>
</DTModal>
<DTModal
visible={warningModalVisible}
onDismiss={() => setWarningModalVisible(false)}
title="DELETE ITEM"
variant="warning"
>
<Text variant="bodyMedium" style={{ color: theme.colors.onSurface, marginBottom: 16 }}>
This action cannot be undone. All data will be permanently removed.
</Text>
<DTButton
variant="warning"
mode="contained"
onPress={() => setWarningModalVisible(false)}
>
DELETE
</DTButton>
</DTModal>
</DemoSection>
{/* DTDrawer */}
<DemoSection
title="DTDrawer"
variant="emphasis"
description="Sliding panel via Portal + Animated. Beveled leading edge."
>
<View style={{ gap: 12 }}>
<DTButton variant="emphasis" onPress={() => setRightDrawer(true)}>
OPEN RIGHT DRAWER
</DTButton>
<DTButton variant="normal" onPress={() => setLeftDrawer(true)}>
OPEN LEFT DRAWER
</DTButton>
</View>
<DTDrawer
visible={rightDrawer}
onDismiss={() => setRightDrawer(false)}
heading="CART"
headingVariant="emphasis"
position="right"
>
<DTCard mode="normal" title="ITEM 1" style={{ marginBottom: 12 }}>
<Text style={{ color: theme.colors.onSurface }}>xNT NFC Implant</Text>
</DTCard>
<DTCard mode="normal" title="ITEM 2" style={{ marginBottom: 12 }}>
<Text style={{ color: theme.colors.onSurface }}>xDF2 DESFire Implant</Text>
</DTCard>
<DTButton
variant="emphasis"
mode="contained"
onPress={() => setRightDrawer(false)}
>
CHECKOUT
</DTButton>
</DTDrawer>
<DTDrawer
visible={leftDrawer}
onDismiss={() => setLeftDrawer(false)}
heading="MENU"
headingVariant="normal"
position="left"
>
<View style={{ gap: 12 }}>
<DTButton variant="normal" onPress={() => setLeftDrawer(false)}>
HOME
</DTButton>
<DTButton variant="normal" onPress={() => setLeftDrawer(false)}>
PRODUCTS
</DTButton>
<DTButton variant="normal" onPress={() => setLeftDrawer(false)}>
ABOUT
</DTButton>
<DTButton variant="normal" onPress={() => setLeftDrawer(false)}>
CONTACT
</DTButton>
</View>
</DTDrawer>
</DemoSection>
{/* DTAccordion */}
<DemoSection
title="DTAccordion"
variant="normal"
description="Wraps RNP List.Accordion with angular headers and thick top border."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
SINGLE EXPAND:
</Text>
<DTAccordion
variant="normal"
activeVariant="emphasis"
sections={[
{
key: 'size',
title: 'Size',
children: (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
<DTChip variant="normal">Small</DTChip>
<DTChip variant="normal">Medium</DTChip>
<DTChip variant="normal">Large</DTChip>
</View>
),
},
{
key: 'type',
title: 'Chip Type',
children: (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
<DTChip variant="emphasis">NTAG</DTChip>
<DTChip variant="emphasis">DESFire</DTChip>
<DTChip variant="emphasis">MIFARE Classic</DTChip>
</View>
),
},
{
key: 'freq',
title: 'Frequency',
children: (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
<DTChip variant="success">13.56 MHz (HF)</DTChip>
<DTChip variant="success">125 kHz (LF)</DTChip>
</View>
),
},
]}
/>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 24, marginBottom: 8 }}>
MULTI EXPAND:
</Text>
<DTAccordion
variant="other"
activeVariant="warning"
allowMultiple
initialOpenKeys={['about']}
sections={[
{
key: 'about',
title: 'About',
children: (
<Text style={{ color: theme.colors.onSurface }}>
Dangerous Things makes NFC and RFID implants for human use.
</Text>
),
},
{
key: 'faq',
title: 'FAQ',
children: (
<Text style={{ color: theme.colors.onSurface }}>
Frequently asked questions about biohacking and body implants.
</Text>
),
},
]}
/>
</DemoSection>
{/* DTMenu */}
<DemoSection
title="DTMenu"
variant="normal"
description="Hierarchical menu with expandable sub-items. Thick top border accent."
>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginBottom: 8 }}>
VERTICAL (default):
</Text>
<DTMenu
variant="normal"
activeVariant="emphasis"
items={menuItems}
/>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 24, marginBottom: 8 }}>
HORIZONTAL:
</Text>
<DTMenu
variant="normal"
layout="horizontal"
items={horizontalMenuItems}
/>
<Text variant="labelSmall" style={{ color: theme.colors.onSurface, opacity: 0.6, marginTop: 24, marginBottom: 8 }}>
DROPDOWN (DTMenuDropdown):
</Text>
<DTMenuDropdown
visible={menuDropdownVisible}
onDismiss={() => setMenuDropdownVisible(false)}
variant="normal"
anchor={
<DTButton variant="normal" onPress={() => setMenuDropdownVisible(true)}>
OPEN DROPDOWN
</DTButton>
}
items={dropdownItems}
/>
</DemoSection>
{/* DTGallery */}
<DemoSection
title="DTGallery"
variant="normal"
description="Image gallery with thumbnail strip and arrow navigation."
>
<DTGallery
items={galleryItems}
activeIndex={galleryIndex}
onSelect={setGalleryIndex}
variant="normal"
thumbnailSize={64}
/>
</DemoSection>
</ScreenContainer>
);
}

View File

@@ -0,0 +1,201 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
import { useDTTheme } from '@dangerousthings/react-native';
import { ScreenContainer } from '../components/ScreenContainer';
import { DemoSection } from '../components/DemoSection';
import { BrandSwitcher } from '../components/BrandSwitcher';
import { useBrand } from '../brand/BrandContext';
const typographyScale = [
{ variant: 'displaySmall' as const, label: 'Display Small (36px)' },
{ variant: 'headlineMedium' as const, label: 'Headline Medium (28px)' },
{ variant: 'headlineSmall' as const, label: 'Headline Small (24px)' },
{ variant: 'titleLarge' as const, label: 'Title Large (22px)' },
{ variant: 'titleMedium' as const, label: 'Title Medium (16px)' },
{ variant: 'bodyLarge' as const, label: 'Body Large (16px)' },
{ variant: 'bodyMedium' as const, label: 'Body Medium (14px)' },
{ variant: 'bodySmall' as const, label: 'Body Small (12px)' },
{ variant: 'labelLarge' as const, label: 'Label Large (14px)' },
{ variant: 'labelSmall' as const, label: 'Label Small (11px)' },
];
export function ThemeScreen() {
const theme = useDTTheme();
const { brand } = useBrand();
const colors = brand.dark;
const colorSwatches = [
{ name: 'primary', hex: colors.primary, label: 'Primary interactive' },
{ name: 'secondary', hex: colors.secondary, label: 'Highlights, attention' },
{ name: 'error', hex: colors.error, label: 'Errors, destructive' },
{ name: 'success', hex: colors.success, label: 'Confirmations' },
{ name: 'other', hex: colors.other, label: 'Secondary, misc' },
];
const baseColors = [
{ name: 'bg', hex: colors.bg, label: 'Background', textColor: colors.textPrimary },
{ name: 'textPrimary', hex: colors.textPrimary, label: 'Text', textColor: colors.bg },
{ name: 'surface', hex: colors.surface, label: 'Surface', textColor: colors.textPrimary },
{ name: 'border', hex: colors.border, label: 'Border', textColor: colors.bg },
];
return (
<ScreenContainer>
{/* Brand Switcher */}
<DemoSection
title="Brand Selector"
variant="emphasis"
description="Switch between all three brand themes."
>
<BrandSwitcher />
<Text variant="bodySmall" style={{ color: 'rgba(255,255,255,0.5)', textAlign: 'center' }}>
Current: {brand.name} {brand.description}
</Text>
</DemoSection>
{/* Color Palette */}
<DemoSection
title="Color Palette"
variant="normal"
description={`The ${brand.name} color system. Semantic variants on dark background.`}
>
<Text variant="labelSmall" style={styles.subLabel}>MODE COLORS:</Text>
{colorSwatches.map((swatch) => (
<View key={swatch.name} style={styles.swatchRow}>
<View style={[styles.swatch, { backgroundColor: swatch.hex }]} />
<View style={styles.swatchInfo}>
<Text style={[styles.swatchName, { color: swatch.hex }]}>{swatch.name}</Text>
<Text style={styles.swatchHex}>{swatch.hex}</Text>
<Text style={styles.swatchDesc}>{swatch.label}</Text>
</View>
</View>
))}
<Text variant="labelSmall" style={[styles.subLabel, { marginTop: 20 }]}>BASE COLORS:</Text>
{baseColors.map((color) => (
<View key={color.name} style={styles.swatchRow}>
<View style={[styles.swatch, { backgroundColor: color.hex, borderWidth: 1, borderColor: '#333' }]} />
<View style={styles.swatchInfo}>
<Text style={[styles.swatchName, { color: theme.colors.onSurface }]}>{color.name}</Text>
<Text style={styles.swatchHex}>{color.hex}</Text>
<Text style={styles.swatchDesc}>{color.label}</Text>
</View>
</View>
))}
</DemoSection>
{/* Typography */}
<DemoSection
title="Typography Scale"
variant="emphasis"
description={`MD3 type scale. Font: ${brand.typography.heading}`}
>
<View style={{ gap: 8 }}>
{typographyScale.map((item) => (
<View key={item.variant} style={styles.typeRow}>
<Text variant={item.variant} style={{ color: theme.colors.onSurface }}>
{item.label}
</Text>
</View>
))}
</View>
</DemoSection>
{/* Shape Tokens */}
<DemoSection
title="Shape Tokens"
variant="success"
description="Bevel and radius values for the current brand."
>
<View style={styles.codeBlock}>
<Text style={[styles.codeText, { color: theme.custom.modeSuccess }]}>
{`bevelSm: ${brand.shape.bevelSm}\nbevelMd: ${brand.shape.bevelMd}\nbevelLg: ${brand.shape.bevelLg}\nradiusSm: ${brand.shape.radiusSm}\nradius: ${brand.shape.radius}\nradiusLg: ${brand.shape.radiusLg}`}
</Text>
</View>
</DemoSection>
{/* Spacing */}
<DemoSection
title="Spacing Reference"
variant="other"
description="Common spacing values used throughout the design system."
>
{[4, 8, 12, 16, 24, 32, 48].map((size) => (
<View key={size} style={styles.spacingRow}>
<View
style={[
styles.spacingBar,
{ width: size * 3, backgroundColor: theme.custom.modeOther },
]}
/>
<Text style={styles.spacingLabel}>{size}px</Text>
</View>
))}
</DemoSection>
</ScreenContainer>
);
}
const styles = StyleSheet.create({
subLabel: {
color: 'rgba(255,255,255,0.6)',
marginBottom: 12,
},
swatchRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
marginBottom: 12,
},
swatch: {
width: 40,
height: 40,
},
swatchInfo: {
flex: 1,
},
swatchName: {
fontWeight: '600',
fontSize: 14,
},
swatchHex: {
color: 'rgba(255,255,255,0.5)',
fontSize: 11,
fontFamily: 'monospace',
},
swatchDesc: {
color: 'rgba(255,255,255,0.35)',
fontSize: 11,
},
typeRow: {
paddingVertical: 4,
borderBottomWidth: 1,
borderBottomColor: 'rgba(255,255,255,0.05)',
},
codeBlock: {
backgroundColor: '#0a0a0a',
padding: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
codeText: {
fontFamily: 'monospace',
fontSize: 12,
lineHeight: 20,
},
spacingRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
marginBottom: 8,
},
spacingBar: {
height: 12,
},
spacingLabel: {
color: 'rgba(255,255,255,0.5)',
fontSize: 11,
fontFamily: 'monospace',
},
});