- 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>
14 KiB
🎨 Unified Styling System - Admin Dashboard
Sistem styling terpusat untuk admin dashboard Darmasaba dengan dukungan dark mode.
Berdasarkan spesifikasi: darkMode.md
📋 Daftar Isi
- Konsep Utama
- Dark Mode Palette
- Struktur File
- Cara Menggunakan
- Mengedit Style
- Dark Mode Toggle
- Contoh Penggunaan
🎯 Konsep Utama
Satu File Edit = Semua Halaman Terupdate
Sebelumnya:
- ❌ Style tersebar di 493 file
.tsx - ❌ Hardcode warna di setiap komponen
- ❌ Tidak ada konsistensi
- ❌ Sulit maintain
Sekarang:
- ✅ Edit di 1 file = semua halaman update
- ✅ Component reusable
- ✅ Konsisten di seluruh aplikasi
- ✅ Dark mode otomatis sesuai spesifikasi
darkMode.md
🌙 Dark Mode Palette
Background Layers (Dark Mode)
| Layer | Token | Warna | Fungsi |
|---|---|---|---|
| Base | bg.base |
#0B1220 |
Background utama aplikasi |
| App | bg.app |
#0F172A |
Area sidebar |
| Card | bg.card |
#162235 |
Card / container |
| Surface | bg.surface |
#1E2A3D |
Table header, tab, input |
Text Colors (Dark Mode)
| Jenis | Token | Warna |
|---|---|---|
| Primary | text.primary |
#E5E7EB |
| Secondary | text.secondary |
#9CA3AF |
| Muted | text.muted |
#6B7280 |
Accent & Actions (Dark Mode)
| Fungsi | Warna |
|---|---|
| Primary Action | #3B82F6 |
| Hover | #2563EB |
| Active | #1D4ED8 |
| Link | #60A5FA |
Borders (Dark Mode)
| Token | Warna |
|---|---|
border.default |
#2A3A52 |
border.soft |
#22314A |
Catatan: Light mode menggunakan palette original yang lebih terang
📁 Struktur File
src/
├── utils/
│ └── themeTokens.ts # 📦 PUSAT SEMUA STYLE (edit di sini!)
├── state/
│ └── darkModeStore.ts # 🌙 State management dark mode
├── components/admin/
│ ├── DarkModeToggle.tsx # 🌓 Toggle button
│ ├── AdminThemeProvider.tsx # 🎨 Theme provider wrapper
│ ├── UnifiedTypography.tsx # 📝 Text components (Title, Text)
│ ├── UnifiedSurface.tsx # 📦 Card, Paper components
│ └── README_UNIFIED_STYLING.md # 📖 Dokumentasi ini
├── app/admin/
│ ├── layout.tsx # ✅ Sudah diupdate dengan dark mode
│ └── (dashboard)/
│ └── _com/
│ ├── header.tsx # ✅ Sudah diupdate
│ ├── judulList.tsx # ✅ Sudah diupdate
│ └── judulListTab.tsx # ✅ Sudah diupdate
└── darkMode.md # 📐 Spesifikasi lengkap dark mode
🚀 Cara Menggunakan
1. Untuk Developer: Edit Style Global
Edit file: src/utils/themeTokens.ts
export const themeTokens = (isDark: boolean = false): ThemeTokens => {
const darkColors = {
bgBase: '#0B1220', // ← Edit warna dark mode di sini
bgCard: '#162235',
textPrimary: '#E5E7EB',
primaryAction: '#3B82F6',
// ... dan lainnya
};
return {
colors: {
primary: current.primaryAction,
bg: {
base: current.bgBase,
card: current.bgCard,
// ...
},
// ...
},
};
};
2. Menggunakan Components di Halaman
A. Typography Components
import { UnifiedTitle, UnifiedText } from '@/components/admin/UnifiedTypography';
// Heading - otomatis dark mode
<UnifiedTitle order={1}>Judul Halaman</UnifiedTitle>
<UnifiedTitle order={2}>Sub Judul</UnifiedTitle>
<UnifiedTitle order={3}>Section Title</UnifiedTitle>
<UnifiedTitle order={4}>Card Title</UnifiedTitle>
// Text dengan color semantic
<UnifiedText size="body" color="primary">Teks primary</UnifiedText>
<UnifiedText size="body" color="secondary">Teks secondary</UnifiedText>
<UnifiedText size="body" color="muted">Teks muted</UnifiedText>
<UnifiedText size="body" color="link">Link text</UnifiedText>
<UnifiedText size="body" color="brand">Brand color</UnifiedText>
// Dengan weight
<UnifiedText weight="bold">Teks bold</UnifiedText>
<UnifiedText weight="medium">Teks medium</UnifiedText>
B. Surface Components
import UnifiedCard, { UnifiedDivider } from '@/components/admin/UnifiedSurface';
// Card sederhana - border dan warna otomatis dark mode
<UnifiedCard>
<p>Isi card</p>
</UnifiedCard>
// Card dengan sections
<UnifiedCard>
<UnifiedCard.Header>
<UnifiedTitle order={4}>Header</UnifiedTitle>
</UnifiedCard.Header>
<UnifiedCard.Body>
<p>Body content</p>
</UnifiedCard.Body>
<UnifiedCard.Footer>
<Button>Action</Button>
</UnifiedCard.Footer>
</UnifiedCard>
// Divider dengan variant
<UnifiedDivider variant="soft" /> {/* Default */}
<UnifiedDivider variant="default" />
<UnifiedDivider variant="strong" />
C. Page Header Component
import { UnifiedPageHeader } from '@/components/admin/UnifiedTypography';
<UnifiedPageHeader
title="Daftar Berita"
subtitle="Kelola semua berita di sini"
action={
<Button onClick={handleCreate}>
<IconPlus /> Tambah Baru
</Button>
}
/>
3. Menggunakan Theme Tokens Langsung
import { useDarkMode } from '@/state/darkModeStore';
import { themeTokens } from '@/utils/themeTokens';
function MyComponent() {
const { isDark } = useDarkMode();
const tokens = themeTokens(isDark);
return (
<div style={{
backgroundColor: tokens.colors.bg.card,
color: tokens.colors.text.primary,
padding: tokens.spacing.md,
borderRadius: tokens.radius.lg,
border: `1px solid ${tokens.colors.border.default}`,
}}>
<p style={{ fontSize: tokens.typography.body.fz }}>
Konten dengan styling konsisten
</p>
</div>
);
}
🌓 Dark Mode Toggle
Otomatis di Header
Dark mode toggle sudah terintegrasi di header admin dashboard. User bisa toggle dengan klik tombol 🌙/☀️.
Manual Toggle
import { useDarkMode } from '@/state/darkModeStore';
import { DarkModeToggle } from '@/components/admin/DarkModeToggle';
function MyComponent() {
const { isDark, toggle } = useDarkMode();
return (
<div>
<p>Current mode: {isDark ? 'Dark' : 'Light'}</p>
{/* Gunakan component toggle */}
<DarkModeToggle />
{/* Atau manual */}
<button onClick={toggle}>Toggle</button>
</div>
);
}
Persistensi
Dark mode preference disimpan di localStorage dengan key darmasaba-admin-dark-mode.
Preference akan tetap ada saat user refresh halaman atau kembali nanti.
📝 Contoh Penggunaan Lengkap
Contoh 1: List Page dengan Table
'use client'
import { UnifiedPageHeader, UnifiedText } from '@/components/admin/UnifiedTypography';
import UnifiedCard from '@/components/admin/UnifiedSurface';
import { useDarkMode } from '@/state/darkModeStore';
import { themeTokens } from '@/utils/themeTokens';
import { Button, Table, TableTr, TableTh, TableTd } from '@mantine/core';
export default function DaftarBerita() {
const { isDark } = useDarkMode();
const tokens = themeTokens(isDark);
return (
<div>
{/* Header Halaman */}
<UnifiedPageHeader
title="Daftar Berita"
subtitle="Kelola semua berita yang diterbitkan"
action={
<Button
bg={tokens.colors.primary}
style={{
background: `linear-gradient(135deg, ${tokens.colors.primary}, ${isDark ? '#60A5FA' : '#4facfe'})`,
}}
>
+ Tambah Berita
</Button>
}
/>
{/* Card untuk Table */}
<UnifiedCard>
<Table>
<TableThead>
<TableTr>
<TableTh style={{ backgroundColor: tokens.colors.bg.surface }}>
<UnifiedText size="label" color="secondary">Judul</UnifiedText>
</TableTh>
<TableTh style={{ backgroundColor: tokens.colors.bg.surface }}>
<UnifiedText size="label" color="secondary">Kategori</UnifiedText>
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{data.map((item) => (
<TableTr
key={item.id}
style={{
'&:hover': {
backgroundColor: tokens.colors.bg.hover,
},
}}
>
<TableTd>
<UnifiedText size="body">{item.judul}</UnifiedText>
</TableTd>
<TableTd>
<UnifiedText size="small" color="secondary">
{item.kategori}
</UnifiedText>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</UnifiedCard>
</div>
);
}
Contoh 2: Detail Page
import { UnifiedTitle, UnifiedText } from '@/components/admin/UnifiedTypography';
import UnifiedCard, { UnifiedDivider } from '@/components/admin/UnifiedSurface';
export default function DetailBerita({ data }) {
return (
<UnifiedCard>
<UnifiedCard.Header>
<UnifiedTitle order={3} color="brand">{data.judul}</UnifiedTitle>
</UnifiedCard.Header>
<UnifiedCard.Body>
<Box mb="md">
<UnifiedText size="label" color="muted">Kategori</UnifiedText>
<UnifiedText size="body" weight="medium">{data.kategori}</UnifiedText>
</Box>
<UnifiedDivider variant="soft" />
<Box my="md">
<UnifiedText size="label" color="muted">Deskripsi</UnifiedText>
<UnifiedText size="body">{data.deskripsi}</UnifiedText>
</Box>
<UnifiedDivider variant="soft" />
<Box mt="md">
<UnifiedText size="label" color="muted">Konten</UnifiedText>
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</Box>
</UnifiedCard.Body>
<UnifiedCard.Footer>
<Group justify="right">
<Button color="red">Hapus</Button>
<Button color="green">Edit</Button>
</Group>
</UnifiedCard.Footer>
</UnifiedCard>
);
}
🎨 Mengedit Style
Edit Warna Dark Mode
File: src/utils/themeTokens.ts
const darkColors = {
// Background Layers
bgBase: '#0B1220', // ← Edit di sini
bgApp: '#0F172A',
bgCard: '#162235',
bgSurface: '#1E2A3D',
// Text
textPrimary: '#E5E7EB', // ← Edit di sini
textSecondary: '#9CA3AF',
// Accent
primaryAction: '#3B82F6', // ← Edit primary color
};
Edit Warna Light Mode
const lightColors = {
bgBase: '#f6f9fc',
bgCard: '#ffffff',
textPrimary: '#1a1b1e',
primaryAction: baseColors['blue-button'], // Dari colors.ts
};
Edit Typography
typography: {
h1: {
fz: '2rem', // ← Edit ukuran
fw: 700, // ← Edit weight
lh: 1.2, // ← Edit line height
},
body: {
fz: '1rem',
fw: 400,
lh: 1.5,
},
}
Edit Spacing & Radius
spacing: {
xs: '0.625rem', // 10px
sm: '1rem', // 16px
md: '1.5rem', // 24px
lg: '2rem', // 32px
}
radius: {
sm: '0.5rem', // 8px
md: '0.75rem', // 12px
lg: '1rem', // 16px
}
✅ Checklist Migrasi
Komponen yang sudah diupdate dengan dark mode:
- ✅
src/app/admin/layout.tsx - ✅
src/app/admin/(dashboard)/_com/header.tsx - ✅
src/app/admin/(dashboard)/_com/judulList.tsx - ✅
src/app/admin/(dashboard)/_com/judulListTab.tsx - ✅
src/components/admin/UnifiedTypography.tsx - ✅
src/components/admin/UnifiedSurface.tsx - ✅
src/components/admin/DarkModeToggle.tsx - ✅
src/utils/themeTokens.ts
Komponen yang perlu diupdate (TODO):
- Komponen di
src/app/admin/(dashboard)/desa/ - Komponen di
src/app/admin/(dashboard)/ppid/ - Komponen di
src/app/admin/(dashboard)/kesehatan/ - Komponen di
src/app/admin/(dashboard)/pendidikan/ - Komponen di
src/app/admin/(dashboard)/ekonomi/ - Dan lain-lain...
📚 Referensi
- Dark Mode Specification - Spesifikasi lengkap dark mode
- Mantine Theme System
- Mantine Dark Mode
- Valtio State Management
💡 Tips
- Selalu gunakan unified components untuk konsistensi dark/light mode
- Edit di
themeTokens.tsuntuk perubahan global - Test dark mode setelah perubahan style
- Gunakan color semantic (
primary,secondary,muted) bukan hex langsung - Jangan hardcode shadow di dark mode (spec: "Jangan pakai shadow hitam")
- Border harus terlihat di dark mode (opacity > 20%)
🆘 Troubleshooting
Style tidak berubah setelah edit themeTokens.ts?
- Clear browser cache (Cmd+Shift+R / Ctrl+Shift+R)
- Restart dev server:
bun run dev - Pastikan komponen menggunakan unified components
Dark mode tidak berfungsi?
- Cek
darkModeStore.tssudah diimport - Pastikan
useDarkMode()hook digunakan - Clear localStorage:
localStorage.clear() - Cek console untuk error
Border tidak terlihat di dark mode?
Pastikan menggunakan tokens.colors.border.default atau tokens.colors.border.soft, bukan hardcode warna.
Component tidak re-render?
- Pastikan
'use client'ada di file component - Gunakan
useSnapshot()jika menggunakan Valtio di non-event handler - Cek console untuk error
📐 Spesifikasi Dark Mode
Untuk spesifikasi lengkap dark mode (layout rules, table styles, button rules, dll), lihat:
darkMode.md
Highlights:
- ✅ Background layers berbeda (base, app, card, surface)
- ✅ Border wajib terlihat (tidak flat)
- ✅ Active state dengan accent bar (2-3px)
- ✅ Tidak pakai shadow hitam
- ✅ Hover state dengan background soft
- ✅ Text kontras terbaca
Last Updated: February 20, 2026
Version: 2.0.0 (Dark Mode Ready)
Based on: darkMode.md specification