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:
42
packages/showcase/mobile/src/brand/BrandContext.tsx
Normal file
42
packages/showcase/mobile/src/brand/BrandContext.tsx
Normal 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>;
|
||||
}
|
||||
68
packages/showcase/mobile/src/brand/buildTheme.ts
Normal file
68
packages/showcase/mobile/src/brand/buildTheme.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
70
packages/showcase/mobile/src/components/BrandSwitcher.tsx
Normal file
70
packages/showcase/mobile/src/components/BrandSwitcher.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
21
packages/showcase/mobile/src/components/CodeLabel.tsx
Normal file
21
packages/showcase/mobile/src/components/CodeLabel.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
19
packages/showcase/mobile/src/components/DemoRow.tsx
Normal file
19
packages/showcase/mobile/src/components/DemoRow.tsx
Normal 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',
|
||||
},
|
||||
});
|
||||
45
packages/showcase/mobile/src/components/DemoSection.tsx
Normal file
45
packages/showcase/mobile/src/components/DemoSection.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
22
packages/showcase/mobile/src/components/ScreenContainer.tsx
Normal file
22
packages/showcase/mobile/src/components/ScreenContainer.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
48
packages/showcase/mobile/src/data/sampleData.ts
Normal file
48
packages/showcase/mobile/src/data/sampleData.ts
Normal 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: () => {} },
|
||||
];
|
||||
67
packages/showcase/mobile/src/navigation/RootNavigator.tsx
Normal file
67
packages/showcase/mobile/src/navigation/RootNavigator.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
21
packages/showcase/mobile/src/navigation/navigationTheme.ts
Normal file
21
packages/showcase/mobile/src/navigation/navigationTheme.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
9
packages/showcase/mobile/src/navigation/types.ts
Normal file
9
packages/showcase/mobile/src/navigation/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type RootStackParamList = {
|
||||
Home: undefined;
|
||||
Buttons: undefined;
|
||||
Cards: undefined;
|
||||
Forms: undefined;
|
||||
Feedback: undefined;
|
||||
Overlays: undefined;
|
||||
Theme: undefined;
|
||||
};
|
||||
178
packages/showcase/mobile/src/screens/ButtonsScreen.tsx
Normal file
178
packages/showcase/mobile/src/screens/ButtonsScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
180
packages/showcase/mobile/src/screens/CardsScreen.tsx
Normal file
180
packages/showcase/mobile/src/screens/CardsScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
139
packages/showcase/mobile/src/screens/FeedbackScreen.tsx
Normal file
139
packages/showcase/mobile/src/screens/FeedbackScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
290
packages/showcase/mobile/src/screens/FormsScreen.tsx
Normal file
290
packages/showcase/mobile/src/screens/FormsScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
160
packages/showcase/mobile/src/screens/HomeScreen.tsx
Normal file
160
packages/showcase/mobile/src/screens/HomeScreen.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
298
packages/showcase/mobile/src/screens/OverlaysScreen.tsx
Normal file
298
packages/showcase/mobile/src/screens/OverlaysScreen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
201
packages/showcase/mobile/src/screens/ThemeScreen.tsx
Normal file
201
packages/showcase/mobile/src/screens/ThemeScreen.tsx
Normal 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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user