Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/theme/fonts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { TypescaleStyle, Typescale, TypescaleKey } from '../types';
import { typescale } from './tokens';
import type { TypescaleStyle, Typescale, TypescaleKey } from './types';

type FontsConfig =
| {
Expand Down
74 changes: 5 additions & 69 deletions src/theme/schemes/DarkTheme.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,11 @@
import color from 'color';

import { LightTheme } from './LightTheme';
import type { Theme } from '../../types';
import { baseTheme } from './base';
import { tokens } from '../tokens';

const { palette, stateOpacity } = tokens.md.ref;
import { buildScheme } from '../tokens/sys/color/roles';
import type { Theme } from '../types';

export const DarkTheme: Theme = {
...LightTheme,
...baseTheme,
dark: true,
mode: 'adaptive',
colors: {
primary: palette.primary80,
primaryContainer: palette.primary30,
secondary: palette.secondary80,
secondaryContainer: palette.secondary30,
tertiary: palette.tertiary80,
tertiaryContainer: palette.tertiary30,
surface: palette.neutral6,
surfaceDim: palette.neutral6,
surfaceBright: palette.neutral24,
surfaceContainerLowest: palette.neutral4,
surfaceContainerLow: palette.neutral10,
surfaceContainer: palette.neutral12,
surfaceContainerHigh: palette.neutral17,
surfaceContainerHighest: palette.neutral22,
surfaceVariant: palette.neutralVariant30,
background: palette.neutral6,
error: palette.error80,
errorContainer: palette.error30,
onPrimary: palette.primary20,
onPrimaryContainer: palette.primary90,
onSecondary: palette.secondary20,
onSecondaryContainer: palette.secondary90,
onTertiary: palette.tertiary20,
onTertiaryContainer: palette.tertiary90,
onSurface: palette.neutral90,
onSurfaceVariant: palette.neutralVariant80,
onError: palette.error20,
onErrorContainer: palette.error80,
onBackground: palette.neutral90,
outline: palette.neutralVariant60,
outlineVariant: palette.neutralVariant30,
inverseSurface: palette.neutral90,
inverseOnSurface: palette.neutral20,
inversePrimary: palette.primary40,
primaryFixed: palette.primary90,
primaryFixedDim: palette.primary80,
onPrimaryFixed: palette.primary10,
onPrimaryFixedVariant: palette.primary30,
secondaryFixed: palette.secondary90,
secondaryFixedDim: palette.secondary80,
onSecondaryFixed: palette.secondary10,
onSecondaryFixedVariant: palette.secondary30,
tertiaryFixed: palette.tertiary90,
tertiaryFixedDim: palette.tertiary80,
onTertiaryFixed: palette.tertiary10,
onTertiaryFixedVariant: palette.tertiary30,
shadow: palette.neutral0,
scrim: palette.neutral0,
stateLayerPressed: color(palette.neutral90)
.alpha(stateOpacity.pressed)
.rgb()
.string(),
elevation: {
level0: 'transparent',
level1: palette.neutral10,
level2: palette.neutral12,
level3: palette.neutral17,
level4: palette.neutral17,
level5: palette.neutral22,
},
},
colors: buildScheme(tokens.md.ref.palette, tokens.md.ref, { mode: 'dark' }),
};
2 changes: 1 addition & 1 deletion src/theme/schemes/DynamicTheme.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Platform, PlatformColor } from 'react-native';

import { DarkTheme } from './DarkTheme';
import { LightTheme } from './LightTheme';
import type { Theme } from '../../types';
import type { Theme } from '../types';

const isApi34 = (Platform.Version as number) >= 34;
const isApi31 = (Platform.Version as number) >= 31;
Expand Down
78 changes: 5 additions & 73 deletions src/theme/schemes/LightTheme.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,10 @@
import color from 'color';

import type { Theme } from '../../types';
import configureFonts from '../fonts';
import { baseTheme } from './base';
import { tokens } from '../tokens';

const { palette, stateOpacity } = tokens.md.ref;
import { buildScheme } from '../tokens/sys/color/roles';
import type { Theme } from '../types';

export const LightTheme: Theme = {
...baseTheme,
dark: false,
roundness: 4,
colors: {
primary: palette.primary40,
primaryContainer: palette.primary90,
secondary: palette.secondary40,
secondaryContainer: palette.secondary90,
tertiary: palette.tertiary40,
tertiaryContainer: palette.tertiary90,
surface: palette.neutral98,
surfaceDim: palette.neutral87,
surfaceBright: palette.neutral98,
surfaceContainerLowest: palette.neutral100,
surfaceContainerLow: palette.neutral96,
surfaceContainer: palette.neutral94,
surfaceContainerHigh: palette.neutral92,
surfaceContainerHighest: palette.neutral90,
surfaceVariant: palette.neutralVariant90,
background: palette.neutral98,
error: palette.error40,
errorContainer: palette.error90,
onPrimary: palette.primary100,
onPrimaryContainer: palette.primary10,
onSecondary: palette.secondary100,
onSecondaryContainer: palette.secondary10,
onTertiary: palette.tertiary100,
onTertiaryContainer: palette.tertiary10,
onSurface: palette.neutral10,
onSurfaceVariant: palette.neutralVariant30,
onError: palette.error100,
onErrorContainer: palette.error10,
onBackground: palette.neutral10,
outline: palette.neutralVariant50,
outlineVariant: palette.neutralVariant80,
inverseSurface: palette.neutral20,
inverseOnSurface: palette.neutral95,
inversePrimary: palette.primary80,
primaryFixed: palette.primary90,
primaryFixedDim: palette.primary80,
onPrimaryFixed: palette.primary10,
onPrimaryFixedVariant: palette.primary30,
secondaryFixed: palette.secondary90,
secondaryFixedDim: palette.secondary80,
onSecondaryFixed: palette.secondary10,
onSecondaryFixedVariant: palette.secondary30,
tertiaryFixed: palette.tertiary90,
tertiaryFixedDim: palette.tertiary80,
onTertiaryFixed: palette.tertiary10,
onTertiaryFixedVariant: palette.tertiary30,
shadow: palette.neutral0,
scrim: palette.neutral0,
stateLayerPressed: color(palette.neutral10)
.alpha(stateOpacity.pressed)
.rgb()
.string(),
elevation: {
level0: 'transparent',
level1: palette.neutral96,
level2: palette.neutral94,
level3: palette.neutral92,
level4: palette.neutral92,
level5: palette.neutral90,
},
},
fonts: configureFonts(),
animation: {
scale: 1.0,
},
colors: buildScheme(tokens.md.ref.palette, tokens.md.ref, { mode: 'light' }),
};
9 changes: 9 additions & 0 deletions src/theme/schemes/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import configureFonts from '../fonts';

export const baseTheme = {
roundness: 4,
fonts: configureFonts(),
animation: {
scale: 1.0,
},
};
2 changes: 1 addition & 1 deletion src/theme/tokens/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Platform } from 'react-native';

import type { Font } from '../../types';
import type { Font } from '../types';

const ref = {
palette: {
Expand Down
180 changes: 180 additions & 0 deletions src/theme/tokens/sys/color/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import color from 'color';

import type { ElevationColors, ThemeColors } from '../../../types';
import { tokens } from '../../index';

type Palette = typeof tokens.md.ref.palette;
type PaletteKey = keyof Palette;
type Ref = typeof tokens.md.ref;

/** Roles that map 1:1 to a palette key. Excludes the computed fields. */
type MappedRoles = Omit<ThemeColors, 'stateLayerPressed' | 'elevation'>;

type Contrast = 'standard'; // extend with 'medium' | 'high' when those ship

const roleToTone: Record<
'light' | 'dark',
Record<Contrast, Record<keyof MappedRoles, PaletteKey>>
> = {
light: {
standard: {
primary: 'primary40',
onPrimary: 'primary100',
primaryContainer: 'primary90',
onPrimaryContainer: 'primary10',
secondary: 'secondary40',
onSecondary: 'secondary100',
secondaryContainer: 'secondary90',
onSecondaryContainer: 'secondary10',
tertiary: 'tertiary40',
onTertiary: 'tertiary100',
tertiaryContainer: 'tertiary90',
onTertiaryContainer: 'tertiary10',
error: 'error40',
onError: 'error100',
errorContainer: 'error90',
onErrorContainer: 'error10',
surface: 'neutral98',
surfaceDim: 'neutral87',
surfaceBright: 'neutral98',
surfaceContainerLowest: 'neutral100',
surfaceContainerLow: 'neutral96',
surfaceContainer: 'neutral94',
surfaceContainerHigh: 'neutral92',
surfaceContainerHighest: 'neutral90',
surfaceVariant: 'neutralVariant90',
background: 'neutral98',
onSurface: 'neutral10',
onSurfaceVariant: 'neutralVariant30',
onBackground: 'neutral10',
outline: 'neutralVariant50',
outlineVariant: 'neutralVariant80',
inverseSurface: 'neutral20',
inverseOnSurface: 'neutral95',
inversePrimary: 'primary80',
primaryFixed: 'primary90',
primaryFixedDim: 'primary80',
onPrimaryFixed: 'primary10',
onPrimaryFixedVariant: 'primary30',
secondaryFixed: 'secondary90',
secondaryFixedDim: 'secondary80',
onSecondaryFixed: 'secondary10',
onSecondaryFixedVariant: 'secondary30',
tertiaryFixed: 'tertiary90',
tertiaryFixedDim: 'tertiary80',
onTertiaryFixed: 'tertiary10',
onTertiaryFixedVariant: 'tertiary30',
shadow: 'neutral0',
scrim: 'neutral0',
},
},
dark: {
standard: {
primary: 'primary80',
onPrimary: 'primary20',
primaryContainer: 'primary30',
onPrimaryContainer: 'primary90',
secondary: 'secondary80',
onSecondary: 'secondary20',
secondaryContainer: 'secondary30',
onSecondaryContainer: 'secondary90',
tertiary: 'tertiary80',
onTertiary: 'tertiary20',
tertiaryContainer: 'tertiary30',
onTertiaryContainer: 'tertiary90',
error: 'error80',
onError: 'error20',
errorContainer: 'error30',
onErrorContainer: 'error80',
surface: 'neutral6',
surfaceDim: 'neutral6',
surfaceBright: 'neutral24',
surfaceContainerLowest: 'neutral4',
surfaceContainerLow: 'neutral10',
surfaceContainer: 'neutral12',
surfaceContainerHigh: 'neutral17',
surfaceContainerHighest: 'neutral22',
surfaceVariant: 'neutralVariant30',
background: 'neutral6',
onSurface: 'neutral90',
onSurfaceVariant: 'neutralVariant80',
onBackground: 'neutral90',
outline: 'neutralVariant60',
outlineVariant: 'neutralVariant30',
inverseSurface: 'neutral90',
inverseOnSurface: 'neutral20',
inversePrimary: 'primary40',
primaryFixed: 'primary90',
primaryFixedDim: 'primary80',
onPrimaryFixed: 'primary10',
onPrimaryFixedVariant: 'primary30',
secondaryFixed: 'secondary90',
secondaryFixedDim: 'secondary80',
onSecondaryFixed: 'secondary10',
onSecondaryFixedVariant: 'secondary30',
tertiaryFixed: 'tertiary90',
tertiaryFixedDim: 'tertiary80',
onTertiaryFixed: 'tertiary10',
onTertiaryFixedVariant: 'tertiary30',
shadow: 'neutral0',
scrim: 'neutral0',
},
},
};

const elevationToTone: Record<
'light' | 'dark',
Record<Contrast, Record<Exclude<keyof ElevationColors, 'level0'>, PaletteKey>>
> = {
light: {
standard: {
level1: 'neutral96',
level2: 'neutral94',
level3: 'neutral92',
level4: 'neutral92',
level5: 'neutral90',
},
},
dark: {
standard: {
level1: 'neutral10',
level2: 'neutral12',
level3: 'neutral17',
level4: 'neutral17',
level5: 'neutral22',
},
},
};

export function buildScheme(
palette: Palette,
ref: Ref,
opts: { mode: 'light' | 'dark'; contrast?: Contrast }
): ThemeColors {
const contrast = opts.contrast ?? 'standard';
const tones = roleToTone[opts.mode][contrast];
const elevTones = elevationToTone[opts.mode][contrast];

const mapped = Object.fromEntries(
(Object.keys(tones) as Array<keyof typeof tones>).map((role) => [
role,
palette[tones[role]],
])
) as MappedRoles;

return {
...mapped,
stateLayerPressed: color(palette[tones.onSurface])
.alpha(ref.stateOpacity.pressed)
.rgb()
.string(),
elevation: {
level0: 'transparent',
level1: palette[elevTones.level1],
level2: palette[elevTones.level2],
level3: palette[elevTones.level3],
level4: palette[elevTones.level4],
level5: palette[elevTones.level5],
},
};
}
Loading