From 63ec3ca1f7f542eb29d0b5eb3dc3a62ce6ddfc3b Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Thu, 25 Dec 2025 16:59:48 +0000 Subject: [PATCH] feat: refactor theme structure by adding design tokens, component overrides, and updating mui-theme for better customization chore: remove unused dependency 'cmdk' from package.json --- frontends/nextjs/package.json | 1 - frontends/nextjs/src/theme/components.ts | 103 +++ frontends/nextjs/src/theme/index.ts | 8 + frontends/nextjs/src/theme/mui-theme.ts | 837 ++--------------------- frontends/nextjs/src/theme/tokens.ts | 47 ++ 5 files changed, 204 insertions(+), 792 deletions(-) create mode 100644 frontends/nextjs/src/theme/components.ts create mode 100644 frontends/nextjs/src/theme/index.ts create mode 100644 frontends/nextjs/src/theme/tokens.ts diff --git a/frontends/nextjs/package.json b/frontends/nextjs/package.json index 6023089e8..5dd7c2470 100644 --- a/frontends/nextjs/package.json +++ b/frontends/nextjs/package.json @@ -70,7 +70,6 @@ "@prisma/client": "^6.19.1", "@tanstack/react-query": "^5.83.1", "@types/jszip": "^3.4.0", - "cmdk": "^1.1.1", "d3": "^7.9.0", "date-fns": "^3.6.0", "fengari-interop": "^0.1.4", diff --git a/frontends/nextjs/src/theme/components.ts b/frontends/nextjs/src/theme/components.ts new file mode 100644 index 000000000..9117469cd --- /dev/null +++ b/frontends/nextjs/src/theme/components.ts @@ -0,0 +1,103 @@ +/** Component style overrides for MUI */ + +import { ThemeOptions, alpha } from '@mui/material/styles' +import { colors, fonts } from './tokens' + +export const getComponentOverrides = (mode: 'light' | 'dark'): ThemeOptions['components'] => { + const isDark = mode === 'dark' + const n = colors.neutral + + return { + MuiCssBaseline: { + styleOverrides: { + ':root': { '--font-body': fonts.body, '--font-heading': fonts.heading, '--font-mono': fonts.mono }, + html: { scrollBehavior: 'smooth' }, + body: { WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale' }, + 'code, pre, kbd': { fontFamily: fonts.mono }, + '::selection': { backgroundColor: isDark ? colors.primary.dark.main : colors.primary.light.main, color: '#fff' }, + '::-webkit-scrollbar': { width: 8, height: 8 }, + '::-webkit-scrollbar-track': { backgroundColor: isDark ? n[800] : n[100] }, + '::-webkit-scrollbar-thumb': { backgroundColor: isDark ? n[600] : n[400], borderRadius: 4 }, + }, + }, + MuiButton: { + defaultProps: { disableElevation: true }, + styleOverrides: { + root: { borderRadius: 8, textTransform: 'none', fontWeight: 500, padding: '8px 16px' }, + sizeSmall: { padding: '4px 12px', fontSize: '0.8125rem' }, + sizeLarge: { padding: '12px 24px', fontSize: '0.9375rem' }, + containedPrimary: { + '&:hover': { boxShadow: `0 4px 12px ${alpha(isDark ? colors.primary.dark.main : colors.primary.light.main, 0.4)}` }, + }, + }, + }, + MuiCard: { + defaultProps: { elevation: 0 }, + styleOverrides: { + root: { + borderRadius: 12, + border: `1px solid ${isDark ? n[800] : n[200]}`, + backgroundImage: 'none', + }, + }, + }, + MuiCardHeader: { + styleOverrides: { + root: { padding: '20px 24px 16px' }, + title: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.125rem' }, + subheader: { fontSize: '0.875rem', color: isDark ? n[400] : n[600] }, + }, + }, + MuiCardContent: { styleOverrides: { root: { padding: '16px 24px', '&:last-child': { paddingBottom: 24 } } } }, + MuiCardActions: { styleOverrides: { root: { padding: '16px 24px', borderTop: `1px solid ${isDark ? n[800] : n[200]}` } } }, + MuiPaper: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundImage: 'none' }, outlined: { borderColor: isDark ? n[800] : n[200] } } }, + MuiTextField: { + defaultProps: { variant: 'outlined', size: 'small' }, + styleOverrides: { + root: { + '& .MuiOutlinedInput-root': { borderRadius: 8, backgroundColor: isDark ? n[900] : '#fff' }, + '& .MuiOutlinedInput-notchedOutline': { borderColor: isDark ? n[700] : n[300] }, + }, + }, + }, + MuiOutlinedInput: { styleOverrides: { root: { borderRadius: 8 } } }, + MuiSelect: { styleOverrides: { root: { borderRadius: 8 } } }, + MuiChip: { styleOverrides: { root: { borderRadius: 6, fontWeight: 500 }, sizeSmall: { height: 24 } } }, + MuiAlert: { + styleOverrides: { + root: { borderRadius: 8, alignItems: 'center' }, + standardSuccess: { backgroundColor: alpha(isDark ? colors.success.dark.main : colors.success.light.main, isDark ? 0.15 : 0.1) }, + standardError: { backgroundColor: alpha(isDark ? colors.error.dark.main : colors.error.light.main, isDark ? 0.15 : 0.1) }, + standardWarning: { backgroundColor: alpha(isDark ? colors.warning.dark.main : colors.warning.light.main, isDark ? 0.15 : 0.1) }, + standardInfo: { backgroundColor: alpha(isDark ? colors.info.dark.main : colors.info.light.main, isDark ? 0.15 : 0.1) }, + }, + }, + MuiDialog: { styleOverrides: { paper: { borderRadius: 16 } } }, + MuiDialogTitle: { styleOverrides: { root: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.25rem', padding: '24px 24px 16px' } } }, + MuiDialogContent: { styleOverrides: { root: { padding: '16px 24px' } } }, + MuiDialogActions: { styleOverrides: { root: { padding: '16px 24px 24px', gap: 8 } } }, + MuiTable: { styleOverrides: { root: { borderCollapse: 'separate', borderSpacing: 0 } } }, + MuiTableHead: { styleOverrides: { root: { '& .MuiTableCell-root': { fontWeight: 600, backgroundColor: isDark ? n[900] : n[50], borderBottom: `2px solid ${isDark ? n[700] : n[300]}` } } } }, + MuiTableCell: { styleOverrides: { root: { borderBottom: `1px solid ${isDark ? n[800] : n[200]}`, padding: '12px 16px' } } }, + MuiTableRow: { styleOverrides: { root: { '&:hover': { backgroundColor: isDark ? alpha(n[700], 0.3) : alpha(n[100], 0.5) } } } }, + MuiTabs: { styleOverrides: { root: { minHeight: 48 }, indicator: { height: 3, borderRadius: '3px 3px 0 0' } } }, + MuiTab: { styleOverrides: { root: { textTransform: 'none', fontWeight: 500, minHeight: 48, padding: '12px 16px' } } }, + MuiTooltip: { styleOverrides: { tooltip: { backgroundColor: isDark ? n[700] : n[900], fontSize: '0.75rem', padding: '6px 12px', borderRadius: 6 }, arrow: { color: isDark ? n[700] : n[900] } } }, + MuiMenu: { styleOverrides: { paper: { borderRadius: 8, border: `1px solid ${isDark ? n[800] : n[200]}` } } }, + MuiMenuItem: { styleOverrides: { root: { padding: '8px 16px', borderRadius: 4, margin: '2px 8px' } } }, + MuiDrawer: { styleOverrides: { paper: { borderRight: `1px solid ${isDark ? n[800] : n[200]}` } } }, + MuiAppBar: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundColor: isDark ? n[900] : '#fff', borderBottom: `1px solid ${isDark ? n[800] : n[200]}` } } }, + MuiDivider: { styleOverrides: { root: { borderColor: isDark ? n[800] : n[200] } } }, + MuiAvatar: { styleOverrides: { root: { fontFamily: fonts.heading, fontWeight: 600 } } }, + MuiBadge: { styleOverrides: { badge: { fontWeight: 600, fontSize: '0.6875rem' } } }, + MuiLinearProgress: { styleOverrides: { root: { borderRadius: 4, height: 6 } } }, + MuiSkeleton: { styleOverrides: { root: { borderRadius: 4, backgroundColor: isDark ? n[800] : n[200] } } }, + MuiAccordion: { defaultProps: { elevation: 0 }, styleOverrides: { root: { border: `1px solid ${isDark ? n[800] : n[200]}`, borderRadius: 8, '&:before': { display: 'none' } } } }, + MuiAccordionSummary: { styleOverrides: { root: { padding: '0 16px', minHeight: 56 }, content: { fontWeight: 500 } } }, + MuiAccordionDetails: { styleOverrides: { root: { padding: '0 16px 16px' } } }, + MuiSwitch: { styleOverrides: { root: { width: 46, height: 26, padding: 0 }, switchBase: { padding: 2, '&.Mui-checked': { transform: 'translateX(20px)' } }, thumb: { width: 22, height: 22 }, track: { borderRadius: 13, opacity: 1, backgroundColor: isDark ? n[700] : n[300] } } }, + MuiIconButton: { styleOverrides: { root: { borderRadius: 8 } } }, + MuiListItemButton: { styleOverrides: { root: { borderRadius: 8, margin: '2px 8px', padding: '8px 12px' } } }, + MuiSnackbar: { styleOverrides: { root: { '& .MuiPaper-root': { borderRadius: 8 } } } }, + } +} diff --git a/frontends/nextjs/src/theme/index.ts b/frontends/nextjs/src/theme/index.ts new file mode 100644 index 000000000..65b3ab692 --- /dev/null +++ b/frontends/nextjs/src/theme/index.ts @@ -0,0 +1,8 @@ +/** + * Theme Module - Central export for all theme functionality + */ + +export { lightTheme, darkTheme, colors, fonts, layout } from './mui-theme' +export { useTheme, styled, alpha } from '@mui/material/styles' +export type { Theme, SxProps } from '@mui/material/styles' + diff --git a/frontends/nextjs/src/theme/mui-theme.ts b/frontends/nextjs/src/theme/mui-theme.ts index f9dd8bc4e..afabf5cdf 100644 --- a/frontends/nextjs/src/theme/mui-theme.ts +++ b/frontends/nextjs/src/theme/mui-theme.ts @@ -1,753 +1,41 @@ 'use client' -import { createTheme, ThemeOptions, alpha, Theme } from '@mui/material/styles' -import './theme.d.ts' // Import type extensions - -// ======================================== -// Design Tokens - Unified color system -// ======================================== - -// Primary colors - Purple/Magenta accent -const colors = { - primary: { - light: { - main: '#8b5cf6', - light: '#a78bfa', - dark: '#7c3aed', - contrastText: '#ffffff', - }, - dark: { - main: '#a78bfa', - light: '#c4b5fd', - dark: '#8b5cf6', - contrastText: '#ffffff', - }, - }, - secondary: { - light: { - main: '#f59e0b', - light: '#fbbf24', - dark: '#d97706', - contrastText: '#ffffff', - }, - dark: { - main: '#fbbf24', - light: '#fcd34d', - dark: '#f59e0b', - contrastText: '#000000', - }, - }, - error: { - light: { main: '#ef4444', light: '#f87171', dark: '#dc2626' }, - dark: { main: '#f87171', light: '#fca5a5', dark: '#ef4444' }, - }, - warning: { - light: { main: '#f59e0b', light: '#fbbf24', dark: '#d97706' }, - dark: { main: '#fbbf24', light: '#fcd34d', dark: '#f59e0b' }, - }, - info: { - light: { main: '#06b6d4', light: '#22d3ee', dark: '#0891b2' }, - dark: { main: '#22d3ee', light: '#67e8f9', dark: '#06b6d4' }, - }, - success: { - light: { main: '#10b981', light: '#34d399', dark: '#059669' }, - dark: { main: '#34d399', light: '#6ee7b7', dark: '#10b981' }, - }, - // Neutral grays - neutral: { - 50: '#fafafa', - 100: '#f4f4f5', - 200: '#e4e4e7', - 300: '#d4d4d8', - 400: '#a1a1aa', - 500: '#71717a', - 600: '#52525b', - 700: '#3f3f46', - 800: '#27272a', - 900: '#18181b', - 950: '#09090b', - }, -} - -// ======================================== -// Typography System -// ======================================== - -const fontFamilyBody = [ - 'IBM Plex Sans', - '-apple-system', - 'BlinkMacSystemFont', - 'Segoe UI', - 'Roboto', - 'Helvetica Neue', - 'Arial', - 'sans-serif', -].join(',') - -const fontFamilyHeading = ['Space Grotesk', 'IBM Plex Sans', 'sans-serif'].join(',') -const fontFamilyMono = ['JetBrains Mono', 'Consolas', 'Monaco', 'monospace'].join(',') +import { createTheme, alpha, ThemeOptions } from '@mui/material/styles' +import { colors, fonts, layout } from './tokens' +import { getComponentOverrides } from './components' const typography = { - fontFamily: fontFamilyBody, - h1: { - fontFamily: fontFamilyHeading, - fontWeight: 700, - fontSize: '3rem', // 48px - lineHeight: 1.2, - letterSpacing: '-0.02em', - }, - h2: { - fontFamily: fontFamilyHeading, - fontWeight: 700, - fontSize: '2.25rem', // 36px - lineHeight: 1.25, - letterSpacing: '-0.01em', - }, - h3: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1.875rem', // 30px - lineHeight: 1.3, - }, - h4: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1.5rem', // 24px - lineHeight: 1.35, - }, - h5: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1.25rem', // 20px - lineHeight: 1.4, - }, - h6: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1rem', // 16px - lineHeight: 1.5, - }, - subtitle1: { - fontFamily: fontFamilyBody, - fontWeight: 500, - fontSize: '1rem', - lineHeight: 1.5, - }, - subtitle2: { - fontFamily: fontFamilyBody, - fontWeight: 500, - fontSize: '0.875rem', - lineHeight: 1.5, - }, - body1: { - fontFamily: fontFamilyBody, - fontWeight: 400, - fontSize: '1rem', - lineHeight: 1.6, - }, - body2: { - fontFamily: fontFamilyBody, - fontWeight: 400, - fontSize: '0.875rem', - lineHeight: 1.5, - }, - caption: { - fontFamily: fontFamilyBody, - fontWeight: 400, - fontSize: '0.75rem', - lineHeight: 1.4, - }, - overline: { - fontFamily: fontFamilyBody, - fontWeight: 600, - fontSize: '0.75rem', - lineHeight: 1.5, - letterSpacing: '0.08em', - textTransform: 'uppercase' as const, - }, - button: { - fontFamily: fontFamilyBody, - fontWeight: 500, - fontSize: '0.875rem', - textTransform: 'none' as const, - letterSpacing: '0.01em', - }, + fontFamily: fonts.body, + h1: { fontFamily: fonts.heading, fontWeight: 700, fontSize: '3rem', lineHeight: 1.2, letterSpacing: '-0.02em' }, + h2: { fontFamily: fonts.heading, fontWeight: 700, fontSize: '2.25rem', lineHeight: 1.25, letterSpacing: '-0.01em' }, + h3: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.875rem', lineHeight: 1.3 }, + h4: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.5rem', lineHeight: 1.35 }, + h5: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1.25rem', lineHeight: 1.4 }, + h6: { fontFamily: fonts.heading, fontWeight: 600, fontSize: '1rem', lineHeight: 1.5 }, + subtitle1: { fontWeight: 500, fontSize: '1rem', lineHeight: 1.5 }, + subtitle2: { fontWeight: 500, fontSize: '0.875rem', lineHeight: 1.5 }, + body1: { fontSize: '1rem', lineHeight: 1.6 }, + body2: { fontSize: '0.875rem', lineHeight: 1.5 }, + caption: { fontSize: '0.75rem', lineHeight: 1.4 }, + overline: { fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.08em', textTransform: 'uppercase' as const }, + button: { fontWeight: 500, fontSize: '0.875rem', textTransform: 'none' as const }, + code: { fontFamily: fonts.mono, fontSize: '0.875rem', backgroundColor: 'rgba(0,0,0,0.06)', padding: '2px 6px', borderRadius: 4 }, + kbd: { fontFamily: fonts.mono, fontSize: '0.75rem', fontWeight: 500, padding: '2px 8px', borderRadius: 4, border: '1px solid' }, + label: { fontSize: '0.875rem', fontWeight: 500, lineHeight: 1.5 }, } -// ======================================== -// Spacing & Shape -// ======================================== - -const spacing = 8 // Base 8px grid - -const shape = { - borderRadius: 8, -} - -// ======================================== -// Shadows System -// ======================================== - -const lightShadows = [ +const shadows = (opacity: number) => [ 'none', - '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)', - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', - '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', - '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', - '0 25px 50px -12px rgba(0, 0, 0, 0.25)', - // Remaining slots (7-24) - using progressive shadows - ...Array(18).fill('0 25px 50px -12px rgba(0, 0, 0, 0.25)'), + `0 1px 2px rgba(0,0,0,${opacity * 0.5})`, + `0 1px 3px rgba(0,0,0,${opacity}), 0 1px 2px rgba(0,0,0,${opacity})`, + `0 4px 6px rgba(0,0,0,${opacity}), 0 2px 4px rgba(0,0,0,${opacity})`, + `0 10px 15px rgba(0,0,0,${opacity}), 0 4px 6px rgba(0,0,0,${opacity})`, + `0 20px 25px rgba(0,0,0,${opacity}), 0 8px 10px rgba(0,0,0,${opacity})`, + `0 25px 50px rgba(0,0,0,${opacity * 2.5})`, + ...Array(18).fill(`0 25px 50px rgba(0,0,0,${opacity * 2.5})`), ] as ThemeOptions['shadows'] -const darkShadows = [ - 'none', - '0 1px 2px 0 rgba(0, 0, 0, 0.3)', - '0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.3)', - '0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3)', - '0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.3)', - '0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3)', - '0 25px 50px -12px rgba(0, 0, 0, 0.5)', - ...Array(18).fill('0 25px 50px -12px rgba(0, 0, 0, 0.5)'), -] as ThemeOptions['shadows'] - -// ======================================== -// Z-Index Scale -// ======================================== - -const zIndex = { - mobileStepper: 1000, - fab: 1050, - speedDial: 1050, - appBar: 1100, - drawer: 1200, - modal: 1300, - snackbar: 1400, - tooltip: 1500, -} - -// ======================================== -// Transitions -// ======================================== - -const transitions = { - duration: { - shortest: 150, - shorter: 200, - short: 250, - standard: 300, - complex: 375, - enteringScreen: 225, - leavingScreen: 195, - }, - easing: { - easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)', - easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)', - easeIn: 'cubic-bezier(0.4, 0, 1, 1)', - sharp: 'cubic-bezier(0.4, 0, 0.6, 1)', - }, -} - -// ======================================== -// Component Overrides -// ======================================== - -const getComponentOverrides = (mode: 'light' | 'dark'): ThemeOptions['components'] => { - const isDark = mode === 'dark' - const neutral = colors.neutral - - return { - MuiCssBaseline: { - styleOverrides: { - ':root': { - '--font-family-body': fontFamilyBody, - '--font-family-heading': fontFamilyHeading, - '--font-family-mono': fontFamilyMono, - }, - html: { - scrollBehavior: 'smooth', - }, - body: { - WebkitFontSmoothing: 'antialiased', - MozOsxFontSmoothing: 'grayscale', - }, - 'code, pre, kbd': { - fontFamily: fontFamilyMono, - }, - '::selection': { - backgroundColor: isDark ? colors.primary.dark.main : colors.primary.light.main, - color: '#ffffff', - }, - // Custom scrollbar - '::-webkit-scrollbar': { - width: '8px', - height: '8px', - }, - '::-webkit-scrollbar-track': { - backgroundColor: isDark ? neutral[800] : neutral[100], - }, - '::-webkit-scrollbar-thumb': { - backgroundColor: isDark ? neutral[600] : neutral[400], - borderRadius: '4px', - '&:hover': { - backgroundColor: isDark ? neutral[500] : neutral[500], - }, - }, - }, - }, - MuiButton: { - defaultProps: { - disableElevation: true, - }, - styleOverrides: { - root: { - borderRadius: 8, - textTransform: 'none', - fontWeight: 500, - padding: '8px 16px', - transition: 'all 200ms ease-in-out', - }, - sizeSmall: { - padding: '4px 12px', - fontSize: '0.8125rem', - }, - sizeLarge: { - padding: '12px 24px', - fontSize: '0.9375rem', - }, - containedPrimary: { - '&:hover': { - boxShadow: `0 4px 12px ${alpha(isDark ? colors.primary.dark.main : colors.primary.light.main, 0.4)}`, - }, - }, - }, - }, - MuiCard: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - borderRadius: 12, - border: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - backgroundImage: 'none', - transition: 'box-shadow 200ms ease-in-out, border-color 200ms ease-in-out', - '&:hover': { - boxShadow: isDark - ? '0 4px 12px rgba(0, 0, 0, 0.4)' - : '0 4px 12px rgba(0, 0, 0, 0.1)', - }, - }, - }, - }, - MuiCardHeader: { - styleOverrides: { - root: { - padding: '20px 24px 16px', - }, - title: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1.125rem', - }, - subheader: { - fontSize: '0.875rem', - color: isDark ? neutral[400] : neutral[600], - }, - }, - }, - MuiCardContent: { - styleOverrides: { - root: { - padding: '16px 24px', - '&:last-child': { - paddingBottom: '24px', - }, - }, - }, - }, - MuiCardActions: { - styleOverrides: { - root: { - padding: '16px 24px', - borderTop: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - }, - }, - }, - MuiPaper: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - backgroundImage: 'none', - }, - outlined: { - borderColor: isDark ? neutral[800] : neutral[200], - }, - }, - }, - MuiTextField: { - defaultProps: { - variant: 'outlined', - size: 'small', - }, - styleOverrides: { - root: { - '& .MuiOutlinedInput-root': { - borderRadius: 8, - backgroundColor: isDark ? neutral[900] : '#ffffff', - transition: 'border-color 200ms ease-in-out, box-shadow 200ms ease-in-out', - '&:hover .MuiOutlinedInput-notchedOutline': { - borderColor: isDark ? neutral[600] : neutral[400], - }, - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderWidth: 2, - }, - }, - '& .MuiOutlinedInput-notchedOutline': { - borderColor: isDark ? neutral[700] : neutral[300], - }, - }, - }, - }, - MuiOutlinedInput: { - styleOverrides: { - root: { - borderRadius: 8, - }, - }, - }, - MuiSelect: { - styleOverrides: { - root: { - borderRadius: 8, - }, - }, - }, - MuiChip: { - styleOverrides: { - root: { - borderRadius: 6, - fontWeight: 500, - }, - sizeSmall: { - height: 24, - }, - }, - }, - MuiAlert: { - styleOverrides: { - root: { - borderRadius: 8, - alignItems: 'center', - }, - standardSuccess: { - backgroundColor: isDark ? alpha(colors.success.dark.main, 0.15) : alpha(colors.success.light.main, 0.1), - color: isDark ? colors.success.dark.light : colors.success.light.dark, - }, - standardError: { - backgroundColor: isDark ? alpha(colors.error.dark.main, 0.15) : alpha(colors.error.light.main, 0.1), - color: isDark ? colors.error.dark.light : colors.error.light.dark, - }, - standardWarning: { - backgroundColor: isDark ? alpha(colors.warning.dark.main, 0.15) : alpha(colors.warning.light.main, 0.1), - color: isDark ? colors.warning.dark.dark : colors.warning.light.dark, - }, - standardInfo: { - backgroundColor: isDark ? alpha(colors.info.dark.main, 0.15) : alpha(colors.info.light.main, 0.1), - color: isDark ? colors.info.dark.light : colors.info.light.dark, - }, - }, - }, - MuiDialog: { - styleOverrides: { - paper: { - borderRadius: 16, - boxShadow: isDark ? '0 25px 50px -12px rgba(0, 0, 0, 0.5)' : '0 25px 50px -12px rgba(0, 0, 0, 0.25)', - }, - }, - }, - MuiDialogTitle: { - styleOverrides: { - root: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - fontSize: '1.25rem', - padding: '24px 24px 16px', - }, - }, - }, - MuiDialogContent: { - styleOverrides: { - root: { - padding: '16px 24px', - }, - }, - }, - MuiDialogActions: { - styleOverrides: { - root: { - padding: '16px 24px 24px', - gap: 8, - }, - }, - }, - MuiTable: { - styleOverrides: { - root: { - borderCollapse: 'separate', - borderSpacing: 0, - }, - }, - }, - MuiTableHead: { - styleOverrides: { - root: { - '& .MuiTableCell-root': { - fontWeight: 600, - backgroundColor: isDark ? neutral[900] : neutral[50], - borderBottom: `2px solid ${isDark ? neutral[700] : neutral[300]}`, - }, - }, - }, - }, - MuiTableCell: { - styleOverrides: { - root: { - borderBottom: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - padding: '12px 16px', - }, - }, - }, - MuiTableRow: { - styleOverrides: { - root: { - '&:hover': { - backgroundColor: isDark ? alpha(neutral[700], 0.3) : alpha(neutral[100], 0.5), - }, - }, - }, - }, - MuiTabs: { - styleOverrides: { - root: { - minHeight: 48, - }, - indicator: { - height: 3, - borderRadius: '3px 3px 0 0', - }, - }, - }, - MuiTab: { - styleOverrides: { - root: { - textTransform: 'none', - fontWeight: 500, - minHeight: 48, - padding: '12px 16px', - }, - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - backgroundColor: isDark ? neutral[700] : neutral[900], - color: isDark ? neutral[100] : '#ffffff', - fontSize: '0.75rem', - padding: '6px 12px', - borderRadius: 6, - }, - arrow: { - color: isDark ? neutral[700] : neutral[900], - }, - }, - }, - MuiMenu: { - styleOverrides: { - paper: { - borderRadius: 8, - boxShadow: isDark - ? '0 10px 40px rgba(0, 0, 0, 0.5)' - : '0 10px 40px rgba(0, 0, 0, 0.15)', - border: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - }, - }, - }, - MuiMenuItem: { - styleOverrides: { - root: { - padding: '8px 16px', - borderRadius: 4, - margin: '2px 8px', - '&:hover': { - backgroundColor: isDark ? neutral[800] : neutral[100], - }, - }, - }, - }, - MuiDrawer: { - styleOverrides: { - paper: { - borderRight: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - }, - }, - }, - MuiAppBar: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - backgroundColor: isDark ? neutral[900] : '#ffffff', - borderBottom: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - }, - }, - }, - MuiDivider: { - styleOverrides: { - root: { - borderColor: isDark ? neutral[800] : neutral[200], - }, - }, - }, - MuiAvatar: { - styleOverrides: { - root: { - fontFamily: fontFamilyHeading, - fontWeight: 600, - }, - }, - }, - MuiBadge: { - styleOverrides: { - badge: { - fontWeight: 600, - fontSize: '0.6875rem', - }, - }, - }, - MuiLinearProgress: { - styleOverrides: { - root: { - borderRadius: 4, - height: 6, - }, - }, - }, - MuiCircularProgress: { - styleOverrides: { - root: { - strokeLinecap: 'round', - }, - }, - }, - MuiSkeleton: { - styleOverrides: { - root: { - borderRadius: 4, - backgroundColor: isDark ? neutral[800] : neutral[200], - }, - }, - }, - MuiAccordion: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - border: `1px solid ${isDark ? neutral[800] : neutral[200]}`, - borderRadius: 8, - '&:before': { - display: 'none', - }, - '&.Mui-expanded': { - margin: 0, - }, - }, - }, - }, - MuiAccordionSummary: { - styleOverrides: { - root: { - padding: '0 16px', - minHeight: 56, - '&.Mui-expanded': { - minHeight: 56, - }, - }, - content: { - fontWeight: 500, - '&.Mui-expanded': { - margin: '12px 0', - }, - }, - }, - }, - MuiAccordionDetails: { - styleOverrides: { - root: { - padding: '0 16px 16px', - }, - }, - }, - MuiSwitch: { - styleOverrides: { - root: { - width: 46, - height: 26, - padding: 0, - }, - switchBase: { - padding: 2, - '&.Mui-checked': { - transform: 'translateX(20px)', - }, - }, - thumb: { - width: 22, - height: 22, - }, - track: { - borderRadius: 13, - opacity: 1, - backgroundColor: isDark ? neutral[700] : neutral[300], - }, - }, - }, - MuiIconButton: { - styleOverrides: { - root: { - borderRadius: 8, - transition: 'background-color 200ms ease-in-out', - }, - }, - }, - MuiListItemButton: { - styleOverrides: { - root: { - borderRadius: 8, - margin: '2px 8px', - padding: '8px 12px', - }, - }, - }, - MuiSnackbar: { - styleOverrides: { - root: { - '& .MuiPaper-root': { - borderRadius: 8, - }, - }, - }, - }, - } -} - -// ======================================== -// Create Themes -// ======================================== +const custom = { fonts, borderRadius: layout.borderRadius, contentWidth: layout.contentWidth, sidebar: layout.sidebar, header: layout.header } export const lightTheme = createTheme({ palette: { @@ -758,31 +46,18 @@ export const lightTheme = createTheme({ warning: colors.warning.light, info: colors.info.light, success: colors.success.light, - background: { - default: '#ffffff', - paper: colors.neutral[50], - }, - text: { - primary: colors.neutral[900], - secondary: colors.neutral[600], - disabled: colors.neutral[400], - }, + neutral: colors.neutral, + background: { default: '#ffffff', paper: colors.neutral[50] }, + text: { primary: colors.neutral[900], secondary: colors.neutral[600], disabled: colors.neutral[400] }, divider: colors.neutral[200], - action: { - active: colors.neutral[700], - hover: alpha(colors.neutral[500], 0.08), - selected: alpha(colors.primary.light.main, 0.12), - disabled: colors.neutral[400], - disabledBackground: colors.neutral[200], - }, + action: { active: colors.neutral[700], hover: alpha(colors.neutral[500], 0.08), selected: alpha(colors.primary.light.main, 0.12), disabled: colors.neutral[400], disabledBackground: colors.neutral[200] }, }, typography, - spacing, - shape, - shadows: lightShadows, - zIndex, - transitions, + spacing: layout.spacing, + shape: { borderRadius: 8 }, + shadows: shadows(0.1), components: getComponentOverrides('light'), + custom, }) export const darkTheme = createTheme({ @@ -794,38 +69,18 @@ export const darkTheme = createTheme({ warning: colors.warning.dark, info: colors.info.dark, success: colors.success.dark, - background: { - default: colors.neutral[950], - paper: colors.neutral[900], - }, - text: { - primary: colors.neutral[100], - secondary: colors.neutral[400], - disabled: colors.neutral[600], - }, + neutral: colors.neutral, + background: { default: colors.neutral[950], paper: colors.neutral[900] }, + text: { primary: colors.neutral[100], secondary: colors.neutral[400], disabled: colors.neutral[600] }, divider: colors.neutral[800], - action: { - active: colors.neutral[300], - hover: alpha(colors.neutral[400], 0.12), - selected: alpha(colors.primary.dark.main, 0.2), - disabled: colors.neutral[600], - disabledBackground: colors.neutral[800], - }, + action: { active: colors.neutral[300], hover: alpha(colors.neutral[400], 0.12), selected: alpha(colors.primary.dark.main, 0.2), disabled: colors.neutral[600], disabledBackground: colors.neutral[800] }, }, typography, - spacing, - shape, - shadows: darkShadows, - zIndex, - transitions, + spacing: layout.spacing, + shape: { borderRadius: 8 }, + shadows: shadows(0.4), components: getComponentOverrides('dark'), + custom, }) -// Export design tokens for SCSS interop -export const designTokens = { - colors, - fontFamilyBody, - fontFamilyHeading, - fontFamilyMono, - spacing, -} +export { colors, fonts, layout } from './tokens' diff --git a/frontends/nextjs/src/theme/tokens.ts b/frontends/nextjs/src/theme/tokens.ts new file mode 100644 index 000000000..90cd4d977 --- /dev/null +++ b/frontends/nextjs/src/theme/tokens.ts @@ -0,0 +1,47 @@ +/** Design tokens - colors, spacing, typography */ + +export const colors = { + primary: { + light: { main: '#8b5cf6', light: '#a78bfa', dark: '#7c3aed', contrastText: '#ffffff' }, + dark: { main: '#a78bfa', light: '#c4b5fd', dark: '#8b5cf6', contrastText: '#ffffff' }, + }, + secondary: { + light: { main: '#f59e0b', light: '#fbbf24', dark: '#d97706', contrastText: '#ffffff' }, + dark: { main: '#fbbf24', light: '#fcd34d', dark: '#f59e0b', contrastText: '#000000' }, + }, + error: { + light: { main: '#ef4444', light: '#f87171', dark: '#dc2626' }, + dark: { main: '#f87171', light: '#fca5a5', dark: '#ef4444' }, + }, + warning: { + light: { main: '#f59e0b', light: '#fbbf24', dark: '#d97706' }, + dark: { main: '#fbbf24', light: '#fcd34d', dark: '#f59e0b' }, + }, + info: { + light: { main: '#06b6d4', light: '#22d3ee', dark: '#0891b2' }, + dark: { main: '#22d3ee', light: '#67e8f9', dark: '#06b6d4' }, + }, + success: { + light: { main: '#10b981', light: '#34d399', dark: '#059669' }, + dark: { main: '#34d399', light: '#6ee7b7', dark: '#10b981' }, + }, + neutral: { + 50: '#fafafa', 100: '#f4f4f5', 200: '#e4e4e7', 300: '#d4d4d8', + 400: '#a1a1aa', 500: '#71717a', 600: '#52525b', 700: '#3f3f46', + 800: '#27272a', 900: '#18181b', 950: '#09090b', + }, +} + +export const fonts = { + body: ['IBM Plex Sans', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'].join(','), + heading: ['Space Grotesk', 'IBM Plex Sans', 'sans-serif'].join(','), + mono: ['JetBrains Mono', 'Consolas', 'Monaco', 'monospace'].join(','), +} + +export const layout = { + spacing: 8, + borderRadius: { none: 0, sm: 4, md: 8, lg: 12, xl: 16, full: 9999 }, + contentWidth: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', full: '100%' }, + sidebar: { width: 280, collapsedWidth: 72 }, + header: { height: 64 }, +}