feat: complete UI redesign with dark mode, sidebar navigation, and modern design system

Implemented comprehensive UI overhaul with three-layer architecture:

Layer 1 - Theme System:
- CSS variables for light/dark themes (theme.css)
- Theme composable with light/dark/auto mode (useTheme.ts)
- Sidebar state management composable (useSidebar.ts)
- Refactored main.css to use CSS variables throughout

Layer 2 - Core Components:
- AppSidebar with collapsible navigation (desktop) and overlay (mobile)
- CollapsibleSection reusable component for expandable cards
- Restructured App.vue with new sidebar layout
- Integrated Lucide icons library (lucide-vue-next)

Layer 3 - Views & Components:
- Updated all 14 views with CSS variables and responsive design
- Replaced inline SVG with Lucide icon components
- Added collapsible sections to Dashboard, Admin pages, UserProfile
- Updated 3 shared components (BookingForm, SpaceCalendar, AttachmentsList)

Features:
- Dark/light/auto theme with persistent preference
- Collapsible sidebar (icons-only on desktop, overlay on mobile)
- Consistent color palette using CSS variables
- Full responsive design across all pages
- Modern minimalist aesthetic with Indigo accent color

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-11 21:27:05 +00:00
parent 9c2846cf00
commit 0bf3e6a7e2
28 changed files with 1960 additions and 1641 deletions

View File

@@ -0,0 +1,43 @@
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const STORAGE_KEY = 'space-booking-sidebar-collapsed'
const collapsed = ref(localStorage.getItem(STORAGE_KEY) === 'true')
const mobileOpen = ref(false)
export function useSidebar() {
const router = useRouter()
// Close mobile sidebar on route change
watch(() => router.currentRoute.value.path, () => {
mobileOpen.value = false
})
function toggle() {
collapsed.value = !collapsed.value
localStorage.setItem(STORAGE_KEY, String(collapsed.value))
}
function close() {
collapsed.value = true
localStorage.setItem(STORAGE_KEY, 'true')
}
function toggleMobile() {
mobileOpen.value = !mobileOpen.value
}
function closeMobile() {
mobileOpen.value = false
}
return {
collapsed,
mobileOpen,
toggle,
close,
toggleMobile,
closeMobile
}
}

View File

@@ -0,0 +1,52 @@
import { ref, computed, watch, onMounted } from 'vue'
type Theme = 'light' | 'dark' | 'auto'
const STORAGE_KEY = 'space-booking-theme'
const theme = ref<Theme>((localStorage.getItem(STORAGE_KEY) as Theme) || 'light')
const systemDark = ref(window.matchMedia('(prefers-color-scheme: dark)').matches)
// Listen for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', (e) => {
systemDark.value = e.matches
})
const resolvedTheme = computed<'light' | 'dark'>(() => {
if (theme.value === 'auto') {
return systemDark.value ? 'dark' : 'light'
}
return theme.value
})
function applyTheme() {
document.documentElement.setAttribute('data-theme', resolvedTheme.value)
}
watch(resolvedTheme, applyTheme)
export function useTheme() {
onMounted(() => {
applyTheme()
})
function toggleTheme() {
const cycle: Theme[] = ['light', 'dark', 'auto']
const idx = cycle.indexOf(theme.value)
theme.value = cycle[(idx + 1) % cycle.length]
localStorage.setItem(STORAGE_KEY, theme.value)
}
function setTheme(value: Theme) {
theme.value = value
localStorage.setItem(STORAGE_KEY, value)
}
return {
theme,
resolvedTheme,
toggleTheme,
setTheme
}
}