feat: implement dark mode support for admin layout and components
- Add dark mode toggle component in admin header - Integrate dark mode store across admin layout and child components - Update header, judulList, and judulListTab components with theme tokens - Add unified typography components for consistent theming - Implement smooth transitions for dark/light mode switching - Add mounted state to prevent hydration mismatches - Style navbar with dark mode aware colors and hover states - Update button styles with gradient effects for both themes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
383
src/utils/themeTokens.ts
Normal file
383
src/utils/themeTokens.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* Unified Theme Tokens for Admin Dashboard
|
||||
*
|
||||
* Berdasarkan spesifikasi: darkMode.md
|
||||
*
|
||||
* Semua styling constants disimpan di sini untuk konsistensi
|
||||
* Edit di sini = edit di seluruh aplikasi
|
||||
*
|
||||
* Usage:
|
||||
* import { themeTokens } from '@/utils/themeTokens';
|
||||
*
|
||||
* // Light mode (default)
|
||||
* const tokens = themeTokens(false);
|
||||
*
|
||||
* // Dark mode
|
||||
* const tokens = themeTokens(true);
|
||||
*/
|
||||
|
||||
export type ThemeTokens = {
|
||||
// Colors
|
||||
colors: {
|
||||
primary: string;
|
||||
primaryLight: string;
|
||||
primaryDark: string;
|
||||
gradient: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
// Backgrounds
|
||||
bg: {
|
||||
base: string;
|
||||
main: string;
|
||||
app: string;
|
||||
surface: string;
|
||||
surfaceElevated: string;
|
||||
header: string;
|
||||
navbar: string;
|
||||
card: string;
|
||||
hover: string;
|
||||
tableHeader: string;
|
||||
tableHover: string;
|
||||
};
|
||||
// Text
|
||||
text: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
tertiary: string;
|
||||
muted: string;
|
||||
brand: string;
|
||||
inverse: string;
|
||||
link: string;
|
||||
};
|
||||
// Borders
|
||||
border: {
|
||||
default: string;
|
||||
soft: string;
|
||||
strong: string;
|
||||
};
|
||||
// Status
|
||||
success: string;
|
||||
warning: string;
|
||||
error: string;
|
||||
info: string;
|
||||
};
|
||||
|
||||
// Typography
|
||||
typography: {
|
||||
h1: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
h2: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
h3: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
h4: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
body: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
small: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
label: {
|
||||
fz: string;
|
||||
fw: number;
|
||||
lh: number;
|
||||
};
|
||||
};
|
||||
|
||||
// Spacing
|
||||
spacing: {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
};
|
||||
|
||||
// Border Radius
|
||||
radius: {
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
};
|
||||
|
||||
// Shadows
|
||||
shadows: {
|
||||
none: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
};
|
||||
|
||||
// Layout
|
||||
layout: {
|
||||
headerHeight: number;
|
||||
navbarWidth: {
|
||||
base: number;
|
||||
sm: number;
|
||||
lg: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const themeTokens = (isDark: boolean = false): ThemeTokens => {
|
||||
// Base colors - tetap menggunakan colors.ts sebagai base untuk light mode
|
||||
const baseColors = {
|
||||
'orange': '#FCAE00',
|
||||
'blue-button': '#0A4E78',
|
||||
'blue-button-1': '#E5F2FA',
|
||||
'blue-button-2': '#B8DAEF',
|
||||
'blue-button-3': '#8AC1E3',
|
||||
'blue-button-4': '#5DA9D8',
|
||||
'blue-button-5': '#2F91CC',
|
||||
'blue-button-6': '#083F61',
|
||||
'blue-button-7': '#062F49',
|
||||
'blue-button-8': '#041F32',
|
||||
'blue-button-trans': '#628EC6',
|
||||
'white-1': '#FBFBFC',
|
||||
'white-trans-1': 'rgba(255, 255, 255, 0.5)',
|
||||
'white-trans-2': 'rgba(255, 255, 255, 0.7)',
|
||||
'white-trans-3': 'rgba(255, 255, 255, 0.9)',
|
||||
'grey-1': '#F4F5F6',
|
||||
'grey-2': '#CBCACD',
|
||||
'Bg': '#D1d9e8',
|
||||
'BG-trans': '#B1C5F2',
|
||||
};
|
||||
|
||||
/**
|
||||
* DARK MODE PALETTE
|
||||
* Berdasarkan spesifikasi: darkMode.md
|
||||
*/
|
||||
const darkColors = {
|
||||
// Background Layers
|
||||
bgBase: '#0B1220',
|
||||
bgApp: '#0F172A',
|
||||
bgCard: '#162235',
|
||||
bgSurface: '#1E2A3D',
|
||||
|
||||
// Borders
|
||||
borderDefault: '#2A3A52',
|
||||
borderSoft: '#22314A',
|
||||
|
||||
// Text
|
||||
textPrimary: '#E5E7EB',
|
||||
textSecondary: '#9CA3AF',
|
||||
textMuted: '#6B7280',
|
||||
textInverse: '#020617',
|
||||
|
||||
// Accent & Actions
|
||||
primaryAction: '#3B82F6',
|
||||
primaryHover: '#2563EB',
|
||||
primaryActive: '#1D4ED8',
|
||||
link: '#60A5FA',
|
||||
|
||||
// Status
|
||||
success: '#22C55E',
|
||||
warning: '#FACC15',
|
||||
error: '#EF4444',
|
||||
info: '#38BDF8',
|
||||
|
||||
// Hover states
|
||||
hoverSoft: 'rgba(255,255,255,0.03)',
|
||||
hoverMedium: 'rgba(255,255,255,0.04)',
|
||||
activeAccent: 'rgba(59,130,246,0.15)',
|
||||
};
|
||||
|
||||
/**
|
||||
* LIGHT MODE PALETTE
|
||||
* Original light theme
|
||||
*/
|
||||
const lightColors = {
|
||||
bgBase: '#f6f9fc',
|
||||
bgApp: '#ffffff',
|
||||
bgCard: '#ffffff',
|
||||
bgSurface: '#f8fafc',
|
||||
borderDefault: '#e2e8f0',
|
||||
borderSoft: '#e9ecef',
|
||||
textPrimary: '#1a1b1e',
|
||||
textSecondary: '#495057',
|
||||
textMuted: '#868e96',
|
||||
textInverse: '#ffffff',
|
||||
primaryAction: baseColors['blue-button'],
|
||||
primaryHover: '#083F61',
|
||||
primaryActive: '#062F49',
|
||||
link: '#2563eb',
|
||||
hoverSoft: 'rgba(25, 113, 194, 0.03)',
|
||||
hoverMedium: 'rgba(25, 113, 194, 0.05)',
|
||||
activeAccent: 'rgba(25, 113, 194, 0.1)',
|
||||
};
|
||||
|
||||
const current = isDark ? darkColors : lightColors;
|
||||
|
||||
return {
|
||||
colors: {
|
||||
primary: current.primaryAction,
|
||||
primaryLight: isDark ? current.activeAccent : baseColors['blue-button-1'],
|
||||
primaryDark: current.primaryActive,
|
||||
gradient: {
|
||||
from: current.primaryAction,
|
||||
to: isDark ? '#60A5FA' : '#228be6',
|
||||
},
|
||||
bg: {
|
||||
base: current.bgBase,
|
||||
main: isDark ? current.bgBase : 'linear-gradient(180deg, #fdfdfd, #f6f9fc)',
|
||||
app: current.bgApp,
|
||||
surface: current.bgSurface,
|
||||
surfaceElevated: isDark ? '#253347' : '#ffffff',
|
||||
header: isDark
|
||||
? `linear-gradient(180deg, ${current.bgApp} 0%, ${current.bgBase} 100%)`
|
||||
: 'linear-gradient(90deg, #ffffff, #f9fbff)',
|
||||
navbar: current.bgApp,
|
||||
card: current.bgCard,
|
||||
hover: current.hoverMedium,
|
||||
tableHeader: current.bgSurface,
|
||||
tableHover: isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.02)',
|
||||
},
|
||||
text: {
|
||||
primary: current.textPrimary,
|
||||
secondary: current.textSecondary,
|
||||
tertiary: current.textMuted,
|
||||
muted: current.textMuted,
|
||||
brand: current.primaryAction,
|
||||
inverse: current.textInverse,
|
||||
link: current.link,
|
||||
},
|
||||
border: {
|
||||
default: current.borderDefault,
|
||||
soft: current.borderSoft,
|
||||
strong: isDark ? '#3A4A62' : '#ced4da',
|
||||
},
|
||||
success: current.success,
|
||||
warning: current.warning,
|
||||
error: current.error,
|
||||
info: current.info,
|
||||
},
|
||||
|
||||
typography: {
|
||||
h1: {
|
||||
fz: isDark ? '2rem' : '2.25rem',
|
||||
fw: 700,
|
||||
lh: 1.2,
|
||||
},
|
||||
h2: {
|
||||
fz: isDark ? '1.75rem' : '2rem',
|
||||
fw: 700,
|
||||
lh: 1.25,
|
||||
},
|
||||
h3: {
|
||||
fz: isDark ? '1.5rem' : '1.75rem',
|
||||
fw: 700,
|
||||
lh: 1.3,
|
||||
},
|
||||
h4: {
|
||||
fz: isDark ? '1.25rem' : '1.5rem',
|
||||
fw: 600,
|
||||
lh: 1.35,
|
||||
},
|
||||
body: {
|
||||
fz: '1rem',
|
||||
fw: 400,
|
||||
lh: 1.5,
|
||||
},
|
||||
small: {
|
||||
fz: '0.875rem',
|
||||
fw: 400,
|
||||
lh: 1.4,
|
||||
},
|
||||
label: {
|
||||
fz: '0.75rem',
|
||||
fw: 600,
|
||||
lh: 1.4,
|
||||
},
|
||||
},
|
||||
|
||||
spacing: {
|
||||
xs: '0.625rem',
|
||||
sm: '1rem',
|
||||
md: '1.5rem',
|
||||
lg: '2rem',
|
||||
xl: '2.5rem',
|
||||
},
|
||||
|
||||
radius: {
|
||||
sm: '0.5rem', // 8px
|
||||
md: '0.75rem', // 12px
|
||||
lg: '1rem', // 16px
|
||||
xl: '1.25rem', // 20px
|
||||
},
|
||||
|
||||
shadows: {
|
||||
none: 'none',
|
||||
sm: isDark ? '0 1px 3px rgba(0,0,0,0.3)' : '0 1px 3px rgba(0,0,0,0.1)',
|
||||
md: isDark ? '0 4px 6px rgba(0,0,0,0.3)' : '0 4px 6px rgba(0,0,0,0.1)',
|
||||
lg: isDark ? '0 10px 15px rgba(0,0,0,0.3)' : '0 10px 15px rgba(0,0,0,0.1)',
|
||||
},
|
||||
|
||||
layout: {
|
||||
headerHeight: 64,
|
||||
navbarWidth: {
|
||||
base: 260,
|
||||
sm: 280,
|
||||
lg: 300,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Export default theme instances
|
||||
export const lightTheme = themeTokens(false);
|
||||
export const darkTheme = themeTokens(true);
|
||||
|
||||
// Helper untuk mendapatkan responsive font size
|
||||
export const getResponsiveFz = (isDark: boolean = false) => ({
|
||||
base: isDark ? 'md' : 'lg',
|
||||
md: isDark ? 'lg' : 'xl',
|
||||
});
|
||||
|
||||
// Helper untuk mendapatkan color berdasarkan state
|
||||
export const getActiveColor = (isActive: boolean, isDark: boolean = false) =>
|
||||
isActive ? themeTokens(isDark).colors.primary : isDark ? themeTokens(isDark).colors.text.secondary : 'gray';
|
||||
|
||||
// Helper untuk mendapatkan background hover
|
||||
export const getHoverBackground = (isActive: boolean, isDark: boolean = false) => {
|
||||
const tokens = themeTokens(isDark);
|
||||
return isActive
|
||||
? tokens.colors.bg.hover
|
||||
: tokens.colors.bg.hover;
|
||||
};
|
||||
|
||||
// Helper untuk active state dengan accent bar (sidebar)
|
||||
export const getActiveStateStyles = (isActive: boolean, isDark: boolean = false) => {
|
||||
const tokens = themeTokens(isDark);
|
||||
|
||||
if (isActive) {
|
||||
return {
|
||||
backgroundColor: isDark ? tokens.colors.bg.hover : 'rgba(25, 113, 194, 0.1)',
|
||||
borderLeft: isDark ? `3px solid ${tokens.colors.primary}` : '3px solid #1971c2',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'&:hover': {
|
||||
backgroundColor: tokens.colors.bg.hover,
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user