main
Samuel Gbafa 2 months ago
parent e430cb6a3a
commit b21e00fc79
  1. 43
      src/App.tsx
  2. 314
      src/components/layout/DashboardLayout.tsx
  3. 156
      src/pages/ContactListPage.tsx
  4. 39
      src/pages/ImportPage.tsx
  5. 384
      src/theme/theme.ts

@ -1,25 +1,16 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { AppBar, Toolbar, Typography, Box, Button } from '@mui/material';
import { QrCode, PersonAdd } from '@mui/icons-material';
import { OnboardingProvider } from './context/OnboardingContext';
import DashboardLayout from './components/layout/DashboardLayout';
import ImportPage from './pages/ImportPage';
import ContactListPage from './pages/ContactListPage';
import ContactViewPage from './pages/ContactViewPage';
import InvitationPage from './pages/InvitationPage';
import OnboardingPage from './pages/OnboardingPage';
import { createAppTheme } from './theme/theme';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
const theme = createAppTheme('light');
function App() {
return (
@ -27,29 +18,7 @@ function App() {
<CssBaseline />
<OnboardingProvider>
<Router>
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Personal Network Manager
</Typography>
<Button
color="inherit"
startIcon={<QrCode />}
href="/invite"
sx={{ mr: 1 }}
>
Invite
</Button>
<Button
color="inherit"
startIcon={<PersonAdd />}
href="/onboarding"
>
Join
</Button>
</Toolbar>
</AppBar>
<DashboardLayout>
<Routes>
<Route path="/" element={<ImportPage />} />
<Route path="/contacts" element={<ContactListPage />} />
@ -57,7 +26,7 @@ function App() {
<Route path="/invite" element={<InvitationPage />} />
<Route path="/onboarding" element={<OnboardingPage />} />
</Routes>
</Box>
</DashboardLayout>
</Router>
</OnboardingProvider>
</ThemeProvider>

@ -0,0 +1,314 @@
import { useState } from 'react';
import type { ReactNode } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import {
Box,
Drawer,
AppBar,
Toolbar,
List,
Typography,
Divider,
IconButton,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
useTheme,
useMediaQuery,
Avatar,
Menu,
MenuItem,
Badge,
} from '@mui/material';
import {
Menu as MenuIcon,
People,
QrCode,
PersonAdd,
CloudDownload,
Settings,
Logout,
NotificationsNone,
SearchRounded,
} from '@mui/icons-material';
const drawerWidth = 280;
interface NavItem {
text: string;
icon: ReactNode;
path: string;
badge?: number;
}
interface DashboardLayoutProps {
children: ReactNode;
}
const DashboardLayout = ({ children }: DashboardLayoutProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('lg'));
const [mobileOpen, setMobileOpen] = useState(false);
const [profileMenuAnchor, setProfileMenuAnchor] = useState<null | HTMLElement>(null);
const location = useLocation();
const navigate = useNavigate();
const navItems: NavItem[] = [
{ text: 'Contacts', icon: <People />, path: '/contacts' },
{ text: 'Import', icon: <CloudDownload />, path: '/' },
{ text: 'Invite', icon: <QrCode />, path: '/invite' },
{ text: 'Join Network', icon: <PersonAdd />, path: '/onboarding' },
];
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const handleProfileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setProfileMenuAnchor(event.currentTarget);
};
const handleProfileMenuClose = () => {
setProfileMenuAnchor(null);
};
const handleNavigation = (path: string) => {
navigate(path);
if (isMobile) {
setMobileOpen(false);
}
};
const isActiveRoute = (path: string) => {
if (path === '/' && location.pathname === '/') return true;
if (path !== '/' && location.pathname.startsWith(path)) return true;
return false;
};
const drawerContent = (
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ p: 3, borderBottom: 1, borderColor: 'divider' }}>
<Typography variant="h6" sx={{ fontWeight: 700, color: 'primary.main' }}>
Network Manager
</Typography>
<Typography variant="body2" color="text.secondary">
Personal Network Hub
</Typography>
</Box>
<List sx={{ flex: 1, py: 2 }}>
{navItems.map((item) => (
<ListItem key={item.text} disablePadding>
<ListItemButton
onClick={() => handleNavigation(item.path)}
selected={isActiveRoute(item.path)}
sx={{
mx: 1,
borderRadius: 2,
minHeight: 48,
'&.Mui-selected': {
backgroundColor: 'primary.main',
color: 'primary.contrastText',
'&:hover': {
backgroundColor: 'primary.dark',
},
'& .MuiListItemIcon-root': {
color: 'primary.contrastText',
},
},
}}
>
<ListItemIcon sx={{ minWidth: 40 }}>
{item.badge ? (
<Badge badgeContent={item.badge} color="error">
{item.icon}
</Badge>
) : (
item.icon
)}
</ListItemIcon>
<ListItemText
primary={item.text}
primaryTypographyProps={{
fontSize: '0.875rem',
fontWeight: isActiveRoute(item.path) ? 600 : 500,
}}
/>
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
<List sx={{ py: 2 }}>
<ListItem disablePadding>
<ListItemButton
onClick={() => handleNavigation('/settings')}
sx={{
mx: 1,
borderRadius: 2,
minHeight: 48,
}}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<Settings />
</ListItemIcon>
<ListItemText
primary="Settings"
primaryTypographyProps={{
fontSize: '0.875rem',
fontWeight: 500,
}}
/>
</ListItemButton>
</ListItem>
</List>
</Box>
);
return (
<Box sx={{ display: 'flex', height: '100vh' }}>
<AppBar
position="fixed"
sx={{
width: { lg: `calc(100% - ${drawerWidth}px)` },
ml: { lg: `${drawerWidth}px` },
backgroundColor: 'background.paper',
borderBottom: 1,
borderColor: 'divider',
boxShadow: 'none',
}}
>
<Toolbar sx={{ justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { lg: 'none' } }}
>
<MenuIcon />
</IconButton>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton size="large" color="inherit">
<SearchRounded />
</IconButton>
<IconButton size="large" color="inherit">
<Badge badgeContent={3} color="error">
<NotificationsNone />
</Badge>
</IconButton>
<IconButton
size="large"
edge="end"
aria-label="account of current user"
aria-haspopup="true"
onClick={handleProfileMenuOpen}
color="inherit"
>
<Avatar
sx={{ width: 32, height: 32 }}
alt="Profile"
src="/static/images/avatar/2.jpg"
>
U
</Avatar>
</IconButton>
</Box>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { lg: drawerWidth }, flexShrink: { lg: 0 } }}
>
<Drawer
variant={isMobile ? 'temporary' : 'permanent'}
open={isMobile ? mobileOpen : true}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true,
}}
sx={{
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
borderRight: 1,
borderColor: 'divider',
},
}}
>
{drawerContent}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
width: { lg: `calc(100% - ${drawerWidth}px)` },
minHeight: '100vh',
backgroundColor: 'background.default',
}}
>
<Toolbar />
<Box sx={{ p: 3, height: 'calc(100vh - 64px)', overflow: 'auto' }}>
{children}
</Box>
</Box>
<Menu
anchorEl={profileMenuAnchor}
open={Boolean(profileMenuAnchor)}
onClose={handleProfileMenuClose}
onClick={handleProfileMenuClose}
PaperProps={{
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&::before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={handleProfileMenuClose}>
<Avatar /> My Profile
</MenuItem>
<MenuItem onClick={handleProfileMenuClose}>
<Settings sx={{ mr: 1 }} /> Settings
</MenuItem>
<Divider />
<MenuItem onClick={handleProfileMenuClose}>
<Logout sx={{ mr: 1 }} /> Logout
</MenuItem>
</Menu>
</Box>
);
};
export default DashboardLayout;

@ -1,23 +1,21 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Container,
Typography,
Box,
Tabs,
Tab,
List,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Chip,
Paper,
Divider,
TextField,
InputAdornment,
Button,
Tooltip
Tooltip,
Card,
CardContent,
Grid,
alpha,
useTheme
} from '@mui/material';
import {
List as ListIcon,
@ -39,6 +37,7 @@ const ContactListPage = () => {
const [searchQuery, setSearchQuery] = useState('');
const [tabValue, setTabValue] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const theme = useTheme();
const navigate = useNavigate();
useEffect(() => {
@ -103,16 +102,22 @@ const ContactListPage = () => {
};
return (
<Container maxWidth="lg" sx={{ py: 3 }}>
<Box sx={{ height: '100%' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h4" component="h1">
Contacts ({filteredContacts.length})
</Typography>
<Box>
<Typography variant="h4" component="h1" sx={{ fontWeight: 700, mb: 1 }}>
Contacts
</Typography>
<Typography variant="body1" color="text.secondary">
Manage your network of {filteredContacts.length} contacts
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
startIcon={<QrCode />}
onClick={handleInvite}
sx={{ borderRadius: 2 }}
>
Invite
</Button>
@ -120,18 +125,27 @@ const ContactListPage = () => {
variant="contained"
startIcon={<Add />}
onClick={handleAddContact}
sx={{ borderRadius: 2 }}
>
Add Contacts
</Button>
</Box>
</Box>
<Paper sx={{ mb: 3 }}>
<Card sx={{ mb: 3 }}>
<Tabs
value={tabValue}
onChange={handleTabChange}
aria-label="contact view tabs"
sx={{ borderBottom: 1, borderColor: 'divider' }}
sx={{
borderBottom: 1,
borderColor: 'divider',
'& .MuiTab-root': {
minHeight: 56,
textTransform: 'none',
fontWeight: 500,
}
}}
>
<Tab icon={<ListIcon />} label="List View" />
<Tooltip title="Coming soon">
@ -146,7 +160,7 @@ const ContactListPage = () => {
</Tooltip>
</Tabs>
<Box sx={{ p: 2 }}>
<Box sx={{ p: 3 }}>
<TextField
fullWidth
placeholder="Search contacts..."
@ -159,81 +173,103 @@ const ContactListPage = () => {
</InputAdornment>
),
}}
sx={{ mb: 2 }}
sx={{ mb: 3 }}
/>
{isLoading ? (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="body1" color="text.secondary">
<Box sx={{ textAlign: 'center', py: 8 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
Loading contacts...
</Typography>
<Typography variant="body2" color="text.secondary">
Please wait while we fetch your contacts
</Typography>
</Box>
) : filteredContacts.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="body1" color="text.secondary">
{searchQuery ? 'No contacts found matching your search.' : 'No contacts yet. Import some contacts to get started!'}
<Box sx={{ textAlign: 'center', py: 8 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
{searchQuery ? 'No contacts found' : 'No contacts yet'}
</Typography>
<Typography variant="body2" color="text.secondary">
{searchQuery ? 'Try adjusting your search terms.' : 'Import some contacts to get started!'}
</Typography>
</Box>
) : (
<List>
{filteredContacts.map((contact, index) => (
<Box key={contact.id}>
<ListItem
<Grid container spacing={2}>
{filteredContacts.map((contact) => (
<Grid size={{ xs: 12 }} key={contact.id}>
<Card
onClick={() => handleContactClick(contact.id)}
sx={{
cursor: 'pointer',
borderRadius: 1,
transition: 'all 0.2s ease-in-out',
border: 1,
borderColor: 'divider',
'&:hover': {
backgroundColor: 'action.hover',
borderColor: 'primary.main',
boxShadow: theme.shadows[4],
transform: 'translateY(-2px)',
},
}}
>
<ListItemAvatar>
<Avatar
src={contact.profileImage}
alt={contact.name}
sx={{ width: 56, height: 56 }}
>
{contact.name.charAt(0)}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" component="span">
{contact.name}
</Typography>
{getSourceIcon(contact.source)}
</Box>
}
secondary={
<Box sx={{ mt: 1 }}>
<Typography variant="body2" color="text.secondary">
<CardContent sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar
src={contact.profileImage}
alt={contact.name}
sx={{ width: 64, height: 64 }}
>
{contact.name.charAt(0)}
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Typography variant="h6" component="div" sx={{ fontWeight: 600 }}>
{contact.name}
</Typography>
{getSourceIcon(contact.source)}
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{contact.position} {contact.company && `at ${contact.company}`}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{contact.email}
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
{contact.tags?.map((tag) => (
<Chip key={tag} label={tag} size="small" variant="outlined" />
<Chip
key={tag}
label={tag}
size="small"
variant="outlined"
sx={{
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
fontWeight: 500,
}}
/>
))}
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
<Typography variant="caption" color="text.secondary">
Added {formatDate(contact.createdAt)}
</Typography>
</Box>
}
/>
</ListItem>
{index < filteredContacts.length - 1 && <Divider variant="inset" component="li" />}
</Box>
</Box>
</CardContent>
</Card>
</Grid>
))}
</List>
</Grid>
)}
</Box>
</Paper>
</Container>
</Card>
</Box>
);
};

@ -1,7 +1,6 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Container,
Typography,
Grid,
Card,
@ -85,13 +84,15 @@ const ImportPage = () => {
};
return (
<Container maxWidth="md" sx={{ py: 4 }}>
<Typography variant="h4" component="h1" gutterBottom align="center">
Import Your Contacts
</Typography>
<Typography variant="body1" align="center" sx={{ mb: 4, color: 'text.secondary' }}>
Choose a source to import your contacts from
</Typography>
<Box sx={{ height: '100%' }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h4" component="h1" gutterBottom sx={{ fontWeight: 700 }}>
Import Your Contacts
</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Choose a source to import your contacts from
</Typography>
</Box>
<Grid container spacing={3}>
{importSources.map((source) => (
@ -101,30 +102,34 @@ const ImportPage = () => {
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'all 0.3s ease-in-out',
transition: 'all 0.2s ease-in-out',
border: 1,
borderColor: 'divider',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: 3,
transform: 'translateY(-2px)',
boxShadow: 4,
borderColor: 'primary.main',
}
}}
>
<CardContent sx={{ flexGrow: 1, textAlign: 'center' }}>
<Box sx={{ mb: 2 }}>
<CardContent sx={{ flexGrow: 1, textAlign: 'center', p: 3 }}>
<Box sx={{ mb: 3 }}>
{getSourceIcon(source.icon)}
</Box>
<Typography variant="h6" component="h2" gutterBottom>
<Typography variant="h6" component="h2" gutterBottom sx={{ fontWeight: 600 }}>
{source.name}
</Typography>
<Typography variant="body2" color="text.secondary">
<Typography variant="body2" color="text.secondary" sx={{ lineHeight: 1.6 }}>
{source.description}
</Typography>
</CardContent>
<CardActions sx={{ justifyContent: 'center', pb: 2 }}>
<CardActions sx={{ justifyContent: 'center', p: 3, pt: 0 }}>
<Button
variant="contained"
onClick={() => handleImportClick(source)}
disabled={!source.isAvailable}
startIcon={<CloudDownload />}
sx={{ borderRadius: 2 }}
>
Import from {source.name}
</Button>
@ -182,7 +187,7 @@ const ImportPage = () => {
)}
</DialogActions>
</Dialog>
</Container>
</Box>
);
};

@ -0,0 +1,384 @@
import { createTheme, alpha } from '@mui/material/styles';
import type { PaletteMode } from '@mui/material';
// Custom color palette inspired by professional dashboards
const colors = {
primary: {
50: '#e8f4fd',
100: '#d1e9fb',
200: '#a3d2f7',
300: '#75bcf3',
400: '#47a5ef',
500: '#1976d2', // Main brand color
600: '#155a9f',
700: '#10436c',
800: '#0a2d39',
900: '#051616',
},
secondary: {
50: '#fce4ec',
100: '#f8bbd9',
200: '#f48fb1',
300: '#f06292',
400: '#ec407a',
500: '#dc004e', // Accent color
600: '#c2185b',
700: '#ad1457',
800: '#880e4f',
900: '#560e2e',
},
neutral: {
50: '#fafafa',
100: '#f5f5f5',
200: '#eeeeee',
300: '#e0e0e0',
400: '#bdbdbd',
500: '#9e9e9e',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121',
},
success: {
50: '#e8f5e8',
100: '#c8e6c9',
200: '#a5d6a7',
300: '#81c784',
400: '#66bb6a',
500: '#4caf50',
600: '#43a047',
700: '#388e3c',
800: '#2e7d32',
900: '#1b5e20',
},
warning: {
50: '#fff8e1',
100: '#ffecb3',
200: '#ffe082',
300: '#ffd54f',
400: '#ffca28',
500: '#ffc107',
600: '#ffb300',
700: '#ffa000',
800: '#ff8f00',
900: '#ff6f00',
},
error: {
50: '#ffebee',
100: '#ffcdd2',
200: '#ef9a9a',
300: '#e57373',
400: '#ef5350',
500: '#f44336',
600: '#e53935',
700: '#d32f2f',
800: '#c62828',
900: '#b71c1c',
},
};
// Enhanced theme configuration
export const createAppTheme = (mode: PaletteMode) => {
const isDark = mode === 'dark';
return createTheme({
palette: {
mode,
primary: {
main: colors.primary[500],
light: colors.primary[300],
dark: colors.primary[700],
contrastText: '#ffffff',
},
secondary: {
main: colors.secondary[500],
light: colors.secondary[300],
dark: colors.secondary[700],
contrastText: '#ffffff',
},
success: {
main: colors.success[500],
light: colors.success[300],
dark: colors.success[700],
},
warning: {
main: colors.warning[500],
light: colors.warning[300],
dark: colors.warning[700],
},
error: {
main: colors.error[500],
light: colors.error[300],
dark: colors.error[700],
},
background: {
default: isDark ? '#0a1929' : '#f8fafc',
paper: isDark ? '#1e293b' : '#ffffff',
},
text: {
primary: isDark ? '#e2e8f0' : '#334155',
secondary: isDark ? '#94a3b8' : '#64748b',
},
divider: isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08),
action: {
hover: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
selected: isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08),
},
},
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 700,
lineHeight: 1.2,
letterSpacing: '-0.02em',
},
h2: {
fontSize: '2rem',
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: '-0.01em',
},
h3: {
fontSize: '1.75rem',
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: '-0.01em',
},
h4: {
fontSize: '1.5rem',
fontWeight: 600,
lineHeight: 1.4,
letterSpacing: '-0.005em',
},
h5: {
fontSize: '1.25rem',
fontWeight: 600,
lineHeight: 1.4,
},
h6: {
fontSize: '1.125rem',
fontWeight: 600,
lineHeight: 1.4,
},
subtitle1: {
fontSize: '1rem',
fontWeight: 500,
lineHeight: 1.5,
},
subtitle2: {
fontSize: '0.875rem',
fontWeight: 500,
lineHeight: 1.5,
},
body1: {
fontSize: '1rem',
fontWeight: 400,
lineHeight: 1.6,
},
body2: {
fontSize: '0.875rem',
fontWeight: 400,
lineHeight: 1.6,
},
button: {
fontSize: '0.875rem',
fontWeight: 500,
lineHeight: 1.5,
textTransform: 'none' as const,
},
caption: {
fontSize: '0.75rem',
fontWeight: 400,
lineHeight: 1.5,
},
overline: {
fontSize: '0.75rem',
fontWeight: 500,
lineHeight: 1.5,
textTransform: 'uppercase' as const,
letterSpacing: '0.08em',
},
},
spacing: 8,
shape: {
borderRadius: 12,
},
shadows: [
'none',
'0px 1px 3px rgba(0, 0, 0, 0.04), 0px 1px 2px rgba(0, 0, 0, 0.06)',
'0px 2px 4px rgba(0, 0, 0, 0.04), 0px 2px 3px rgba(0, 0, 0, 0.06)',
'0px 3px 6px rgba(0, 0, 0, 0.04), 0px 3px 4px rgba(0, 0, 0, 0.06)',
'0px 4px 8px rgba(0, 0, 0, 0.04), 0px 4px 6px rgba(0, 0, 0, 0.06)',
'0px 6px 12px rgba(0, 0, 0, 0.04), 0px 6px 8px rgba(0, 0, 0, 0.06)',
'0px 8px 16px rgba(0, 0, 0, 0.04), 0px 8px 12px rgba(0, 0, 0, 0.06)',
'0px 12px 24px rgba(0, 0, 0, 0.04), 0px 12px 18px rgba(0, 0, 0, 0.06)',
'0px 16px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.06)',
'0px 24px 48px rgba(0, 0, 0, 0.04), 0px 24px 36px rgba(0, 0, 0, 0.06)',
'0px 32px 64px rgba(0, 0, 0, 0.04), 0px 32px 48px rgba(0, 0, 0, 0.06)',
'0px 40px 80px rgba(0, 0, 0, 0.04), 0px 40px 60px rgba(0, 0, 0, 0.06)',
'0px 48px 96px rgba(0, 0, 0, 0.04), 0px 48px 72px rgba(0, 0, 0, 0.06)',
'0px 56px 112px rgba(0, 0, 0, 0.04), 0px 56px 84px rgba(0, 0, 0, 0.06)',
'0px 64px 128px rgba(0, 0, 0, 0.04), 0px 64px 96px rgba(0, 0, 0, 0.06)',
'0px 72px 144px rgba(0, 0, 0, 0.04), 0px 72px 108px rgba(0, 0, 0, 0.06)',
'0px 80px 160px rgba(0, 0, 0, 0.04), 0px 80px 120px rgba(0, 0, 0, 0.06)',
'0px 88px 176px rgba(0, 0, 0, 0.04), 0px 88px 132px rgba(0, 0, 0, 0.06)',
'0px 96px 192px rgba(0, 0, 0, 0.04), 0px 96px 144px rgba(0, 0, 0, 0.06)',
'0px 104px 208px rgba(0, 0, 0, 0.04), 0px 104px 156px rgba(0, 0, 0, 0.06)',
'0px 112px 224px rgba(0, 0, 0, 0.04), 0px 112px 168px rgba(0, 0, 0, 0.06)',
'0px 120px 240px rgba(0, 0, 0, 0.04), 0px 120px 180px rgba(0, 0, 0, 0.06)',
'0px 128px 256px rgba(0, 0, 0, 0.04), 0px 128px 192px rgba(0, 0, 0, 0.06)',
'0px 136px 272px rgba(0, 0, 0, 0.04), 0px 136px 204px rgba(0, 0, 0, 0.06)',
'0px 144px 288px rgba(0, 0, 0, 0.04), 0px 144px 216px rgba(0, 0, 0, 0.06)',
],
components: {
MuiCssBaseline: {
styleOverrides: {
'*': {
boxSizing: 'border-box',
},
html: {
MozOsxFontSmoothing: 'grayscale',
WebkitFontSmoothing: 'antialiased',
display: 'flex',
flexDirection: 'column',
minHeight: '100%',
width: '100%',
},
body: {
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
minHeight: '100%',
width: '100%',
},
'#root': {
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
height: '100%',
width: '100%',
},
},
},
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
padding: '8px 16px',
fontWeight: 500,
fontSize: '0.875rem',
lineHeight: 1.5,
textTransform: 'none',
boxShadow: 'none',
'&:hover': {
boxShadow: 'none',
},
'&:active': {
boxShadow: 'none',
},
},
contained: {
'&:hover': {
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 2px 3px rgba(0, 0, 0, 0.12)',
},
},
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 12,
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.04), 0px 1px 2px rgba(0, 0, 0, 0.06)',
border: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
'&:hover': {
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.08), 0px 4px 6px rgba(0, 0, 0, 0.12)',
},
},
},
},
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 12,
border: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
},
},
},
MuiTextField: {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
borderRadius: 8,
backgroundColor: isDark ? alpha('#e2e8f0', 0.02) : alpha('#334155', 0.02),
'&:hover': {
backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
},
'&.Mui-focused': {
backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
},
},
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
backgroundColor: isDark ? '#1e293b' : '#ffffff',
color: isDark ? '#e2e8f0' : '#334155',
boxShadow: 'none',
borderBottom: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
},
},
},
MuiDrawer: {
styleOverrides: {
paper: {
backgroundColor: isDark ? '#0f172a' : '#ffffff',
borderRight: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
},
},
},
MuiListItem: {
styleOverrides: {
root: {
borderRadius: 8,
margin: '2px 8px',
'&:hover': {
backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
},
'&.Mui-selected': {
backgroundColor: isDark ? alpha('#1976d2', 0.12) : alpha('#1976d2', 0.08),
'&:hover': {
backgroundColor: isDark ? alpha('#1976d2', 0.16) : alpha('#1976d2', 0.12),
},
},
},
},
},
MuiTabs: {
styleOverrides: {
indicator: {
borderRadius: 2,
height: 3,
},
},
},
MuiTab: {
styleOverrides: {
root: {
textTransform: 'none',
fontWeight: 500,
fontSize: '0.875rem',
minHeight: 48,
'&.Mui-selected': {
fontWeight: 600,
},
},
},
},
},
});
};
export default createAppTheme;
Loading…
Cancel
Save