From dc37d7644ce300968d535422a2df2094b9508366 Mon Sep 17 00:00:00 2001 From: Adrian Cotfas Date: Tue, 5 May 2026 10:54:26 +0300 Subject: [PATCH] feat: add theme.shapes and cornersToStyle Adds the 10 MD3 shape scale tokens to theme.shapes.*, ShapeCorners for directional variants, and cornersToStyle() for mapping to RN style props. Deprecates theme.roundness in favour of theme.shapes. --- .../__snapshots__/ListSection.test.tsx.snap | 36 +++++++++++++++++++ src/index.tsx | 1 + src/theme/schemes/DarkTheme.tsx | 2 ++ src/theme/schemes/LightTheme.tsx | 2 ++ src/theme/tokens/sys/shape.ts | 33 +++++++++++++++++ src/theme/types/index.ts | 1 + src/theme/types/shape.ts | 19 ++++++++++ src/theme/types/theme.ts | 3 ++ 8 files changed, 97 insertions(+) create mode 100644 src/theme/tokens/sys/shape.ts create mode 100644 src/theme/types/shape.ts diff --git a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap index f5691bf2d2..04309ae431 100644 --- a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap @@ -188,6 +188,18 @@ exports[`renders list section with custom title style 1`] = ` }, }, "roundness": 4, + "shapes": { + "extraExtraLarge": 48, + "extraLarge": 28, + "extraLargeIncreased": 32, + "extraSmall": 4, + "full": 9999, + "large": 16, + "largeIncreased": 20, + "medium": 12, + "none": 0, + "small": 8, + }, "state": { "opacity": { "dragged": 0.16, @@ -738,6 +750,18 @@ exports[`renders list section with subheader 1`] = ` }, }, "roundness": 4, + "shapes": { + "extraExtraLarge": 48, + "extraLarge": 28, + "extraLargeIncreased": 32, + "extraSmall": 4, + "full": 9999, + "large": 16, + "largeIncreased": 20, + "medium": 12, + "none": 0, + "small": 8, + }, "state": { "opacity": { "dragged": 0.16, @@ -1286,6 +1310,18 @@ exports[`renders list section without subheader 1`] = ` }, }, "roundness": 4, + "shapes": { + "extraExtraLarge": 48, + "extraLarge": 28, + "extraLargeIncreased": 32, + "extraSmall": 4, + "full": 9999, + "large": 16, + "largeIncreased": 20, + "medium": 12, + "none": 0, + "small": 8, + }, "state": { "opacity": { "dragged": 0.16, diff --git a/src/index.tsx b/src/index.tsx index 298658cf50..4f45286d6b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,6 +14,7 @@ export { default as Provider } from './core/PaperProvider'; export { default as PaperProvider } from './core/PaperProvider'; export { default as shadow } from './theme/shadow'; export { default as configureFonts } from './theme/fonts'; +export { cornersToStyle } from './theme/tokens/sys/shape'; import * as Avatar from './components/Avatar/Avatar'; import * as Drawer from './components/Drawer/Drawer'; diff --git a/src/theme/schemes/DarkTheme.tsx b/src/theme/schemes/DarkTheme.tsx index 2bb6eacf5d..97153f939c 100644 --- a/src/theme/schemes/DarkTheme.tsx +++ b/src/theme/schemes/DarkTheme.tsx @@ -1,6 +1,7 @@ import { baseTheme } from './base'; import { tokens } from '../tokens'; import { buildScheme } from '../tokens/sys/color/roles'; +import { defaultShapes } from '../tokens/sys/shape'; import { defaultState } from '../tokens/sys/state'; import type { Theme } from '../types'; @@ -10,4 +11,5 @@ export const DarkTheme: Theme = { mode: 'adaptive', colors: buildScheme(tokens.md.ref.palette, tokens.md.ref, { mode: 'dark' }), state: defaultState, + shapes: defaultShapes, }; diff --git a/src/theme/schemes/LightTheme.tsx b/src/theme/schemes/LightTheme.tsx index c63324fa63..753370602b 100644 --- a/src/theme/schemes/LightTheme.tsx +++ b/src/theme/schemes/LightTheme.tsx @@ -1,6 +1,7 @@ import { baseTheme } from './base'; import { tokens } from '../tokens'; import { buildScheme } from '../tokens/sys/color/roles'; +import { defaultShapes } from '../tokens/sys/shape'; import { defaultState } from '../tokens/sys/state'; import type { Theme } from '../types'; @@ -9,4 +10,5 @@ export const LightTheme: Theme = { dark: false, colors: buildScheme(tokens.md.ref.palette, tokens.md.ref, { mode: 'light' }), state: defaultState, + shapes: defaultShapes, }; diff --git a/src/theme/tokens/sys/shape.ts b/src/theme/tokens/sys/shape.ts new file mode 100644 index 0000000000..988aa8aeed --- /dev/null +++ b/src/theme/tokens/sys/shape.ts @@ -0,0 +1,33 @@ +import type { ViewStyle } from 'react-native'; + +import type { ShapeCorners, ThemeShapes } from '../../types'; + +export const defaultShapes: ThemeShapes = { + none: 0, + extraSmall: 4, + small: 8, + medium: 12, + large: 16, + largeIncreased: 20, + extraLarge: 28, + extraLargeIncreased: 32, + extraExtraLarge: 48, + full: 9999, +}; + +type CornersStyle = Pick< + ViewStyle, + | 'borderTopStartRadius' + | 'borderTopEndRadius' + | 'borderBottomStartRadius' + | 'borderBottomEndRadius' +>; + +export function cornersToStyle(corners: ShapeCorners): CornersStyle { + return { + borderTopStartRadius: corners.topStart, + borderTopEndRadius: corners.topEnd, + borderBottomStartRadius: corners.bottomStart, + borderBottomEndRadius: corners.bottomEnd, + }; +} diff --git a/src/theme/types/index.ts b/src/theme/types/index.ts index 562a7ba943..eff2bd98a9 100644 --- a/src/theme/types/index.ts +++ b/src/theme/types/index.ts @@ -1,6 +1,7 @@ export * from './color'; export * from './elevation'; export * from './navigation'; +export * from './shape'; export * from './state'; export * from './theme'; export * from './typography'; diff --git a/src/theme/types/shape.ts b/src/theme/types/shape.ts new file mode 100644 index 0000000000..b2d18f3124 --- /dev/null +++ b/src/theme/types/shape.ts @@ -0,0 +1,19 @@ +export type ShapeCorners = { + topStart: number; + topEnd: number; + bottomStart: number; + bottomEnd: number; +}; + +export type ThemeShapes = { + none: number; + extraSmall: number; + small: number; + medium: number; + large: number; + largeIncreased: number; + extraLarge: number; + extraLargeIncreased: number; + extraExtraLarge: number; + full: number; +}; diff --git a/src/theme/types/theme.ts b/src/theme/types/theme.ts index 742b3db5c4..7eabd8be12 100644 --- a/src/theme/types/theme.ts +++ b/src/theme/types/theme.ts @@ -1,6 +1,7 @@ import type { $DeepPartial } from '@callstack/react-theme-provider'; import type { ThemeColors } from './color'; +import type { ThemeShapes } from './shape'; import type { ThemeState } from './state'; import type { Typescale } from './typography'; @@ -9,6 +10,7 @@ type Mode = 'adaptive' | 'exact'; export type ThemeBase = { dark: boolean; mode?: Mode; + /** @deprecated Use `theme.shapes.*` instead. Will be removed in a future version. */ roundness: number; animation: { scale: number; @@ -20,6 +22,7 @@ export type Theme = ThemeBase & { colors: ThemeColors; fonts: Typescale; state: ThemeState; + shapes: ThemeShapes; }; export type InternalTheme = Theme;