From 1107a1fc8bf8f9cf94f4345b3b8e8ddacfae9fd9 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:28:38 +0300 Subject: [PATCH] feat(design-system): add MUI theme adapter --- .../src/theme/MoexVibeThemeProvider.tsx | 20 +++++++ .../src/theme/createMoexVibeTheme.test.ts | 29 ++++++++++ .../src/theme/createMoexVibeTheme.ts | 55 +++++++++++++++++++ packages/design-system/src/theme/index.ts | 2 + packages/design-system/src/theme/mui.d.ts | 18 ++++++ 5 files changed, 124 insertions(+) create mode 100644 packages/design-system/src/theme/MoexVibeThemeProvider.tsx create mode 100644 packages/design-system/src/theme/createMoexVibeTheme.test.ts create mode 100644 packages/design-system/src/theme/createMoexVibeTheme.ts create mode 100644 packages/design-system/src/theme/index.ts create mode 100644 packages/design-system/src/theme/mui.d.ts diff --git a/packages/design-system/src/theme/MoexVibeThemeProvider.tsx b/packages/design-system/src/theme/MoexVibeThemeProvider.tsx new file mode 100644 index 0000000..9c577ac --- /dev/null +++ b/packages/design-system/src/theme/MoexVibeThemeProvider.tsx @@ -0,0 +1,20 @@ +import { type ReactNode } from 'react'; +import { ThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles'; +import { createMoexVibeTheme } from './createMoexVibeTheme'; + +const theme = createMoexVibeTheme(); + +export interface MoexVibeThemeProviderProps { + children: ReactNode; +} + +export function MoexVibeThemeProvider({ children }: MoexVibeThemeProviderProps) { + return ( + + + {children} + + ); +} diff --git a/packages/design-system/src/theme/createMoexVibeTheme.test.ts b/packages/design-system/src/theme/createMoexVibeTheme.test.ts new file mode 100644 index 0000000..2975a00 --- /dev/null +++ b/packages/design-system/src/theme/createMoexVibeTheme.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { createMoexVibeTheme } from './createMoexVibeTheme'; + +describe('createMoexVibeTheme', () => { + it('sets cssVarPrefix to mv', () => { + const theme = createMoexVibeTheme(); + expect(theme.cssVarPrefix).toBe('mv'); + }); + + it('maps color.action.primary to MUI primary main', () => { + const theme = createMoexVibeTheme(); + expect(theme.colorSchemes.light.palette.primary.main).toBe('#176747'); + }); + + it('uses Inter in typography', () => { + const theme = createMoexVibeTheme(); + expect(theme.typography.body1.fontFamily).toContain('Inter'); + }); + + it('maps radius.md to shape border radius', () => { + const theme = createMoexVibeTheme(); + expect(theme.shape.borderRadius).toBe(8); + }); + + it('disables elevation on MuiButton by default', () => { + const theme = createMoexVibeTheme(); + expect(theme.components?.MuiButton?.defaultProps).toMatchObject({ disableElevation: true }); + }); +}); diff --git a/packages/design-system/src/theme/createMoexVibeTheme.ts b/packages/design-system/src/theme/createMoexVibeTheme.ts new file mode 100644 index 0000000..a341a27 --- /dev/null +++ b/packages/design-system/src/theme/createMoexVibeTheme.ts @@ -0,0 +1,55 @@ +import { createTheme } from '@mui/material/styles'; +import { primitiveTokens, semanticLightTokens, componentTokens, resolveToken } from '../tokens'; + +const allTokens = { + ...primitiveTokens, + ...semanticLightTokens, + ...componentTokens, +} as const; + +function resolveValue(name: string): string | number | readonly number[] | readonly string[] { + return resolveToken(allTokens, name).value; +} + +export function createMoexVibeTheme() { + return createTheme({ + cssVariables: { cssVarPrefix: 'mv' }, + colorSchemes: { + light: { + palette: { + primary: { main: resolveValue('color.action.primary') as string }, + secondary: { main: resolveValue('color.action.secondary') as string }, + error: { main: resolveValue('color.feedback.error') as string }, + warning: { main: resolveValue('color.feedback.warning') as string }, + info: { main: resolveValue('color.feedback.info') as string }, + success: { main: resolveValue('color.finance.positive') as string }, + background: { + default: resolveValue('color.canvas') as string, + paper: resolveValue('color.surface.default') as string, + }, + text: { + primary: resolveValue('color.text.primary') as string, + secondary: resolveValue('color.text.secondary') as string, + disabled: resolveValue('color.text.disabled') as string, + }, + divider: resolveValue('color.border.default') as string, + }, + }, + }, + typography: { + fontFamily: (resolveValue('font.family.sans') as readonly string[]).join(', '), + body1: { fontFamily: (resolveValue('font.family.body') as readonly string[]).join(', ') }, + fontWeightRegular: resolveValue('font.weight.regular') as number, + fontWeightMedium: resolveValue('font.weight.medium') as number, + fontWeightBold: resolveValue('font.weight.bold') as number, + }, + shape: { + borderRadius: parseInt(resolveValue('radius.md') as string, 10), + }, + components: { + MuiButton: { + defaultProps: { disableElevation: true }, + }, + }, + }); +} diff --git a/packages/design-system/src/theme/index.ts b/packages/design-system/src/theme/index.ts new file mode 100644 index 0000000..c328165 --- /dev/null +++ b/packages/design-system/src/theme/index.ts @@ -0,0 +1,2 @@ +export { createMoexVibeTheme } from './createMoexVibeTheme'; +export { MoexVibeThemeProvider } from './MoexVibeThemeProvider'; diff --git a/packages/design-system/src/theme/mui.d.ts b/packages/design-system/src/theme/mui.d.ts new file mode 100644 index 0000000..8bd3daf --- /dev/null +++ b/packages/design-system/src/theme/mui.d.ts @@ -0,0 +1,18 @@ +import type {} from '@mui/material/themeCssVarsAugmentation'; + +declare module '@mui/material/styles' { + interface Palette { + finance: { + positive: string; + negative: string; + neutral: string; + }; + } + interface PaletteOptions { + finance?: { + positive: string; + negative: string; + neutral: string; + }; + } +}