341 lines
13 KiB
TypeScript
341 lines
13 KiB
TypeScript
import { type PropsWithChildren, useMemo } from 'react'
|
|
import CssBaseline from '@mui/material/CssBaseline'
|
|
import { ThemeProvider, createTheme } from '@mui/material/styles'
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { ThemeControllerProvider, useThemeController } from '@/app/providers/theme-controller'
|
|
import { SseProvider } from './SseProvider'
|
|
|
|
function AppThemeInner({ children }: PropsWithChildren) {
|
|
const controller = useThemeController()
|
|
const isDark = controller.resolvedMode === 'dark'
|
|
|
|
const theme = useMemo(
|
|
() =>
|
|
createTheme({
|
|
palette: (() => {
|
|
const common = { mode: controller.resolvedMode }
|
|
|
|
const text = isDark
|
|
? { primary: '#F2F2F2', secondary: 'rgba(242,242,242,0.72)', disabled: 'rgba(242,242,242,0.48)' }
|
|
: { primary: '#1F1B16', secondary: 'rgba(31,27,22,0.72)', disabled: 'rgba(31,27,22,0.48)' }
|
|
|
|
const chip = isDark ? { default: '#0E1510', paper: '#121B14' } : { default: '#F6FAF6', paper: '#FFFFFF' }
|
|
|
|
switch (controller.scheme) {
|
|
case 'forest':
|
|
return {
|
|
...common,
|
|
primary: { main: isDark ? '#8FBC8F' : '#2E8B57' },
|
|
secondary: { main: isDark ? '#CD853F' : '#8B4513' },
|
|
info: { main: isDark ? '#4682B4' : '#1E90FF' },
|
|
success: { main: isDark ? '#90EE90' : '#32CD32' },
|
|
warning: { main: isDark ? '#FFD700' : '#FFA500' },
|
|
error: { main: isDark ? '#F08080' : '#CD5C5C' },
|
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
|
text,
|
|
chip,
|
|
background: isDark
|
|
? { default: '#0F1720', paper: '#1A242E' }
|
|
: { default: '#F8F6F3', paper: '#FFFFFF' },
|
|
}
|
|
case 'ocean':
|
|
return {
|
|
...common,
|
|
primary: { main: isDark ? '#5F9EA0' : '#20B2AA' },
|
|
secondary: { main: isDark ? '#7B68EE' : '#6A5ACD' },
|
|
info: { main: isDark ? '#87CEEB' : '#00BFFF' },
|
|
success: { main: isDark ? '#98FB98' : '#00FA9A' },
|
|
warning: { main: isDark ? '#FFE4B5' : '#FFDAB9' },
|
|
error: { main: isDark ? '#FF6347' : '#FF4500' },
|
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
|
text,
|
|
chip,
|
|
background: isDark
|
|
? { default: '#0A1A2A', paper: '#0F1D35' }
|
|
: { default: '#F0F8FF', paper: '#FFFFFF' },
|
|
}
|
|
case 'berry':
|
|
return {
|
|
...common,
|
|
primary: { main: isDark ? '#9370DB' : '#8A2BE2' },
|
|
secondary: { main: isDark ? '#FF69B4' : '#FF1493' },
|
|
info: { main: isDark ? '#00CED1' : '#00BFFF' },
|
|
success: { main: isDark ? '#00FF7F' : '#7CFC00' },
|
|
warning: { main: isDark ? '#FFD700' : '#FFA500' },
|
|
error: { main: isDark ? '#FF4500' : '#FF6347' },
|
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
|
text,
|
|
chip,
|
|
background: isDark
|
|
? { default: '#1A0A1A', paper: '#250E25' }
|
|
: { default: '#FFF0F5', paper: '#FFFFFF' },
|
|
}
|
|
case 'craft':
|
|
default:
|
|
return {
|
|
...common,
|
|
primary: { main: isDark ? '#90A4AE' : '#546E7A' },
|
|
secondary: { main: isDark ? '#78909C' : '#78909C' },
|
|
info: { main: isDark ? '#7986CB' : '#3F51B5' },
|
|
success: { main: isDark ? '#66BB6A' : '#43A047' },
|
|
warning: { main: isDark ? '#FFB74D' : '#F57C00' },
|
|
error: { main: isDark ? '#EF5350' : '#D32F2F' },
|
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
|
text,
|
|
chip,
|
|
background: isDark
|
|
? { default: '#121212', paper: '#1E1E1E' }
|
|
: { default: '#F5F5F5', paper: '#FFFFFF' },
|
|
}
|
|
}
|
|
})(),
|
|
shape: { borderRadius: 12 },
|
|
typography: {
|
|
fontFamily: '"Outfit", "Segoe UI", system-ui, sans-serif',
|
|
h1: { fontWeight: 700, letterSpacing: '-1px', lineHeight: 1.1, textWrap: 'balance' },
|
|
h2: { fontWeight: 700, letterSpacing: '-0.75px', lineHeight: 1.15, textWrap: 'balance' },
|
|
h3: { fontWeight: 700, letterSpacing: '-0.5px', lineHeight: 1.2, textWrap: 'balance' },
|
|
h4: { fontWeight: 700, letterSpacing: '-0.5px', textWrap: 'balance' },
|
|
h5: { fontWeight: 600, letterSpacing: '-0.25px', textWrap: 'balance' },
|
|
h6: { fontWeight: 600, textWrap: 'balance' },
|
|
subtitle1: { fontWeight: 600 },
|
|
subtitle2: { fontWeight: 500 },
|
|
body1: { fontSize: '0.875rem', lineHeight: 1.6 },
|
|
body2: { fontSize: '0.75rem', lineHeight: 1.5 },
|
|
button: { textTransform: 'none', fontWeight: 600 },
|
|
},
|
|
components: {
|
|
MuiButton: {
|
|
styleOverrides: {
|
|
root: {
|
|
textTransform: 'none',
|
|
borderRadius: 12,
|
|
fontWeight: 600,
|
|
transition: 'all 0.2s ease-in-out',
|
|
'&:focus-visible': {
|
|
outline: '2px solid currentColor',
|
|
outlineOffset: 2,
|
|
},
|
|
},
|
|
contained: {
|
|
boxShadow: '0 4px 14px 0 rgba(0,0,0,0.12)',
|
|
'&:hover': {
|
|
boxShadow: '0 6px 20px 0 rgba(0,0,0,0.18)',
|
|
transform: 'translateY(-2px)',
|
|
},
|
|
'&:active': {
|
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.12)',
|
|
transform: 'translateY(0) scale(0.98)',
|
|
},
|
|
},
|
|
outlined: {
|
|
border: '1px solid',
|
|
'&:hover': {
|
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.08)',
|
|
},
|
|
'&:active': {
|
|
boxShadow: 'none',
|
|
transform: 'scale(0.98)',
|
|
},
|
|
},
|
|
text: {
|
|
'&:hover': {
|
|
backgroundColor: 'action.hover',
|
|
},
|
|
'&:active': {
|
|
backgroundColor: 'action.selected',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiIconButton: {
|
|
styleOverrides: {
|
|
root: {
|
|
transition: 'all 0.2s ease-in-out',
|
|
'&:hover': {
|
|
backgroundColor: 'action.hover',
|
|
transform: 'scale(1.08)',
|
|
},
|
|
'&:active': {
|
|
backgroundColor: 'action.selected',
|
|
transform: 'scale(0.95)',
|
|
},
|
|
'&:focus-visible': {
|
|
outline: '2px solid currentColor',
|
|
outlineOffset: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiCard: {
|
|
styleOverrides: {
|
|
root: {
|
|
'&:focus-visible': {
|
|
outline: '2px solid currentColor',
|
|
outlineOffset: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiLink: {
|
|
styleOverrides: {
|
|
root: {
|
|
'&:focus-visible': {
|
|
outline: '2px solid currentColor',
|
|
outlineOffset: 2,
|
|
borderRadius: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiInputBase: {
|
|
styleOverrides: {
|
|
root: {
|
|
'&.Mui-focused': {
|
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
borderWidth: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiAlert: {
|
|
styleOverrides: {
|
|
root: {
|
|
borderRadius: 12,
|
|
border: '1px solid',
|
|
boxShadow: 'none',
|
|
fontWeight: 500,
|
|
'& .MuiAlert-icon': {
|
|
padding: 0,
|
|
},
|
|
},
|
|
standardSuccess: {
|
|
bgcolor: isDark ? 'rgba(102,187,106,0.08)' : '#EDF3EC',
|
|
borderColor: isDark ? 'rgba(102,187,106,0.2)' : '#C5DFC2',
|
|
color: isDark ? '#A5D6A7' : '#346538',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#A5D6A7' : '#346538',
|
|
},
|
|
},
|
|
standardError: {
|
|
bgcolor: isDark ? 'rgba(239,83,80,0.08)' : '#FDEBEC',
|
|
borderColor: isDark ? 'rgba(239,83,80,0.2)' : '#F5C6C7',
|
|
color: isDark ? '#EF9A9A' : '#9F2F2D',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#EF9A9A' : '#9F2F2D',
|
|
},
|
|
},
|
|
standardWarning: {
|
|
bgcolor: isDark ? 'rgba(255,183,77,0.08)' : '#FBF3DB',
|
|
borderColor: isDark ? 'rgba(255,183,77,0.2)' : '#F0DCA0',
|
|
color: isDark ? '#FFD54F' : '#956400',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#FFD54F' : '#956400',
|
|
},
|
|
},
|
|
standardInfo: {
|
|
bgcolor: isDark ? 'rgba(121,134,203,0.08)' : '#E1F3FE',
|
|
borderColor: isDark ? 'rgba(121,134,203,0.2)' : '#B8D8F0',
|
|
color: isDark ? '#9FA8DA' : '#1F6C9F',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#9FA8DA' : '#1F6C9F',
|
|
},
|
|
},
|
|
outlinedSuccess: {
|
|
borderColor: isDark ? 'rgba(102,187,106,0.3)' : '#C5DFC2',
|
|
color: isDark ? '#A5D6A7' : '#346538',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#A5D6A7' : '#346538',
|
|
},
|
|
},
|
|
outlinedError: {
|
|
borderColor: isDark ? 'rgba(239,83,80,0.3)' : '#F5C6C7',
|
|
color: isDark ? '#EF9A9A' : '#9F2F2D',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#EF9A9A' : '#9F2F2D',
|
|
},
|
|
},
|
|
outlinedWarning: {
|
|
borderColor: isDark ? 'rgba(255,183,77,0.3)' : '#F0DCA0',
|
|
color: isDark ? '#FFD54F' : '#956400',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#FFD54F' : '#956400',
|
|
},
|
|
},
|
|
outlinedInfo: {
|
|
borderColor: isDark ? 'rgba(121,134,203,0.3)' : '#B8D8F0',
|
|
color: isDark ? '#9FA8DA' : '#1F6C9F',
|
|
'& .MuiAlert-icon': {
|
|
color: isDark ? '#9FA8DA' : '#1F6C9F',
|
|
},
|
|
},
|
|
filledSuccess: {
|
|
bgcolor: isDark ? 'rgba(102,187,106,0.15)' : '#346538',
|
|
color: isDark ? '#E8F5E9' : '#FFFFFF',
|
|
},
|
|
filledError: {
|
|
bgcolor: isDark ? 'rgba(239,83,80,0.15)' : '#9F2F2D',
|
|
color: isDark ? '#FFEBEE' : '#FFFFFF',
|
|
},
|
|
filledWarning: {
|
|
bgcolor: isDark ? 'rgba(255,183,77,0.15)' : '#956400',
|
|
color: isDark ? '#FFF8E1' : '#FFFFFF',
|
|
},
|
|
filledInfo: {
|
|
bgcolor: isDark ? 'rgba(121,134,203,0.15)' : '#1F6C9F',
|
|
color: isDark ? '#E8EAF6' : '#FFFFFF',
|
|
},
|
|
},
|
|
},
|
|
MuiSnackbarContent: {
|
|
styleOverrides: {
|
|
root: {
|
|
borderRadius: 12,
|
|
border: '1px solid',
|
|
borderColor: isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.06)',
|
|
bgcolor: isDark ? '#1E1E1E' : '#FFFFFF',
|
|
boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
|
|
color: isDark ? '#F2F2F2' : '#1F1B16',
|
|
fontWeight: 500,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
[controller.resolvedMode, controller.scheme],
|
|
)
|
|
|
|
return (
|
|
<ThemeProvider theme={theme}>
|
|
<CssBaseline />
|
|
{children}
|
|
</ThemeProvider>
|
|
)
|
|
}
|
|
|
|
export function AppProviders({ children }: PropsWithChildren) {
|
|
const queryClient = useMemo(
|
|
() =>
|
|
new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 30_000,
|
|
retry: 1,
|
|
refetchOnWindowFocus: false,
|
|
},
|
|
},
|
|
}),
|
|
[],
|
|
)
|
|
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<SseProvider />
|
|
<ThemeControllerProvider>
|
|
<AppThemeInner>{children}</AppThemeInner>
|
|
</ThemeControllerProvider>
|
|
</QueryClientProvider>
|
|
)
|
|
}
|