Make RN components fully theme-driven for multi-brand support
Refactor all 18 React Native components from static DTColors imports to dynamic useDTTheme() context, enabling proper visual differentiation when switching between DT and Classic brands. Key changes: - Replace static DTColors with theme context in all components - Add shape tokens (bevel/radius) to DTExtendedTheme interface - Conditionally render SVG bevels (DT) vs borderRadius (Classic) - Configure fonts per brand (Tektur for DT, system sans-serif for Classic) - Update buildThemeFromBrand to parse shape and typography tokens - Fix Metro config for monorepo singleton resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,34 @@ config.resolver.nodeModulesPaths = [
|
||||
path.resolve(monorepoRoot, 'node_modules'),
|
||||
];
|
||||
|
||||
// Force singleton packages to always resolve from the mobile project.
|
||||
// In monorepos, Metro can bundle two copies of react (one from root, one from
|
||||
// the project) causing "Cannot read property 'use' of null" crashes.
|
||||
const singletons = ['react', 'react-native', 'react-native-safe-area-context'];
|
||||
|
||||
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
// Check if this is a singleton or a deep import of one (e.g. react/jsx-runtime)
|
||||
const isSingleton = singletons.some(
|
||||
(s) => moduleName === s || moduleName.startsWith(s + '/')
|
||||
);
|
||||
|
||||
if (isSingleton) {
|
||||
// Resolve as if the import originated from the mobile project root
|
||||
const mobileContext = {
|
||||
...context,
|
||||
resolveRequest: undefined,
|
||||
originModulePath: path.join(projectRoot, 'package.json'),
|
||||
};
|
||||
return context.resolveRequest(mobileContext, moduleName, platform);
|
||||
}
|
||||
|
||||
return context.resolveRequest(
|
||||
{ ...context, resolveRequest: undefined },
|
||||
moduleName,
|
||||
platform,
|
||||
);
|
||||
};
|
||||
|
||||
// Block sibling packages' node_modules to prevent duplicate native modules
|
||||
const packagesDir = path.join(monorepoRoot, 'packages');
|
||||
const escapedPath = packagesDir.replace(/[/\\]/g, '[/\\\\]');
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
import { MD3DarkTheme } from 'react-native-paper';
|
||||
import { Platform } from 'react-native';
|
||||
import { MD3DarkTheme, configureFonts } from 'react-native-paper';
|
||||
import type { BrandTokens } from '@dangerousthings/tokens';
|
||||
import type { DTExtendedTheme } from '@dangerousthings/react-native';
|
||||
|
||||
/** Parse CSS size token ("1rem", "0.25rem", "0px", "0") to pixels (1rem = 16px) */
|
||||
function parseSize(value: string): number {
|
||||
if (value === '0' || value === '0px') return 0;
|
||||
const num = parseFloat(value);
|
||||
if (value.endsWith('rem')) return num * 16;
|
||||
if (value.endsWith('px')) return num;
|
||||
return num;
|
||||
}
|
||||
|
||||
/** Build RNP font config from brand typography tokens */
|
||||
function buildFonts(brand: BrandTokens) {
|
||||
const usesTektur = brand.typography.heading.includes('Tektur');
|
||||
if (!usesTektur) {
|
||||
// System sans-serif — use RNP defaults
|
||||
return MD3DarkTheme.fonts;
|
||||
}
|
||||
// Tektur custom font
|
||||
const fontFamily = Platform.select({
|
||||
ios: 'Tektur',
|
||||
android: 'Tektur-Regular',
|
||||
default: 'Tektur',
|
||||
})!;
|
||||
return configureFonts({
|
||||
config: { fontFamily },
|
||||
});
|
||||
}
|
||||
|
||||
export function buildThemeFromBrand(brand: BrandTokens): DTExtendedTheme {
|
||||
const colors = brand.dark;
|
||||
|
||||
@@ -9,7 +37,7 @@ export function buildThemeFromBrand(brand: BrandTokens): DTExtendedTheme {
|
||||
...MD3DarkTheme,
|
||||
dark: true,
|
||||
roundness: brand.shape.radiusSm === '0' || brand.shape.radiusSm === '0px' ? 0 : 4,
|
||||
fonts: MD3DarkTheme.fonts,
|
||||
fonts: buildFonts(brand),
|
||||
colors: {
|
||||
...MD3DarkTheme.colors,
|
||||
primary: colors.primary,
|
||||
@@ -63,6 +91,12 @@ export function buildThemeFromBrand(brand: BrandTokens): DTExtendedTheme {
|
||||
modeOther: colors.other,
|
||||
border: colors.border,
|
||||
borderEmphasis: colors.secondary,
|
||||
bevelSm: parseSize(brand.shape.bevelSm),
|
||||
bevelMd: parseSize(brand.shape.bevelMd),
|
||||
bevelLg: parseSize(brand.shape.bevelLg),
|
||||
radiusSm: parseSize(brand.shape.radiusSm),
|
||||
radius: parseSize(brand.shape.radius),
|
||||
radiusLg: parseSize(brand.shape.radiusLg),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user