feat: add logo, favicon, and theme system

- Add custom SVG favicon and Logo component (house + network nodes motif)
- Update page title to Homelable with meta description
- Show Logo in sidebar header and toolbar
- Add theme store and ThemeModal for canvas style switching
- Refactor node colors and edge styles for theme support
This commit is contained in:
Pouzor
2026-03-11 14:29:15 +01:00
parent 16de7cd390
commit 92d505f78c
19 changed files with 927 additions and 94 deletions
@@ -0,0 +1,31 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { useThemeStore } from '@/stores/themeStore'
describe('themeStore', () => {
beforeEach(() => {
useThemeStore.setState({ activeTheme: 'default' })
})
it('starts with default theme', () => {
expect(useThemeStore.getState().activeTheme).toBe('default')
})
it('setTheme updates activeTheme', () => {
useThemeStore.getState().setTheme('matrix')
expect(useThemeStore.getState().activeTheme).toBe('matrix')
})
it('setTheme can switch between all presets', () => {
const themes = ['default', 'dark', 'light', 'neon', 'matrix'] as const
for (const id of themes) {
useThemeStore.getState().setTheme(id)
expect(useThemeStore.getState().activeTheme).toBe(id)
}
})
it('setTheme back to default after neon', () => {
useThemeStore.getState().setTheme('neon')
useThemeStore.getState().setTheme('default')
expect(useThemeStore.getState().activeTheme).toBe('default')
})
})
+3
View File
@@ -32,6 +32,7 @@ interface CanvasState {
editingGroupRectId: string | null
setEditingGroupRectId: (id: string | null) => void
markSaved: () => void
markUnsaved: () => void
loadCanvas: (nodes: Node<NodeData>[], edges: Edge<EdgeData>[]) => void
notifyScanDeviceFound: () => void
}
@@ -155,6 +156,8 @@ export const useCanvasStore = create<CanvasState>((set) => ({
markSaved: () => set({ hasUnsavedChanges: false }),
markUnsaved: () => set({ hasUnsavedChanges: true }),
notifyScanDeviceFound: () => set({ scanEventTs: Date.now() }),
loadCanvas: (nodes, edges) => {
+12
View File
@@ -0,0 +1,12 @@
import { create } from 'zustand'
import type { ThemeId } from '@/utils/themes'
interface ThemeState {
activeTheme: ThemeId
setTheme: (id: ThemeId) => void
}
export const useThemeStore = create<ThemeState>((set) => ({
activeTheme: 'default',
setTheme: (id) => set({ activeTheme: id }),
}))