diff --git a/src/components/Card/CardTitle.tsx b/src/components/Card/CardTitle.tsx index b37f903499..a8cda33fdc 100644 --- a/src/components/Card/CardTitle.tsx +++ b/src/components/Card/CardTitle.tsx @@ -40,7 +40,7 @@ export type Props = React.ComponentPropsWithRef & { * * Body: `bodyLarge`, `bodyMedium`, `bodySmall` */ - titleVariant?: keyof typeof TypescaleKey; + titleVariant?: TypescaleKey; /** * Text for the subtitle. Note that this will only accept a string or ``-based node. */ @@ -69,7 +69,7 @@ export type Props = React.ComponentPropsWithRef & { * * Body: `bodyLarge`, `bodyMedium`, `bodySmall` */ - subtitleVariant?: keyof typeof TypescaleKey; + subtitleVariant?: TypescaleKey; /** * Callback which returns a React element to display on the left side. */ diff --git a/src/components/Checkbox/CheckboxItem.tsx b/src/components/Checkbox/CheckboxItem.tsx index 008f03640d..9037137e7a 100644 --- a/src/components/Checkbox/CheckboxItem.tsx +++ b/src/components/Checkbox/CheckboxItem.tsx @@ -86,7 +86,7 @@ export type Props = { * * Body: `bodyLarge`, `bodyMedium`, `bodySmall` */ - labelVariant?: keyof typeof TypescaleKey; + labelVariant?: TypescaleKey; /** * @optional */ diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index a3f798d03a..4ba3d59a57 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -24,7 +24,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import MenuItem from './MenuItem'; import { useInternalTheme } from '../../core/theming'; import type { Elevation, Theme, ThemeProp } from '../../types'; -import { ElevationLevels } from '../../types'; import { addEventListener } from '../../utils/addEventListener'; import { BackHandler } from '../../utils/BackHandler/BackHandler'; import Portal from '../Portal/Portal'; @@ -105,9 +104,14 @@ const EASING = Easing.bezier(0.4, 0, 0.2, 1); const WINDOW_LAYOUT = Dimensions.get('window'); const DEFAULT_ELEVATION: Elevation = 2; -export const ELEVATION_LEVELS_MAP = Object.values( - ElevationLevels -) as ElevationLevels[]; +export const ELEVATION_LEVELS_MAP = [ + 'level0', + 'level1', + 'level2', + 'level3', + 'level4', + 'level5', +] as const; const DEFAULT_MODE = 'elevated'; diff --git a/src/components/RadioButton/RadioButtonItem.tsx b/src/components/RadioButton/RadioButtonItem.tsx index fe9b6843a8..936a2cdf3d 100644 --- a/src/components/RadioButton/RadioButtonItem.tsx +++ b/src/components/RadioButton/RadioButtonItem.tsx @@ -88,7 +88,7 @@ export type Props = { * * Body: `bodyLarge`, `bodyMedium`, `bodySmall` */ - labelVariant?: keyof typeof TypescaleKey; + labelVariant?: TypescaleKey; /** * Specifies the largest possible scale a label font can reach. */ diff --git a/src/components/Typography/types.tsx b/src/components/Typography/types.tsx index 3f1754521a..ef7de9891a 100644 --- a/src/components/Typography/types.tsx +++ b/src/components/Typography/types.tsx @@ -2,4 +2,4 @@ import type { TypescaleKey } from '../../types'; export type VariantProp = | (T extends string ? (string extends T ? never : T) : never) - | keyof typeof TypescaleKey; + | TypescaleKey; diff --git a/src/deprecated.ts b/src/deprecated.ts index e294f6a44d..ded0e3f10d 100644 --- a/src/deprecated.ts +++ b/src/deprecated.ts @@ -11,8 +11,7 @@ import { DarkTheme } from './theme/schemes/DarkTheme'; import { LightTheme } from './theme/schemes/LightTheme'; import { Palette } from './theme/tokens'; -import { TypescaleKey } from './types'; -import type { Theme, Elevation } from './types'; +import type { Theme, Elevation, TypescaleKey } from './types'; /** * @deprecated Use `LightTheme` instead. Will be removed in a future version. @@ -32,8 +31,6 @@ export const MD3Colors = Palette; /** * @deprecated Use `TypescaleKey` instead. Will be removed in a future version. */ -export const MD3TypescaleKey = TypescaleKey; -// eslint-disable-next-line no-redeclare export type MD3TypescaleKey = TypescaleKey; /** diff --git a/src/theme/fonts.tsx b/src/theme/fonts.tsx index 7808205474..e380655486 100644 --- a/src/theme/fonts.tsx +++ b/src/theme/fonts.tsx @@ -2,12 +2,8 @@ import { typescale } from './tokens'; import type { TypescaleStyle, Typescale, TypescaleKey } from './types'; type FontsConfig = - | { - [key in TypescaleKey]: Partial; - } - | { - [key: string]: TypescaleStyle; - } + | Record> + | Record | Partial; function configureFontsConfig( diff --git a/src/theme/tokens/index.ts b/src/theme/tokens/index.ts index ecc35ffa24..e8f041163e 100644 --- a/src/theme/tokens/index.ts +++ b/src/theme/tokens/index.ts @@ -122,8 +122,8 @@ const ref = { stateOpacity: { dragged: 0.16, pressed: 0.1, - focus: 0.1, - hover: 0.08, + focused: 0.1, + hovered: 0.08, disabled: 0.38, enabled: 1.0, }, diff --git a/src/theme/types.ts b/src/theme/types.ts deleted file mode 100644 index b424142b86..0000000000 --- a/src/theme/types.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type * as React from 'react'; - -import type { $DeepPartial } from '@callstack/react-theme-provider'; - -export type Font = { - fontFamily: string; - fontWeight?: - | 'normal' - | 'bold' - | '100' - | '200' - | '300' - | '400' - | '500' - | '600' - | '700' - | '800' - | '900'; - fontStyle?: 'normal' | 'italic' | undefined; -}; - -export type Fonts = { - regular: Font; - medium: Font; - light: Font; - thin: Font; -}; - -type Mode = 'adaptive' | 'exact'; - -export type ThemeColors = { - primary: string; - primaryContainer: string; - secondary: string; - secondaryContainer: string; - tertiary: string; - tertiaryContainer: string; - surface: string; - surfaceDim: string; - surfaceBright: string; - surfaceContainerLowest: string; - surfaceContainerLow: string; - surfaceContainer: string; - surfaceContainerHigh: string; - surfaceContainerHighest: string; - surfaceVariant: string; - background: string; - error: string; - errorContainer: string; - onPrimary: string; - onPrimaryContainer: string; - onSecondary: string; - onSecondaryContainer: string; - onTertiary: string; - onTertiaryContainer: string; - onSurface: string; - onSurfaceVariant: string; - onError: string; - onErrorContainer: string; - onBackground: string; - outline: string; - outlineVariant: string; - inverseSurface: string; - inverseOnSurface: string; - inversePrimary: string; - primaryFixed: string; - primaryFixedDim: string; - onPrimaryFixed: string; - onPrimaryFixedVariant: string; - secondaryFixed: string; - secondaryFixedDim: string; - onSecondaryFixed: string; - onSecondaryFixedVariant: string; - tertiaryFixed: string; - tertiaryFixedDim: string; - onTertiaryFixed: string; - onTertiaryFixedVariant: string; - shadow: string; - scrim: string; - /** Pre-computed state layer color at press opacity (0.10). - * Used for ripple effects. Avoids runtime alpha manipulation - * which is incompatible with PlatformColor on Android. - * TODO: revisit after https://github.com/facebook/react-native/pull/56395 - * @see https://m3.material.io/foundations/interaction/states/state-layers */ - stateLayerPressed: string; - elevation: ElevationColors; -}; - -export type ThemeProp = $DeepPartial; - -export type ThemeBase = { - dark: boolean; - mode?: Mode; - roundness: number; - animation: { - scale: number; - defaultAnimationDuration?: number; - }; -}; - -export type Theme = ThemeBase & { - colors: ThemeColors; - fonts: Typescale; -}; - -export type InternalTheme = Theme; - -export enum TypescaleKey { - displayLarge = 'displayLarge', - displayMedium = 'displayMedium', - displaySmall = 'displaySmall', - - headlineLarge = 'headlineLarge', - headlineMedium = 'headlineMedium', - headlineSmall = 'headlineSmall', - - titleLarge = 'titleLarge', - titleMedium = 'titleMedium', - titleSmall = 'titleSmall', - - labelLarge = 'labelLarge', - labelMedium = 'labelMedium', - labelSmall = 'labelSmall', - - bodyLarge = 'bodyLarge', - bodyMedium = 'bodyMedium', - bodySmall = 'bodySmall', -} - -export type TypescaleStyle = { - fontFamily: string; - letterSpacing: number; - fontWeight: Font['fontWeight']; - lineHeight: number; - fontSize: number; - fontStyle?: Font['fontStyle']; -}; - -export type Typescale = - | { - [key in TypescaleKey]: TypescaleStyle; - } & { - ['default']: Omit; - }; - -export type Elevation = 0 | 1 | 2 | 3 | 4 | 5; - -export enum ElevationLevels { - 'level0', - 'level1', - 'level2', - 'level3', - 'level4', - 'level5', -} - -export type ElevationColors = { - [key in keyof typeof ElevationLevels]: string; -}; - -export type $Omit = Pick>; -export type $RemoveChildren> = $Omit< - React.ComponentPropsWithoutRef, - 'children' ->; - -export type EllipsizeProp = 'head' | 'middle' | 'tail' | 'clip'; - -export type NavigationTheme = { - dark: boolean; - colors: { - primary: string; - background: string; - card: string; - text: string; - border: string; - notification: string; - }; -}; diff --git a/src/theme/types/color.ts b/src/theme/types/color.ts new file mode 100644 index 0000000000..cc7ed49b86 --- /dev/null +++ b/src/theme/types/color.ts @@ -0,0 +1,59 @@ +import type { ElevationColors } from './elevation'; + +export type ThemeColors = { + primary: string; + primaryContainer: string; + secondary: string; + secondaryContainer: string; + tertiary: string; + tertiaryContainer: string; + surface: string; + surfaceDim: string; + surfaceBright: string; + surfaceContainerLowest: string; + surfaceContainerLow: string; + surfaceContainer: string; + surfaceContainerHigh: string; + surfaceContainerHighest: string; + surfaceVariant: string; + background: string; + error: string; + errorContainer: string; + onPrimary: string; + onPrimaryContainer: string; + onSecondary: string; + onSecondaryContainer: string; + onTertiary: string; + onTertiaryContainer: string; + onSurface: string; + onSurfaceVariant: string; + onError: string; + onErrorContainer: string; + onBackground: string; + outline: string; + outlineVariant: string; + inverseSurface: string; + inverseOnSurface: string; + inversePrimary: string; + primaryFixed: string; + primaryFixedDim: string; + onPrimaryFixed: string; + onPrimaryFixedVariant: string; + secondaryFixed: string; + secondaryFixedDim: string; + onSecondaryFixed: string; + onSecondaryFixedVariant: string; + tertiaryFixed: string; + tertiaryFixedDim: string; + onTertiaryFixed: string; + onTertiaryFixedVariant: string; + shadow: string; + scrim: string; + /** Pre-computed state layer color at press opacity (0.10). + * Used for ripple effects. Avoids runtime alpha manipulation + * which is incompatible with PlatformColor on Android. + * TODO: revisit after https://github.com/facebook/react-native/pull/56395 + * @see https://m3.material.io/foundations/interaction/states/state-layers */ + stateLayerPressed: string; + elevation: ElevationColors; +}; diff --git a/src/theme/types/elevation.ts b/src/theme/types/elevation.ts new file mode 100644 index 0000000000..839112f58c --- /dev/null +++ b/src/theme/types/elevation.ts @@ -0,0 +1,3 @@ +export type Elevation = 0 | 1 | 2 | 3 | 4 | 5; + +export type ElevationColors = Record<`level${Elevation}`, string>; diff --git a/src/theme/types/index.ts b/src/theme/types/index.ts new file mode 100644 index 0000000000..2207ed0526 --- /dev/null +++ b/src/theme/types/index.ts @@ -0,0 +1,6 @@ +export * from './color'; +export * from './elevation'; +export * from './navigation'; +export * from './theme'; +export * from './typography'; +export * from './utils'; diff --git a/src/theme/types/navigation.ts b/src/theme/types/navigation.ts new file mode 100644 index 0000000000..51d4a6f763 --- /dev/null +++ b/src/theme/types/navigation.ts @@ -0,0 +1,11 @@ +export type NavigationTheme = { + dark: boolean; + colors: { + primary: string; + background: string; + card: string; + text: string; + border: string; + notification: string; + }; +}; diff --git a/src/theme/types/theme.ts b/src/theme/types/theme.ts new file mode 100644 index 0000000000..431b4ecfcf --- /dev/null +++ b/src/theme/types/theme.ts @@ -0,0 +1,25 @@ +import type { $DeepPartial } from '@callstack/react-theme-provider'; + +import type { ThemeColors } from './color'; +import type { Typescale } from './typography'; + +type Mode = 'adaptive' | 'exact'; + +export type ThemeBase = { + dark: boolean; + mode?: Mode; + roundness: number; + animation: { + scale: number; + defaultAnimationDuration?: number; + }; +}; + +export type Theme = ThemeBase & { + colors: ThemeColors; + fonts: Typescale; +}; + +export type InternalTheme = Theme; + +export type ThemeProp = $DeepPartial; diff --git a/src/theme/types/typography.ts b/src/theme/types/typography.ts new file mode 100644 index 0000000000..57fcf3f67e --- /dev/null +++ b/src/theme/types/typography.ts @@ -0,0 +1,53 @@ +export type Font = { + fontFamily: string; + fontWeight?: + | 'normal' + | 'bold' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900'; + fontStyle?: 'normal' | 'italic' | undefined; +}; + +export type Fonts = { + regular: Font; + medium: Font; + light: Font; + thin: Font; +}; + +export type TypescaleKey = + | 'displayLarge' + | 'displayMedium' + | 'displaySmall' + | 'headlineLarge' + | 'headlineMedium' + | 'headlineSmall' + | 'titleLarge' + | 'titleMedium' + | 'titleSmall' + | 'labelLarge' + | 'labelMedium' + | 'labelSmall' + | 'bodyLarge' + | 'bodyMedium' + | 'bodySmall'; + +export type TypescaleStyle = { + fontFamily: string; + letterSpacing: number; + fontWeight: Font['fontWeight']; + lineHeight: number; + fontSize: number; + fontStyle?: Font['fontStyle']; +}; + +export type Typescale = Record & { + default: Omit; +}; diff --git a/src/theme/types/utils.ts b/src/theme/types/utils.ts new file mode 100644 index 0000000000..0ea6b262b9 --- /dev/null +++ b/src/theme/types/utils.ts @@ -0,0 +1,9 @@ +import type * as React from 'react'; + +export type $Omit = Pick>; +export type $RemoveChildren> = $Omit< + React.ComponentPropsWithoutRef, + 'children' +>; + +export type EllipsizeProp = 'head' | 'middle' | 'tail' | 'clip';