mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 07:26:42 +02:00
refactor(theme): remove custom theme editor and community themes
Remove unused custom theme functionality: - Delete custom-themes-store.svelte.ts from shared-theme - Remove ThemeEditor, ColorPicker, ThemeLivePreview components - Remove CommunityThemeGallery, ThemeCommunityCard components - Remove ThemeEditorPage, CommunityThemesPage - Simplify ThemePage to show only built-in themes - Remove editor and community routes from contacts app - Update THEMING.md documentation The built-in theme variants (default, ocean, forest, sunset, etc.) provide sufficient customization. Custom theme creation was never fully implemented and added unnecessary complexity. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
db701fd648
commit
12ba2cf824
17 changed files with 44 additions and 3395 deletions
|
|
@ -1,312 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type {
|
||||
CommunityTheme,
|
||||
CommunityThemeQuery,
|
||||
PaginatedCommunityThemes,
|
||||
CustomThemesStore,
|
||||
} from '@manacore/shared-theme';
|
||||
import { ArrowLeft, Heart, DownloadSimple } from '@manacore/shared-icons';
|
||||
import CommunityThemeGallery from '../components/community/CommunityThemeGallery.svelte';
|
||||
|
||||
type TabType = 'browse' | 'favorites' | 'downloaded';
|
||||
|
||||
interface Props {
|
||||
/** Custom themes store */
|
||||
store: CustomThemesStore;
|
||||
/** Current effective mode */
|
||||
effectiveMode?: 'light' | 'dark';
|
||||
/** Callback to navigate back */
|
||||
onBack?: () => void;
|
||||
/** Callback when a theme is selected for details */
|
||||
onSelectTheme?: (theme: CommunityTheme) => void;
|
||||
/** Page title */
|
||||
title?: string;
|
||||
/** Initial tab */
|
||||
initialTab?: TabType;
|
||||
}
|
||||
|
||||
let {
|
||||
store,
|
||||
effectiveMode = 'light',
|
||||
onBack,
|
||||
onSelectTheme,
|
||||
title = 'Community Themes',
|
||||
initialTab = 'browse',
|
||||
}: Props = $props();
|
||||
|
||||
// Active tab
|
||||
let activeTab = $state<TabType>(initialTab);
|
||||
|
||||
// Current query for browsing
|
||||
let currentQuery = $state<CommunityThemeQuery>({
|
||||
page: 1,
|
||||
sort: 'popular',
|
||||
});
|
||||
|
||||
// Load data based on active tab
|
||||
$effect(() => {
|
||||
if (activeTab === 'browse') {
|
||||
store.browseCommunity(currentQuery);
|
||||
} else if (activeTab === 'favorites') {
|
||||
store.loadFavorites();
|
||||
} else if (activeTab === 'downloaded') {
|
||||
store.loadDownloaded();
|
||||
}
|
||||
});
|
||||
|
||||
function handleQueryChange(query: CommunityThemeQuery) {
|
||||
currentQuery = query;
|
||||
store.browseCommunity(query);
|
||||
}
|
||||
|
||||
async function handleDownload(theme: CommunityTheme) {
|
||||
await store.downloadTheme(theme.id);
|
||||
}
|
||||
|
||||
async function handleToggleFavorite(theme: CommunityTheme) {
|
||||
await store.toggleFavorite(theme.id);
|
||||
}
|
||||
|
||||
async function handleRate(theme: CommunityTheme, rating: number) {
|
||||
await store.rateTheme(theme.id, rating);
|
||||
}
|
||||
|
||||
function handleApplyTheme(theme: CommunityTheme) {
|
||||
store.applyCustomTheme(theme);
|
||||
}
|
||||
|
||||
// Tab definitions
|
||||
const tabs: { id: TabType; label: string; icon: typeof Heart }[] = [
|
||||
{ id: 'browse', label: 'Entdecken', icon: DownloadSimple },
|
||||
{ id: 'favorites', label: 'Favoriten', icon: Heart },
|
||||
{ id: 'downloaded', label: 'Installiert', icon: DownloadSimple },
|
||||
];
|
||||
|
||||
// Get themes for current tab
|
||||
let displayThemes = $derived(() => {
|
||||
switch (activeTab) {
|
||||
case 'browse':
|
||||
return store.communityThemes;
|
||||
case 'favorites':
|
||||
return store.favorites;
|
||||
case 'downloaded':
|
||||
return store.downloaded;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-background">
|
||||
<!-- Header -->
|
||||
<header class="flex items-start gap-4 p-6 border-b border-border bg-surface">
|
||||
{#if onBack}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-10 h-10 bg-muted rounded-lg text-foreground hover:bg-muted/80 transition-colors flex-shrink-0"
|
||||
onclick={onBack}
|
||||
aria-label="Zurück"
|
||||
>
|
||||
<ArrowLeft size={20} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">{title}</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">Entdecke von der Community erstellte Themes</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="bg-surface border-b border-border px-6">
|
||||
<nav class="flex gap-2 overflow-x-auto" role="tablist">
|
||||
{#each tabs as tab}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 px-5 py-4 text-sm font-medium border-b-2 whitespace-nowrap transition-colors
|
||||
{activeTab === tab.id
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground border-transparent hover:text-foreground'}"
|
||||
onclick={() => (activeTab = tab.id)}
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.id}
|
||||
>
|
||||
<svelte:component
|
||||
this={tab.icon}
|
||||
size={16}
|
||||
weight={activeTab === tab.id ? 'fill' : 'regular'}
|
||||
/>
|
||||
{tab.label}
|
||||
{#if tab.id === 'favorites' && store.favorites.length > 0}
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs font-semibold rounded-full
|
||||
{activeTab === tab.id ? 'bg-primary/10 text-primary' : 'bg-muted'}"
|
||||
>
|
||||
{store.favorites.length}
|
||||
</span>
|
||||
{:else if tab.id === 'downloaded' && store.downloaded.length > 0}
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs font-semibold rounded-full
|
||||
{activeTab === tab.id ? 'bg-primary/10 text-primary' : 'bg-muted'}"
|
||||
>
|
||||
{store.downloaded.length}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 max-w-6xl mx-auto">
|
||||
{#if activeTab === 'browse'}
|
||||
<CommunityThemeGallery
|
||||
themes={store.communityThemes}
|
||||
pagination={store.pagination}
|
||||
{currentQuery}
|
||||
loading={store.loading}
|
||||
{effectiveMode}
|
||||
onQueryChange={handleQueryChange}
|
||||
{onSelectTheme}
|
||||
onDownloadTheme={handleDownload}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
onRateTheme={handleRate}
|
||||
/>
|
||||
{:else if activeTab === 'favorites'}
|
||||
{#if store.favorites.length === 0 && !store.loading}
|
||||
<div class="text-center py-16 text-muted-foreground">
|
||||
<Heart size={48} weight="light" class="mx-auto mb-4" />
|
||||
<h3 class="text-xl font-semibold text-foreground mb-2">Keine Favoriten</h3>
|
||||
<p class="text-sm mb-6">Themes, die du favorisierst, werden hier angezeigt.</p>
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2.5 text-sm font-medium bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
|
||||
onclick={() => (activeTab = 'browse')}
|
||||
>
|
||||
Themes entdecken
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each store.favorites as theme (theme.id)}
|
||||
{@const colors = effectiveMode === 'dark' ? theme.darkColors : theme.lightColors}
|
||||
<div
|
||||
class="cursor-pointer outline-none group"
|
||||
onclick={() => onSelectTheme?.(theme)}
|
||||
onkeypress={(e) => e.key === 'Enter' && onSelectTheme?.(theme)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bg-surface border border-border rounded-xl p-4 flex items-center gap-4 transition-all hover:border-border-strong hover:shadow-sm group-focus-visible:ring-2 group-focus-visible:ring-primary group-focus-visible:ring-offset-2"
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.primary})"
|
||||
></div>
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.background})"
|
||||
></div>
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.surface})"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<span class="text-xl">{theme.emoji}</span>
|
||||
<span class="font-semibold text-foreground truncate">{theme.name}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-xs font-medium bg-muted border border-border rounded-md text-foreground hover:bg-muted/80 transition-colors"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleApplyTheme(theme);
|
||||
}}
|
||||
>
|
||||
Anwenden
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 text-red-500 bg-muted border border-border rounded-md hover:bg-red-500/10 transition-colors"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleFavorite(theme);
|
||||
}}
|
||||
>
|
||||
<Heart size={18} weight="fill" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if activeTab === 'downloaded'}
|
||||
{#if store.downloaded.length === 0 && !store.loading}
|
||||
<div class="text-center py-16 text-muted-foreground">
|
||||
<DownloadSimple size={48} weight="light" class="mx-auto mb-4" />
|
||||
<h3 class="text-xl font-semibold text-foreground mb-2">Keine installierten Themes</h3>
|
||||
<p class="text-sm mb-6">Themes, die du installierst, werden hier angezeigt.</p>
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2.5 text-sm font-medium bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
|
||||
onclick={() => (activeTab = 'browse')}
|
||||
>
|
||||
Themes entdecken
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each store.downloaded as theme (theme.id)}
|
||||
{@const colors = effectiveMode === 'dark' ? theme.darkColors : theme.lightColors}
|
||||
<div
|
||||
class="cursor-pointer outline-none group"
|
||||
onclick={() => onSelectTheme?.(theme)}
|
||||
onkeypress={(e) => e.key === 'Enter' && onSelectTheme?.(theme)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bg-surface border border-border rounded-xl p-4 flex items-center gap-4 transition-all hover:border-border-strong hover:shadow-sm group-focus-visible:ring-2 group-focus-visible:ring-primary group-focus-visible:ring-offset-2"
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.primary})"
|
||||
></div>
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.background})"
|
||||
></div>
|
||||
<div
|
||||
class="w-5 h-5 rounded-full border border-border"
|
||||
style="background-color: hsl({colors.surface})"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<span class="text-xl">{theme.emoji}</span>
|
||||
<span class="font-semibold text-foreground truncate">{theme.name}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-xs font-medium bg-primary text-primary-foreground border border-primary rounded-md hover:bg-primary/90 transition-colors"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleApplyTheme(theme);
|
||||
}}
|
||||
>
|
||||
Anwenden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type {
|
||||
ThemeColors,
|
||||
EffectiveMode,
|
||||
CreateCustomThemeInput,
|
||||
CustomTheme,
|
||||
} from '@manacore/shared-theme';
|
||||
import { ArrowLeft } from '@manacore/shared-icons';
|
||||
import ThemeEditor from '../components/editor/ThemeEditor.svelte';
|
||||
import ThemeLivePreview from '../components/editor/ThemeLivePreview.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Theme to edit (for update mode) */
|
||||
editTheme?: CustomTheme;
|
||||
/** Current effective mode */
|
||||
effectiveMode?: EffectiveMode;
|
||||
/** Callback when theme is saved */
|
||||
onSave?: (theme: CreateCustomThemeInput) => Promise<void>;
|
||||
/** Callback to navigate back */
|
||||
onBack?: () => void;
|
||||
/** Is saving in progress */
|
||||
isSaving?: boolean;
|
||||
/** Page title */
|
||||
title?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
editTheme,
|
||||
effectiveMode = 'light',
|
||||
onSave,
|
||||
onBack,
|
||||
isSaving = false,
|
||||
title,
|
||||
}: Props = $props();
|
||||
|
||||
// Track current theme state for preview
|
||||
let currentTheme = $state<Partial<CreateCustomThemeInput>>(
|
||||
editTheme
|
||||
? {
|
||||
name: editTheme.name,
|
||||
description: editTheme.description,
|
||||
emoji: editTheme.emoji,
|
||||
icon: editTheme.icon,
|
||||
lightColors: editTheme.lightColors,
|
||||
darkColors: editTheme.darkColors,
|
||||
baseVariant: editTheme.baseVariant,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
// Preview mode state - syncs with editor mode
|
||||
let previewMode = $state<EffectiveMode>(effectiveMode);
|
||||
|
||||
// Get colors for preview - always show current theme colors
|
||||
let displayColors = $derived<ThemeColors | null>(
|
||||
currentTheme.lightColors && currentTheme.darkColors
|
||||
? previewMode === 'dark'
|
||||
? (currentTheme.darkColors as ThemeColors)
|
||||
: (currentTheme.lightColors as ThemeColors)
|
||||
: null
|
||||
);
|
||||
|
||||
function handleThemeChange(theme: Partial<CreateCustomThemeInput>) {
|
||||
currentTheme = theme;
|
||||
}
|
||||
|
||||
function handlePreview(colors: ThemeColors, mode: EffectiveMode) {
|
||||
// Update preview mode when user clicks preview in editor
|
||||
previewMode = mode;
|
||||
}
|
||||
|
||||
function handleStopPreview() {
|
||||
// No-op - preview always shows current colors
|
||||
}
|
||||
|
||||
async function handleSave(theme: CreateCustomThemeInput) {
|
||||
await onSave?.(theme);
|
||||
}
|
||||
|
||||
let pageTitle = $derived(
|
||||
title ?? (editTheme ? `"${editTheme.name}" bearbeiten` : 'Neues Theme erstellen')
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="editor-page">
|
||||
<!-- Header -->
|
||||
<header class="page-header">
|
||||
{#if onBack}
|
||||
<button type="button" class="back-btn" onclick={onBack} aria-label="Zurück">
|
||||
<ArrowLeft size={20} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">{pageTitle}</h1>
|
||||
<p class="page-subtitle">Gestalte dein eigenes Theme mit individuellen Farben</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Split Layout: Editor + Preview -->
|
||||
<div class="editor-layout">
|
||||
<!-- Editor Panel -->
|
||||
<div class="editor-panel">
|
||||
<ThemeEditor
|
||||
initialTheme={editTheme
|
||||
? {
|
||||
name: editTheme.name,
|
||||
description: editTheme.description,
|
||||
emoji: editTheme.emoji,
|
||||
icon: editTheme.icon,
|
||||
lightColors: editTheme.lightColors,
|
||||
darkColors: editTheme.darkColors,
|
||||
baseVariant: editTheme.baseVariant,
|
||||
}
|
||||
: undefined}
|
||||
{effectiveMode}
|
||||
onThemeChange={handleThemeChange}
|
||||
onSave={handleSave}
|
||||
onPreview={handlePreview}
|
||||
onStopPreview={handleStopPreview}
|
||||
{isSaving}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Preview Panel -->
|
||||
<div class="preview-panel">
|
||||
<div class="preview-sticky">
|
||||
{#if displayColors}
|
||||
<ThemeLivePreview
|
||||
colors={displayColors}
|
||||
mode={previewMode}
|
||||
onModeChange={(m) => (previewMode = m)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="preview-placeholder">
|
||||
<p>Wähle eine Basis-Variante oder passe Farben an, um eine Vorschau zu sehen</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.editor-page {
|
||||
min-height: 100vh;
|
||||
background: hsl(var(--background));
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--surface));
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background: hsl(var(--muted));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
color: hsl(var(--foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: hsl(var(--muted) / 0.8);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0.125rem 0 0;
|
||||
}
|
||||
|
||||
/* Layout - Full width on desktop */
|
||||
.editor-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
.editor-layout {
|
||||
grid-template-columns: 1fr 440px;
|
||||
padding: 1.5rem 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.editor-layout {
|
||||
grid-template-columns: 1fr 480px;
|
||||
padding: 1.5rem 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1920px) {
|
||||
.editor-layout {
|
||||
grid-template-columns: 1fr 520px;
|
||||
padding: 2rem 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-panel {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preview-panel {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preview-sticky {
|
||||
position: sticky;
|
||||
top: 1.5rem;
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
background: hsl(var(--surface));
|
||||
border: 1px dashed hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview-placeholder p {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.8125rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.editor-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.preview-sticky {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.page-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.editor-layout {
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,29 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type {
|
||||
ThemeVariant,
|
||||
ThemeMode,
|
||||
A11yStore,
|
||||
CustomThemesStore,
|
||||
CustomTheme,
|
||||
UserSettingsStore,
|
||||
} from '@manacore/shared-theme';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Sun,
|
||||
Moon,
|
||||
Desktop,
|
||||
Plus,
|
||||
PaintBrush,
|
||||
Users,
|
||||
Palette,
|
||||
} from '@manacore/shared-icons';
|
||||
import type { ThemeVariant, ThemeMode, A11yStore } from '@manacore/shared-theme';
|
||||
import { ArrowLeft, Sun, Moon, Desktop } from '@manacore/shared-icons';
|
||||
import type { ThemeCardData, ThemePageTranslations, A11yTranslations } from '../types';
|
||||
import { defaultTranslations, defaultA11yTranslations } from '../types';
|
||||
import ThemeGrid from '../components/ThemeGrid.svelte';
|
||||
import A11ySettings from '../components/A11ySettings.svelte';
|
||||
|
||||
type TabType = 'themes' | 'custom' | 'community';
|
||||
|
||||
interface Props {
|
||||
// Theme Store Integration
|
||||
currentVariant: ThemeVariant;
|
||||
|
|
@ -55,13 +37,6 @@
|
|||
showA11ySettings?: boolean;
|
||||
a11yTranslations?: Partial<A11yTranslations>;
|
||||
|
||||
// Custom Themes (new)
|
||||
customThemesStore?: CustomThemesStore;
|
||||
showCustomThemes?: boolean;
|
||||
onCreateTheme?: () => void;
|
||||
onEditTheme?: (theme: CustomTheme) => void;
|
||||
onCommunityThemes?: () => void;
|
||||
|
||||
// Theme Pinning (user settings)
|
||||
userSettingsStore?: UserSettingsStore;
|
||||
pinnedThemes?: ThemeVariant[];
|
||||
|
|
@ -85,26 +60,10 @@
|
|||
a11yStore,
|
||||
showA11ySettings = false,
|
||||
a11yTranslations = {},
|
||||
customThemesStore,
|
||||
showCustomThemes = false,
|
||||
onCreateTheme,
|
||||
onEditTheme,
|
||||
onCommunityThemes,
|
||||
userSettingsStore,
|
||||
pinnedThemes = [],
|
||||
onTogglePin,
|
||||
}: Props = $props();
|
||||
|
||||
// Active tab state
|
||||
let activeTab = $state<TabType>('themes');
|
||||
|
||||
// Load custom themes when tab becomes active
|
||||
$effect(() => {
|
||||
if (activeTab === 'custom' && customThemesStore) {
|
||||
customThemesStore.loadCustomThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Merge translations with defaults
|
||||
const t = $derived({ ...defaultTranslations, ...translations });
|
||||
const a11yT = $derived({ ...defaultA11yTranslations, ...a11yTranslations });
|
||||
|
|
@ -114,23 +73,6 @@
|
|||
{ mode: 'dark', icon: Moon, label: t.darkMode },
|
||||
{ mode: 'system', icon: Desktop, label: t.systemMode },
|
||||
]);
|
||||
|
||||
// Tab definitions
|
||||
const tabs: { id: TabType; label: string; icon: typeof Palette }[] = [
|
||||
{ id: 'themes', label: 'Themes', icon: Palette },
|
||||
{ id: 'custom', label: 'Meine Themes', icon: PaintBrush },
|
||||
{ id: 'community', label: 'Community', icon: Users },
|
||||
];
|
||||
|
||||
function handleApplyCustomTheme(theme: CustomTheme) {
|
||||
customThemesStore?.applyCustomTheme(theme);
|
||||
}
|
||||
|
||||
async function handleDeleteTheme(theme: CustomTheme) {
|
||||
if (confirm(`Theme "${theme.name}" wirklich löschen?`)) {
|
||||
await customThemesStore?.deleteTheme(theme.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-background">
|
||||
|
|
@ -158,31 +100,6 @@
|
|||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Tabs (if custom themes enabled) -->
|
||||
{#if showCustomThemes && customThemesStore}
|
||||
<nav class="flex gap-1 mb-6 p-1 bg-muted rounded-lg" role="tablist">
|
||||
{#each tabs as tab}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors flex-1 justify-center
|
||||
{activeTab === tab.id
|
||||
? 'bg-surface text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'}"
|
||||
onclick={() => (activeTab = tab.id)}
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.id}
|
||||
>
|
||||
<svelte:component
|
||||
this={tab.icon}
|
||||
size={16}
|
||||
weight={activeTab === tab.id ? 'fill' : 'regular'}
|
||||
/>
|
||||
{tab.label}
|
||||
</button>
|
||||
{/each}
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<!-- Mode Selector -->
|
||||
{#if showModeSelector && onModeChange}
|
||||
<section class="mb-6">
|
||||
|
|
@ -207,154 +124,22 @@
|
|||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Tab Content -->
|
||||
{#if !showCustomThemes || activeTab === 'themes'}
|
||||
<!-- Theme Grid -->
|
||||
<section>
|
||||
<h2 class="text-sm font-medium text-muted-foreground mb-4">
|
||||
{t.currentTheme}
|
||||
</h2>
|
||||
<ThemeGrid
|
||||
{currentVariant}
|
||||
onSelect={onSelectTheme}
|
||||
{themes}
|
||||
onUnlock={onUnlockTheme}
|
||||
{showLockedThemes}
|
||||
{translations}
|
||||
{pinnedThemes}
|
||||
{onTogglePin}
|
||||
/>
|
||||
</section>
|
||||
{:else if activeTab === 'custom' && customThemesStore}
|
||||
<!-- Custom Themes -->
|
||||
<section>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-medium text-muted-foreground">Meine Themes</h2>
|
||||
{#if onCreateTheme}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onCreateTheme}
|
||||
class="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Neues Theme
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if customThemesStore.loading}
|
||||
<div class="text-center py-8 text-muted-foreground">Lade...</div>
|
||||
{:else if customThemesStore.customThemes.length === 0}
|
||||
<div class="text-center py-12 border border-dashed border-border rounded-xl">
|
||||
<PaintBrush size={48} class="mx-auto mb-4 text-muted-foreground" weight="light" />
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">Noch keine eigenen Themes</h3>
|
||||
<p class="text-muted-foreground mb-4">
|
||||
Erstelle dein erstes eigenes Theme mit individuellen Farben.
|
||||
</p>
|
||||
{#if onCreateTheme}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onCreateTheme}
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Theme erstellen
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
{#each customThemesStore.customThemes as theme (theme.id)}
|
||||
<div class="bg-surface border border-border rounded-xl overflow-hidden">
|
||||
<!-- Color preview bar -->
|
||||
<div class="h-8 flex">
|
||||
<div
|
||||
class="flex-1"
|
||||
style="background-color: hsl({theme.lightColors.primary})"
|
||||
></div>
|
||||
<div
|
||||
class="flex-1"
|
||||
style="background-color: hsl({theme.lightColors.background})"
|
||||
></div>
|
||||
<div
|
||||
class="flex-1"
|
||||
style="background-color: hsl({theme.lightColors.surface})"
|
||||
></div>
|
||||
<div
|
||||
class="flex-1"
|
||||
style="background-color: hsl({theme.lightColors.foreground})"
|
||||
></div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-xl">{theme.emoji}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold text-foreground truncate">{theme.name}</h3>
|
||||
{#if theme.description}
|
||||
<p class="text-sm text-muted-foreground truncate">{theme.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if theme.isPublished}
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs font-medium bg-success/10 text-success rounded"
|
||||
>
|
||||
Veröffentlicht
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex gap-2 mt-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleApplyCustomTheme(theme)}
|
||||
class="flex-1 px-3 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Anwenden
|
||||
</button>
|
||||
{#if onEditTheme}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => onEditTheme(theme)}
|
||||
class="px-3 py-2 bg-muted text-foreground rounded-lg text-sm font-medium hover:bg-muted/80 transition-colors"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDeleteTheme(theme)}
|
||||
class="px-3 py-2 bg-muted text-error rounded-lg text-sm font-medium hover:bg-error/10 transition-colors"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{:else if activeTab === 'community'}
|
||||
<!-- Community Themes -->
|
||||
<section>
|
||||
<div class="text-center py-12 border border-dashed border-border rounded-xl">
|
||||
<Users size={48} class="mx-auto mb-4 text-muted-foreground" weight="light" />
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">Community Themes</h3>
|
||||
<p class="text-muted-foreground mb-4">
|
||||
Entdecke Themes, die von anderen Nutzern erstellt wurden.
|
||||
</p>
|
||||
{#if onCommunityThemes}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onCommunityThemes}
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<Users size={16} />
|
||||
Community durchsuchen
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
<!-- Theme Grid -->
|
||||
<section>
|
||||
<h2 class="text-sm font-medium text-muted-foreground mb-4">
|
||||
{t.currentTheme}
|
||||
</h2>
|
||||
<ThemeGrid
|
||||
{currentVariant}
|
||||
onSelect={onSelectTheme}
|
||||
{themes}
|
||||
onUnlock={onUnlockTheme}
|
||||
{showLockedThemes}
|
||||
{translations}
|
||||
{pinnedThemes}
|
||||
{onTogglePin}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<!-- A11y Settings -->
|
||||
{#if showA11ySettings && a11yStore}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue