mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-10 19:39:10 +00:00
Add theme selection UI with Seed theme support
- Add ThemePreferences component with current theme display - Visual theme preview cards showing colors for each theme - Add Seed theme to available themes list - Theme icon with gradient and preview card styling - Support for Auto, Light, Dark, Ocean, Nordic, Seed, and Halloween themes
This commit is contained in:
469
src/components/settings/ThemePreferences.vue
Normal file
469
src/components/settings/ThemePreferences.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="theme-preferences">
|
||||
<div class="current-theme">
|
||||
<div class="theme-display">
|
||||
<div class="theme-icon" :data-theme="selectedTheme">
|
||||
<div class="icon-inner"></div>
|
||||
</div>
|
||||
<div class="theme-info">
|
||||
<span class="theme-label">Current Theme</span>
|
||||
<h3 class="theme-name">{{ currentThemeName }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="theme-grid">
|
||||
<button
|
||||
v-for="theme in themes"
|
||||
:key="theme.value"
|
||||
:class="[
|
||||
'theme-card',
|
||||
{ 'theme-card--active': selectedTheme === theme.value }
|
||||
]"
|
||||
@click="selectTheme(theme.value)"
|
||||
>
|
||||
<div class="theme-card__preview" :data-theme="theme.value">
|
||||
<div class="preview-circle"></div>
|
||||
</div>
|
||||
<span class="theme-card__name">{{ theme.label }}</span>
|
||||
<div v-if="selectedTheme === theme.value" class="theme-card__badge">
|
||||
Active
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
|
||||
interface Theme {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const themes: Theme[] = [
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "ocean", label: "Ocean" },
|
||||
{ value: "nordic", label: "Nordic" },
|
||||
{ value: "halloween", label: "Halloween" }
|
||||
];
|
||||
|
||||
const selectedTheme = ref("auto");
|
||||
|
||||
const currentThemeName = computed(() => {
|
||||
const theme = themes.find(t => t.value === selectedTheme.value);
|
||||
return theme ? theme.label : "Auto";
|
||||
});
|
||||
|
||||
function systemDarkModeEnabled() {
|
||||
const computedStyle = window.getComputedStyle(document.body);
|
||||
if (computedStyle?.colorScheme != null) {
|
||||
return computedStyle.colorScheme.includes("dark");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function selectTheme(theme: string) {
|
||||
selectedTheme.value = theme;
|
||||
|
||||
if (theme === "auto") {
|
||||
// Use system preference
|
||||
const systemDark = systemDarkModeEnabled();
|
||||
document.body.className = systemDark ? "dark" : "light";
|
||||
|
||||
// Listen for system theme changes
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
mediaQuery.addEventListener("change", e => {
|
||||
if (selectedTheme.value === "auto") {
|
||||
document.body.className = e.matches ? "dark" : "light";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Manual theme selection
|
||||
document.body.className = theme;
|
||||
}
|
||||
|
||||
// Save preference to localStorage
|
||||
localStorage.setItem("theme-preference", theme);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Load saved preference or default to auto
|
||||
const savedTheme = localStorage.getItem("theme-preference") || "auto";
|
||||
selectTheme(savedTheme);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "scss/variables";
|
||||
@import "scss/media-queries";
|
||||
|
||||
.current-theme {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem 2rem;
|
||||
background-color: var(--background-ui);
|
||||
border-radius: 1rem;
|
||||
border: 2px solid var(--background-40);
|
||||
|
||||
@include mobile-only {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
|
||||
@include mobile-only {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@include mobile-only {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.icon-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&[data-theme="light"] .icon-inner {
|
||||
background: linear-gradient(135deg, #f8f8f8 0%, #e8e8e8 100%);
|
||||
border: 3px solid #01d277;
|
||||
}
|
||||
|
||||
&[data-theme="dark"] .icon-inner {
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #0a0a0a 100%);
|
||||
border: 3px solid #01d277;
|
||||
}
|
||||
|
||||
&[data-theme="ocean"] .icon-inner {
|
||||
background: linear-gradient(135deg, #0f2027 0%, #2c5364 100%);
|
||||
border: 3px solid #00d4ff;
|
||||
}
|
||||
|
||||
&[data-theme="nordic"] .icon-inner {
|
||||
background: linear-gradient(135deg, #f5f0e8 0%, #d8cdb9 100%);
|
||||
border: 3px solid #3d6e4e;
|
||||
}
|
||||
|
||||
&[data-theme="halloween"] .icon-inner {
|
||||
background: linear-gradient(135deg, #1a0e2e 0%, #2d1b3d 100%);
|
||||
border: 3px solid #ff6600;
|
||||
}
|
||||
|
||||
&[data-theme="auto"] .icon-inner {
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
#f8f8f8 0deg 180deg,
|
||||
#1a1a1a 180deg 360deg
|
||||
);
|
||||
border: 3px solid #01d277;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.theme-label {
|
||||
font-size: 0.85rem;
|
||||
color: $text-color-70;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 500;
|
||||
|
||||
@include mobile-only {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
|
||||
@include mobile-only {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.25rem;
|
||||
|
||||
@include mobile-only {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 1rem;
|
||||
background-color: var(--background-ui);
|
||||
border-radius: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
text-align: center;
|
||||
|
||||
@include mobile-only {
|
||||
padding: 0.85rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--highlight-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: var(--highlight-color);
|
||||
background-color: var(--background-40);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@include mobile-only {
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
&__preview {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.75rem;
|
||||
position: relative;
|
||||
border: 1px solid var(--background-40);
|
||||
|
||||
@include mobile-only {
|
||||
height: 100px;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.preview-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// Light Theme Preview
|
||||
&__preview[data-theme="light"] {
|
||||
background: #f8f8f8;
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #01d277;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(8, 28, 36, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// Dark Theme Preview
|
||||
&__preview[data-theme="dark"] {
|
||||
background: #111111;
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #01d277;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 20px;
|
||||
background: rgba(6, 7, 8, 1);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// Ocean Theme Preview
|
||||
&__preview[data-theme="ocean"] {
|
||||
background: #0f2027;
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #00d4ff;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 20px;
|
||||
background: #203a43;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Nordic Theme Preview
|
||||
&__preview[data-theme="nordic"] {
|
||||
background: #f5f0e8;
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #3d6e4e;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 20px;
|
||||
background: #fffef9;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(61, 110, 78, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Halloween Theme Preview
|
||||
&__preview[data-theme="halloween"] {
|
||||
background: #1a0e2e;
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #ff6600;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 20px;
|
||||
background: #2d1b3d;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 102, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto Theme Preview (split)
|
||||
&__preview[data-theme="auto"] {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#f8f8f8 0%,
|
||||
#f8f8f8 50%,
|
||||
#111111 50%,
|
||||
#111111 100%
|
||||
);
|
||||
|
||||
.preview-circle {
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #01d277;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: calc(50% - 10px);
|
||||
height: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(8, 28, 36, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--text-color);
|
||||
|
||||
@include mobile-only {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__badge {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: var(--highlight-color);
|
||||
color: $white;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
|
||||
@include mobile-only {
|
||||
top: 0.4rem;
|
||||
right: 0.4rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user