mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat: add multiple color themes to appearance settings
This commit is contained in:
@@ -1,47 +1,275 @@
|
|||||||
//
|
import { useEffect } from 'react'
|
||||||
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
|
import { Moon, Sun, Check } from 'lucide-react'
|
||||||
//
|
import { useTranslation } from 'react-i18next'
|
||||||
// This file is part of the Bichon Email Archiving Project
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
import { Theme, useTheme } from '@/context/theme-context'
|
||||||
import { useCallback, useEffect } from 'react'
|
|
||||||
import { IconMoon, IconSun } from '@tabler/icons-react'
|
|
||||||
import { useTheme } from '@/context/theme-context'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const themeColors: Record<Theme, string> = {
|
||||||
|
'light': '#ffffff',
|
||||||
|
'dark': '#020817',
|
||||||
|
'rose-light': '#fb7185',
|
||||||
|
'rose-dark': '#881337',
|
||||||
|
'orange-light': '#fb923c',
|
||||||
|
'orange-dark': '#9a3412',
|
||||||
|
'green-light': '#4ade80',
|
||||||
|
'green-dark': '#166534',
|
||||||
|
'yellow-light': '#facc15',
|
||||||
|
'yellow-dark': '#a16207',
|
||||||
|
'blue-light': '#60a5fa',
|
||||||
|
'blue-dark': '#1e40af',
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIGHT_THEMES = [
|
||||||
|
{
|
||||||
|
value: 'light',
|
||||||
|
labelKey: 'theme.default',
|
||||||
|
background: '#ffffff',
|
||||||
|
color: '#0f172a',
|
||||||
|
muted: '#cbd5e1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'rose-light',
|
||||||
|
labelKey: 'theme.rose',
|
||||||
|
background: '#fff1f2',
|
||||||
|
color: '#e11d48',
|
||||||
|
muted: '#fda4af',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'orange-light',
|
||||||
|
labelKey: 'theme.orange',
|
||||||
|
background: '#fff7ed',
|
||||||
|
color: '#ea580c',
|
||||||
|
muted: '#fdba74',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'green-light',
|
||||||
|
labelKey: 'theme.green',
|
||||||
|
background: '#f0fdf4',
|
||||||
|
color: '#16a34a',
|
||||||
|
muted: '#86efac',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'yellow-light',
|
||||||
|
labelKey: 'theme.yellow',
|
||||||
|
background: '#fefce8',
|
||||||
|
color: '#ca8a04',
|
||||||
|
muted: '#fde047',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'blue-light',
|
||||||
|
labelKey: 'theme.blue',
|
||||||
|
background: '#eff6ff',
|
||||||
|
color: '#2563eb',
|
||||||
|
muted: '#bfdbfe',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const DARK_THEMES = [
|
||||||
|
{
|
||||||
|
value: 'dark',
|
||||||
|
labelKey: 'theme.default',
|
||||||
|
background: '#020817',
|
||||||
|
color: '#94a3b8',
|
||||||
|
muted: '#334155',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'rose-dark',
|
||||||
|
labelKey: 'theme.rose',
|
||||||
|
background: '#1f1115',
|
||||||
|
color: '#e11d48',
|
||||||
|
muted: '#881337',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'orange-dark',
|
||||||
|
labelKey: 'theme.orange',
|
||||||
|
background: '#1c120d',
|
||||||
|
color: '#f97316',
|
||||||
|
muted: '#9a3412',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'green-dark',
|
||||||
|
labelKey: 'theme.green',
|
||||||
|
background: '#0d1b12',
|
||||||
|
color: '#22c55e',
|
||||||
|
muted: '#166534',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'yellow-dark',
|
||||||
|
labelKey: 'theme.yellow',
|
||||||
|
background: '#1c1608',
|
||||||
|
color: '#facc15',
|
||||||
|
muted: '#a16207',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'blue-dark',
|
||||||
|
labelKey: 'theme.blue',
|
||||||
|
background: '#172554',
|
||||||
|
color: '#60a5fa',
|
||||||
|
muted: '#1e3a8a',
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type ThemeOption =
|
||||||
|
| (typeof LIGHT_THEMES)[number]
|
||||||
|
| (typeof DARK_THEMES)[number]
|
||||||
|
|
||||||
|
function ThemePreview({
|
||||||
|
background,
|
||||||
|
primary,
|
||||||
|
muted,
|
||||||
|
}: {
|
||||||
|
background: string
|
||||||
|
primary: string
|
||||||
|
muted: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='flex h-10 w-20 shrink-0 flex-col gap-1.5 rounded-md border p-1.5'
|
||||||
|
style={{
|
||||||
|
backgroundColor: background,
|
||||||
|
borderColor: muted,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<span className='h-1 w-1 rounded-full' style={{ backgroundColor: muted }} />
|
||||||
|
<span className='h-1 w-1 rounded-full' style={{ backgroundColor: muted }} />
|
||||||
|
<span className='h-1 w-1 rounded-full' style={{ backgroundColor: primary }} />
|
||||||
|
</div>
|
||||||
|
<div className='h-1.5 w-12 rounded-sm' style={{ backgroundColor: primary }} />
|
||||||
|
<div className='h-1 w-16 rounded-sm opacity-70' style={{ backgroundColor: muted }} />
|
||||||
|
<div className='h-1 w-10 rounded-sm opacity-50' style={{ backgroundColor: muted }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ThemeItem({
|
||||||
|
item,
|
||||||
|
active,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
item: ThemeOption
|
||||||
|
active: boolean
|
||||||
|
onSelect: (v: Theme) => void
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => onSelect(item.value as Theme)}
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 transition-colors',
|
||||||
|
active ? 'bg-accent' : 'hover:bg-accent/50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ThemePreview
|
||||||
|
background={item.background}
|
||||||
|
primary={item.color}
|
||||||
|
muted={item.muted}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className='flex-1 text-left text-xs font-medium'>
|
||||||
|
{t(item.labelKey)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
'size-4 shrink-0 transition-opacity',
|
||||||
|
active ? 'opacity-100' : 'opacity-0'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ThemeColumn({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
items,
|
||||||
|
currentTheme,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
icon: React.ReactNode
|
||||||
|
label: string
|
||||||
|
items: readonly ThemeOption[]
|
||||||
|
currentTheme: Theme
|
||||||
|
onSelect: (v: Theme) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<div className='mb-2 flex items-center gap-1.5 px-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/60'>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{items.map((item) => (
|
||||||
|
<ThemeItem
|
||||||
|
key={item.value}
|
||||||
|
item={item}
|
||||||
|
active={currentTheme === item.value}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ThemeSwitch() {
|
export function ThemeSwitch() {
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const isDark = theme.includes('dark')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const themeColor = theme === 'dark' ? '#020817' : '#fff'
|
document
|
||||||
const metaThemeColor = document.querySelector("meta[name='theme-color']")
|
.querySelector("meta[name='theme-color']")
|
||||||
if (metaThemeColor) metaThemeColor.setAttribute('content', themeColor)
|
?.setAttribute('content', themeColors[theme])
|
||||||
}, [theme])
|
|
||||||
|
|
||||||
const toggleTheme = useCallback(() => {
|
|
||||||
setTheme(theme === 'dark' ? 'light' : 'dark')
|
|
||||||
}, [theme])
|
}, [theme])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant='ghost' size='icon' className='scale-95' onClick={toggleTheme}>
|
<Popover>
|
||||||
<IconSun className='size-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0' />
|
<PopoverTrigger asChild>
|
||||||
<IconMoon className='absolute size-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100' />
|
<Button variant='ghost' size='icon' className='rounded-full'>
|
||||||
<span className='sr-only'>Toggle theme</span>
|
{isDark ? (
|
||||||
|
<Moon className='size-5' />
|
||||||
|
) : (
|
||||||
|
<Sun className='size-5' />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
<PopoverContent align='end' className='w-[420px] p-3'>
|
||||||
|
<p className='mb-3 px-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/60'>
|
||||||
|
{t('theme.appearance')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-3'>
|
||||||
|
<ThemeColumn
|
||||||
|
icon={<Sun className='size-3.5' />}
|
||||||
|
label={t('theme.light')}
|
||||||
|
items={LIGHT_THEMES}
|
||||||
|
currentTheme={theme}
|
||||||
|
onSelect={setTheme}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='border-l border-border/40 pl-3'>
|
||||||
|
<ThemeColumn
|
||||||
|
icon={<Moon className='size-3.5' />}
|
||||||
|
label={t('theme.dark')}
|
||||||
|
items={DARK_THEMES}
|
||||||
|
currentTheme={theme}
|
||||||
|
onSelect={setTheme}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
|
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
type Theme = 'dark' | 'light'
|
export type Theme = 'light' | 'dark' | 'rose-light' | 'rose-dark' | 'orange-light'
|
||||||
|
| 'orange-dark' | 'green-light' | 'green-dark' | 'yellow-light' | 'yellow-dark' | 'blue-light' | 'blue-dark'
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
type ThemeProviderProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@@ -41,7 +42,7 @@ const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
|
|||||||
|
|
||||||
export function ThemeProvider({
|
export function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
defaultTheme = 'dark',
|
defaultTheme = 'light',
|
||||||
storageKey = 'vite-ui-theme',
|
storageKey = 'vite-ui-theme',
|
||||||
...props
|
...props
|
||||||
}: ThemeProviderProps) {
|
}: ThemeProviderProps) {
|
||||||
@@ -51,7 +52,8 @@ export function ThemeProvider({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = window.document.documentElement
|
const root = window.document.documentElement
|
||||||
root.classList.remove('light', 'dark')
|
root.classList.remove('light', 'dark', 'rose-light', 'rose-dark', 'orange-light',
|
||||||
|
'orange-dark', 'green-light', 'green-dark', 'yellow-light', 'yellow-dark', 'blue-light', 'blue-dark')
|
||||||
root.classList.add(theme)
|
root.classList.add(theme)
|
||||||
localStorage.setItem(storageKey, theme)
|
localStorage.setItem(storageKey, theme)
|
||||||
}, [theme, storageKey])
|
}, [theme, storageKey])
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
|
|
||||||
//
|
|
||||||
// This file is part of the Bichon Email Archiving Project
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons'
|
import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons'
|
||||||
@@ -32,8 +13,8 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form'
|
} from '@/components/ui/form'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
import { RadioGroup } from '@/components/ui/radio-group'
|
||||||
import { useTheme } from '@/context/theme-context'
|
import { useTheme, Theme } from '@/context/theme-context'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -42,7 +23,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|||||||
import { update_user } from '@/api/users/api'
|
import { update_user } from '@/api/users/api'
|
||||||
import { toast } from '@/hooks/use-toast'
|
import { toast } from '@/hooks/use-toast'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
|
import { Card } from '@/components/ui/card'
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{ value: 'ar', label: 'العربية' },
|
{ value: 'ar', label: 'العربية' },
|
||||||
@@ -65,8 +46,28 @@ const languages = [
|
|||||||
{ value: 'zh-tw', label: '繁體中文' },
|
{ value: 'zh-tw', label: '繁體中文' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const LIGHT_THEMES = [
|
||||||
|
{ value: 'light', labelKey: 'theme.default', background: '#ffffff', color: '#0f172a', muted: '#cbd5e1' },
|
||||||
|
{ value: 'rose-light', labelKey: 'theme.rose', background: '#fff1f2', color: '#e11d48', muted: '#fda4af' },
|
||||||
|
{ value: 'orange-light', labelKey: 'theme.orange', background: '#fff7ed', color: '#ea580c', muted: '#fdba74' },
|
||||||
|
{ value: 'green-light', labelKey: 'theme.green', background: '#f0fdf4', color: '#16a34a', muted: '#86efac' },
|
||||||
|
{ value: 'yellow-light', labelKey: 'theme.yellow', background: '#fefce8', color: '#ca8a04', muted: '#fde047' },
|
||||||
|
{ value: 'blue-light', labelKey: 'theme.blue', background: '#eff6ff', color: '#2563eb', muted: '#bfdbfe' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const DARK_THEMES = [
|
||||||
|
{ value: 'dark', labelKey: 'theme.default', background: '#020817', color: '#94a3b8', muted: '#334155' },
|
||||||
|
{ value: 'rose-dark', labelKey: 'theme.rose', background: '#1f1115', color: '#e11d48', muted: '#881337' },
|
||||||
|
{ value: 'orange-dark', labelKey: 'theme.orange', background: '#1c120d', color: '#f97316', muted: '#9a3412' },
|
||||||
|
{ value: 'green-dark', labelKey: 'theme.green', background: '#0d1b12', color: '#22c55e', muted: '#166534' },
|
||||||
|
{ value: 'yellow-dark', labelKey: 'theme.yellow', background: '#1c1608', color: '#facc15', muted: '#a16207' },
|
||||||
|
{ value: 'blue-dark', labelKey: 'theme.blue', background: '#172554', color: '#60a5fa', muted: '#1e3a8a' }
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const ALL_THEME_VALUES = [...LIGHT_THEMES.map(t => t.value), ...DARK_THEMES.map(t => t.value)] as [string, ...string[]]
|
||||||
|
|
||||||
const appearanceSchema = (t: (key: string) => string) => z.object({
|
const appearanceSchema = (t: (key: string) => string) => z.object({
|
||||||
theme: z.enum(['light', 'dark'], {
|
theme: z.enum(ALL_THEME_VALUES, {
|
||||||
required_error: t('settings.appearance.validation.theme.required'),
|
required_error: t('settings.appearance.validation.theme.required'),
|
||||||
}),
|
}),
|
||||||
language: z.string({
|
language: z.string({
|
||||||
@@ -76,19 +77,30 @@ const appearanceSchema = (t: (key: string) => string) => z.object({
|
|||||||
|
|
||||||
type AppearanceFormValues = z.infer<ReturnType<typeof appearanceSchema>>
|
type AppearanceFormValues = z.infer<ReturnType<typeof appearanceSchema>>
|
||||||
|
|
||||||
|
function ThemePreview({ background, primary, muted }: { background: string; primary: string; muted: string }) {
|
||||||
|
return (
|
||||||
|
<div className='flex h-12 w-full flex-col gap-1.5 rounded-md border p-2 shadow-sm' style={{ backgroundColor: background, borderColor: 'transparent' }}>
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<span className='h-1.5 w-1.5 rounded-full' style={{ backgroundColor: muted }} />
|
||||||
|
<span className='h-1.5 w-1.5 rounded-full' style={{ backgroundColor: primary }} />
|
||||||
|
</div>
|
||||||
|
<div className='h-1.5 w-3/4 rounded-sm' style={{ backgroundColor: primary }} />
|
||||||
|
<div className='h-1.5 w-1/2 rounded-sm opacity-50' style={{ backgroundColor: muted }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AppearanceForm() {
|
export function AppearanceForm() {
|
||||||
const { data: user } = useCurrentUser();
|
const { data: user } = useCurrentUser()
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient()
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation()
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme()
|
||||||
|
|
||||||
|
|
||||||
const form = useForm<AppearanceFormValues>({
|
const form = useForm<AppearanceFormValues>({
|
||||||
resolver: zodResolver(appearanceSchema(t)),
|
resolver: zodResolver(appearanceSchema(t)),
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
theme: (theme as 'light' | 'dark') || 'light',
|
theme: (theme as any) || 'light',
|
||||||
language: i18n.language || 'en',
|
language: i18n.language || 'en',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -108,21 +120,18 @@ export function AppearanceForm() {
|
|||||||
description: (err.response?.data as any)?.message || err.message,
|
description: (err.response?.data as any)?.message || err.message,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
function onSubmit(data: AppearanceFormValues) {
|
function onSubmit(data: AppearanceFormValues) {
|
||||||
i18n.changeLanguage(data.language);
|
i18n.changeLanguage(data.language)
|
||||||
setTheme(data.theme);
|
setTheme(data.theme as Theme)
|
||||||
mutation.mutate(data);
|
mutation.mutate(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-6xl ml-0 px-4">
|
<div className="w-full max-w-6xl ml-0 px-4">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-10 w-full max-w-screen-xl mx-auto px-4 md:px-6">
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-8 w-full max-w-screen-xl mx-auto px-4 md:px-6"
|
|
||||||
>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='language'
|
name='language'
|
||||||
@@ -182,66 +191,61 @@ export function AppearanceForm() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='theme'
|
name='theme'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="space-y-1">
|
<FormItem className="space-y-4">
|
||||||
<FormLabel>{t('settings.appearance.field.theme')}</FormLabel>
|
<div>
|
||||||
<FormDescription>
|
<FormLabel className="text-base">{t('settings.appearance.field.theme')}</FormLabel>
|
||||||
{t('settings.appearance.description.theme')}
|
<FormDescription>{t('settings.appearance.description.theme')}</FormDescription>
|
||||||
</FormDescription>
|
</div>
|
||||||
<FormMessage />
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={field.onChange}
|
onValueChange={(v) => {
|
||||||
|
setTheme(v as Theme);
|
||||||
|
field.onChange(v)
|
||||||
|
}}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
className='grid max-w-md grid-cols-2 gap-8 pt-2'
|
className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 pt-2'
|
||||||
>
|
>
|
||||||
<FormItem>
|
{[...LIGHT_THEMES, ...DARK_THEMES].map((item) => {
|
||||||
<FormLabel className='[&:has([data-state=checked])>div]:border-primary cursor-pointer'>
|
const selected = field.value === item.value
|
||||||
<FormControl>
|
|
||||||
<RadioGroupItem value='light' className='sr-only' />
|
return (
|
||||||
</FormControl>
|
<Card
|
||||||
<div className='items-center rounded-md border-2 border-muted p-1 hover:border-accent'>
|
key={item.value}
|
||||||
<div className='space-y-2 rounded-sm bg-[#ecedef] p-2'>
|
onClick={() => {
|
||||||
<div className='space-y-2 rounded-md bg-white p-2 shadow-sm'>
|
setTheme(item.value as Theme)
|
||||||
<div className='h-2 w-[80px] rounded-lg bg-[#ecedef]' />
|
field.onChange(item.value)
|
||||||
<div className='h-2 w-[100px] rounded-lg bg-[#ecedef]' />
|
}}
|
||||||
</div>
|
className={cn(
|
||||||
<div className='flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm'>
|
"cursor-pointer overflow-hidden border-2 transition-all hover:shadow-md",
|
||||||
<div className='h-4 w-4 rounded-full bg-[#ecedef]' />
|
selected ? "border-primary shadow-md" : "border-muted opacity-80"
|
||||||
<div className='h-2 w-[100px] rounded-lg bg-[#ecedef]' />
|
)}
|
||||||
</div>
|
>
|
||||||
</div>
|
<div className="p-2">
|
||||||
</div>
|
<ThemePreview
|
||||||
<span className='block w-full p-2 text-center font-normal'>
|
background={item.background}
|
||||||
{t('settings.appearance.theme.light')}
|
primary={item.color}
|
||||||
|
muted={item.muted}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-2 flex items-center justify-between">
|
||||||
|
<span className="text-[11px] font-medium uppercase truncate">
|
||||||
|
{t(item.labelKey)}
|
||||||
</span>
|
</span>
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
{selected && (
|
||||||
<FormItem>
|
<CheckIcon className="h-3.5 w-3.5 text-primary" />
|
||||||
<FormLabel className='[&:has([data-state=checked])>div]:border-primary cursor-pointer'>
|
)}
|
||||||
<FormControl>
|
|
||||||
<RadioGroupItem value='dark' className='sr-only' />
|
|
||||||
</FormControl>
|
|
||||||
<div className='items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground'>
|
|
||||||
<div className='space-y-2 rounded-sm bg-slate-950 p-2'>
|
|
||||||
<div className='space-y-2 rounded-md bg-slate-800 p-2 shadow-sm'>
|
|
||||||
<div className='h-2 w-[80px] rounded-lg bg-slate-400' />
|
|
||||||
<div className='h-2 w-[100px] rounded-lg bg-slate-400' />
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm'>
|
|
||||||
<div className='h-4 w-4 rounded-full bg-slate-400' />
|
|
||||||
<div className='h-2 w-[100px] rounded-lg bg-slate-400' />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
<span className='block w-full p-2 text-center font-normal'>
|
)
|
||||||
{t('settings.appearance.theme.dark')}
|
})}
|
||||||
</span>
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+2
-91
@@ -1,101 +1,12 @@
|
|||||||
|
@import "./theme.css";
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 215 28% 17%;
|
|
||||||
|
|
||||||
--muted: 210 40% 96.1%;
|
|
||||||
--muted-foreground: 215 16% 47%;
|
|
||||||
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 215 28% 17%;
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 215 28% 17%;
|
|
||||||
|
|
||||||
--border: 214 32% 88%;
|
|
||||||
--input: 214 32% 88%;
|
|
||||||
|
|
||||||
--primary: 221 83% 53%;
|
|
||||||
--primary-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--secondary: 210 40% 96.1%;
|
|
||||||
--secondary-foreground: 222 47% 11%;
|
|
||||||
--accent: 210 40% 96.1%;
|
|
||||||
--accent-foreground: 222 47% 11%;
|
|
||||||
|
|
||||||
--destructive: 0 84% 60%;
|
|
||||||
--destructive-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--ring: 221 83% 53%;
|
|
||||||
--radius: 0.35rem;
|
|
||||||
|
|
||||||
--chart-1: 221 83% 53%;
|
|
||||||
--chart-2: 200 90% 40%;
|
|
||||||
--chart-3: 35 80% 55%;
|
|
||||||
--chart-4: 280 65% 60%;
|
|
||||||
--chart-5: 340 75% 55%;
|
|
||||||
|
|
||||||
--sidebar-background: 210 40% 98%;
|
|
||||||
--sidebar-foreground: 215 28% 17%;
|
|
||||||
--sidebar-primary: 221 83% 53%;
|
|
||||||
--sidebar-primary-foreground: 210 40% 98%;
|
|
||||||
--sidebar-accent: 214 32% 91%;
|
|
||||||
--sidebar-accent-foreground: 215 28% 17%;
|
|
||||||
--sidebar-border: 214 32% 88%;
|
|
||||||
--sidebar-ring: 221 83% 53%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: 215 20% 12%;
|
|
||||||
--foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--card: 215 20% 16%;
|
|
||||||
--card-foreground: 210 40% 98%;
|
|
||||||
--popover: 215 20% 16%;
|
|
||||||
--popover-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
|
|
||||||
--muted: 215 20% 22%;
|
|
||||||
--muted-foreground: 215 15% 70%;
|
|
||||||
|
|
||||||
--border: 215 20% 28%;
|
|
||||||
--input: 215 20% 28%;
|
|
||||||
|
|
||||||
--primary: 217 90% 70%;
|
|
||||||
--primary-foreground: 215 20% 12%;
|
|
||||||
|
|
||||||
--secondary: 215 20% 22%;
|
|
||||||
--secondary-foreground: 210 40% 98%;
|
|
||||||
--accent: 215 20% 22%;
|
|
||||||
--accent-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--destructive: 0 70% 60%;
|
|
||||||
--destructive-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--ring: 217 90% 70%;
|
|
||||||
|
|
||||||
--chart-1: 217 90% 70%;
|
|
||||||
--chart-2: 200 90% 60%;
|
|
||||||
--chart-3: 35 90% 65%;
|
|
||||||
--chart-4: 280 75% 75%;
|
|
||||||
--chart-5: 340 85% 70%;
|
|
||||||
|
|
||||||
--sidebar-background: 215 20% 10%;
|
|
||||||
--sidebar-foreground: 210 40% 98%;
|
|
||||||
--sidebar-primary: 217 90% 70%;
|
|
||||||
--sidebar-primary-foreground: 215 20% 12%;
|
|
||||||
--sidebar-accent: 215 20% 22%;
|
|
||||||
--sidebar-accent-foreground: 210 40% 98%;
|
|
||||||
--sidebar-border: 215 20% 28%;
|
|
||||||
--sidebar-ring: 217 90% 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* styles.css */
|
|
||||||
.CollapsibleContent {
|
.CollapsibleContent {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,452 @@
|
|||||||
|
/* theme.css */
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 215 28% 17%;
|
||||||
|
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215 16% 47%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 215 28% 17%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 215 28% 17%;
|
||||||
|
|
||||||
|
--border: 214 32% 88%;
|
||||||
|
--input: 214 32% 88%;
|
||||||
|
|
||||||
|
--primary: 221 83% 53%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222 47% 11%;
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222 47% 11%;
|
||||||
|
|
||||||
|
--destructive: 0 84% 60%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--ring: 221 83% 53%;
|
||||||
|
--radius: 0.35rem;
|
||||||
|
|
||||||
|
--chart-1: 221 83% 53%;
|
||||||
|
--chart-2: 200 90% 40%;
|
||||||
|
--chart-3: 35 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 215 20% 12%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--card: 215 20% 16%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 215 20% 16%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
|
||||||
|
--muted: 215 20% 22%;
|
||||||
|
--muted-foreground: 215 15% 70%;
|
||||||
|
|
||||||
|
--border: 215 20% 28%;
|
||||||
|
--input: 215 20% 28%;
|
||||||
|
|
||||||
|
--primary: 217 90% 70%;
|
||||||
|
--primary-foreground: 215 20% 12%;
|
||||||
|
|
||||||
|
--secondary: 215 20% 22%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--accent: 215 20% 22%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 70% 60%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--ring: 217 90% 70%;
|
||||||
|
|
||||||
|
--chart-1: 217 90% 70%;
|
||||||
|
--chart-2: 200 90% 60%;
|
||||||
|
--chart-3: 35 90% 65%;
|
||||||
|
--chart-4: 280 75% 75%;
|
||||||
|
--chart-5: 340 85% 70%;
|
||||||
|
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow-light {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 180 0% 100%;
|
||||||
|
--foreground: 49 5% 15%;
|
||||||
|
--muted: 106 10% 97%;
|
||||||
|
--muted-foreground: 58 5% 55%;
|
||||||
|
--popover: 180 0% 100%;
|
||||||
|
--popover-foreground: 49 5% 15%;
|
||||||
|
--card: 180 0% 100%;
|
||||||
|
--card-foreground: 49 5% 15%;
|
||||||
|
--border: 49 3% 92%;
|
||||||
|
--input: 49 3% 92%;
|
||||||
|
--primary: 48 100% 76%;
|
||||||
|
--primary-foreground: 54 20% 28%;
|
||||||
|
--secondary: 106 10% 97%;
|
||||||
|
--secondary-foreground: 56 10% 22%;
|
||||||
|
--accent: 106 10% 97%;
|
||||||
|
--accent-foreground: 56 10% 22%;
|
||||||
|
--destructive: 2 65% 55%;
|
||||||
|
--destructive-foreground: 106 10% 98%;
|
||||||
|
--ring: 49 5% 15%;
|
||||||
|
--chart-1: 15 65% 55%;
|
||||||
|
--chart-2: 183 45% 50%;
|
||||||
|
--chart-3: 225 35% 35%;
|
||||||
|
--chart-4: 88 55% 75%;
|
||||||
|
--chart-5: 45 60% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow-dark {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 49 10% 20%;
|
||||||
|
--foreground: 106 10% 98%;
|
||||||
|
--muted: 38 10% 25%;
|
||||||
|
--muted-foreground: 56 10% 74%;
|
||||||
|
--popover: 49 10% 23%;
|
||||||
|
--popover-foreground: 106 10% 98%;
|
||||||
|
--card: 49 10% 23%;
|
||||||
|
--card-foreground: 106 10% 98%;
|
||||||
|
--border: 36 10% 30%;
|
||||||
|
--input: 36 10% 30%;
|
||||||
|
--primary: 48 100% 76%;
|
||||||
|
--primary-foreground: 54 20% 28%;
|
||||||
|
--secondary: 38 10% 25%;
|
||||||
|
--secondary-foreground: 106 10% 98%;
|
||||||
|
--accent: 38 10% 25%;
|
||||||
|
--accent-foreground: 106 10% 98%;
|
||||||
|
--destructive: 2 60% 45%;
|
||||||
|
--destructive-foreground: 106 10% 98%;
|
||||||
|
--ring: 66 65% 60%;
|
||||||
|
--chart-1: 262 55% 65%;
|
||||||
|
--chart-2: 165 40% 70%;
|
||||||
|
--chart-3: 60 50% 75%;
|
||||||
|
--chart-4: 312 50% 65%;
|
||||||
|
--chart-5: 6 55% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rose-light {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 180 0% 100%;
|
||||||
|
--foreground: 285 5% 15%;
|
||||||
|
--muted: 286 1% 97%;
|
||||||
|
--muted-foreground: 286 7% 55%;
|
||||||
|
--popover: 180 0% 100%;
|
||||||
|
--popover-foreground: 285 5% 15%;
|
||||||
|
--card: 180 0% 100%;
|
||||||
|
--card-foreground: 285 5% 15%;
|
||||||
|
--border: 286 3% 92%;
|
||||||
|
--input: 286 3% 92%;
|
||||||
|
--primary: 350 55% 45%;
|
||||||
|
--primary-foreground: 355 25% 97%;
|
||||||
|
--secondary: 286 1% 97%;
|
||||||
|
--secondary-foreground: 286 5% 21%;
|
||||||
|
--accent: 286 1% 97%;
|
||||||
|
--accent-foreground: 286 5% 21%;
|
||||||
|
--destructive: 2 65% 55%;
|
||||||
|
--destructive-foreground: 180 0% 98.5%;
|
||||||
|
--ring: 350 55% 45%;
|
||||||
|
--chart-1: 15 65% 55%;
|
||||||
|
--chart-2: 183 45% 50%;
|
||||||
|
--chart-3: 225 35% 35%;
|
||||||
|
--chart-4: 88 55% 75%;
|
||||||
|
--chart-5: 45 60% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rose-dark {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 49 5% 20.5%;
|
||||||
|
--foreground: 180 0% 96%;
|
||||||
|
--muted: 168 5% 31%;
|
||||||
|
--muted-foreground: 286 7% 75%;
|
||||||
|
--popover: 180 3% 24%;
|
||||||
|
--popover-foreground: 180 0% 96%;
|
||||||
|
--card: 56 6% 25%;
|
||||||
|
--card-foreground: 180 0% 96%;
|
||||||
|
--border: 286 5% 33%;
|
||||||
|
--input: 286 5% 33%;
|
||||||
|
--primary: 350 55% 45%;
|
||||||
|
--primary-foreground: 355 25% 97%;
|
||||||
|
--secondary: 286 5% 31%;
|
||||||
|
--secondary-foreground: 180 0% 98.5%;
|
||||||
|
--accent: 34 5% 31%;
|
||||||
|
--accent-foreground: 180 0% 98.5%;
|
||||||
|
--destructive: 2 60% 45%;
|
||||||
|
--destructive-foreground: 350 50% 97%;
|
||||||
|
--ring: 350 60% 50%;
|
||||||
|
--chart-1: 262 55% 65%;
|
||||||
|
--chart-2: 165 40% 70%;
|
||||||
|
--chart-3: 60 50% 75%;
|
||||||
|
--chart-4: 312 50% 65%;
|
||||||
|
--chart-5: 6 55% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange-light {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 180 0% 100%;
|
||||||
|
--foreground: 49 5% 15%;
|
||||||
|
--muted: 106 1% 97%;
|
||||||
|
--muted-foreground: 58 5% 55%;
|
||||||
|
--popover: 180 0% 100%;
|
||||||
|
--popover-foreground: 49 5% 15%;
|
||||||
|
--card: 180 0% 100%;
|
||||||
|
--card-foreground: 49 5% 15%;
|
||||||
|
--border: 49 3% 92%;
|
||||||
|
--input: 49 3% 92%;
|
||||||
|
--primary: 25 85% 58%;
|
||||||
|
--primary-foreground: 106 1% 98.5%;
|
||||||
|
--secondary: 106 1% 97%;
|
||||||
|
--secondary-foreground: 56 10% 22%;
|
||||||
|
--accent: 106 1% 97%;
|
||||||
|
--accent-foreground: 56 10% 22%;
|
||||||
|
--destructive: 2 65% 55%;
|
||||||
|
--destructive-foreground: 106 1% 98.5%;
|
||||||
|
--ring: 25 85% 58%;
|
||||||
|
--chart-1: 15 65% 55%;
|
||||||
|
--chart-2: 183 45% 50%;
|
||||||
|
--chart-3: 225 35% 35%;
|
||||||
|
--chart-4: 88 55% 75%;
|
||||||
|
--chart-5: 45 60% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange-dark {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 49 10% 20.5%;
|
||||||
|
--foreground: 106 1% 98.5%;
|
||||||
|
--muted: 34 10% 29%;
|
||||||
|
--muted-foreground: 56 10% 74%;
|
||||||
|
--popover: 49 10% 23.5%;
|
||||||
|
--popover-foreground: 106 1% 98.5%;
|
||||||
|
--card: 49 10% 23.5%;
|
||||||
|
--card-foreground: 106 1% 98.5%;
|
||||||
|
--border: 34 10% 32%;
|
||||||
|
--input: 34 10% 32%;
|
||||||
|
--primary: 22 80% 55%;
|
||||||
|
--primary-foreground: 106 1% 98.5%;
|
||||||
|
--secondary: 34 10% 29%;
|
||||||
|
--secondary-foreground: 106 1% 98.5%;
|
||||||
|
--accent: 34 10% 29%;
|
||||||
|
--accent-foreground: 106 1% 98.5%;
|
||||||
|
--destructive: 5 65% 55%;
|
||||||
|
--destructive-foreground: 106 1% 98.5%;
|
||||||
|
--ring: 22 75% 62%;
|
||||||
|
--chart-1: 262 55% 65%;
|
||||||
|
--chart-2: 165 40% 70%;
|
||||||
|
--chart-3: 60 50% 75%;
|
||||||
|
--chart-4: 312 50% 65%;
|
||||||
|
--chart-5: 6 55% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-light {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 180 0% 100%;
|
||||||
|
--foreground: 285 5% 14%;
|
||||||
|
--muted: 286 1% 97%;
|
||||||
|
--muted-foreground: 286 7% 55%;
|
||||||
|
--popover: 180 0% 100%;
|
||||||
|
--popover-foreground: 285 5% 14%;
|
||||||
|
--card: 180 0% 100%;
|
||||||
|
--card-foreground: 285 5% 14%;
|
||||||
|
--border: 286 3% 92%;
|
||||||
|
--input: 286 3% 92%;
|
||||||
|
--primary: 149 55% 48%;
|
||||||
|
--primary-foreground: 12 25% 97%;
|
||||||
|
--secondary: 286 1% 97%;
|
||||||
|
--secondary-foreground: 286 5% 21%;
|
||||||
|
--accent: 286 1% 97%;
|
||||||
|
--accent-foreground: 286 5% 21%;
|
||||||
|
--destructive: 2 65% 55%;
|
||||||
|
--destructive-foreground: 180 0% 98.5%;
|
||||||
|
--ring: 149 55% 48%;
|
||||||
|
--chart-1: 15 65% 55%;
|
||||||
|
--chart-2: 183 45% 50%;
|
||||||
|
--chart-3: 225 35% 35%;
|
||||||
|
--chart-4: 88 55% 75%;
|
||||||
|
--chart-5: 45 60% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-dark {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 49 10% 20.5%;
|
||||||
|
--foreground: 180 0% 96%;
|
||||||
|
--muted: 168 5% 31%;
|
||||||
|
--muted-foreground: 286 7% 75%;
|
||||||
|
--popover: 180 3% 24%;
|
||||||
|
--popover-foreground: 180 0% 96%;
|
||||||
|
--card: 56 6% 25%;
|
||||||
|
--card-foreground: 180 0% 96%;
|
||||||
|
--border: 286 5% 33%;
|
||||||
|
--input: 286 5% 33%;
|
||||||
|
--primary: 150 65% 65%;
|
||||||
|
--primary-foreground: 153 35% 20%;
|
||||||
|
--secondary: 286 5% 33%;
|
||||||
|
--secondary-foreground: 180 0% 98.5%;
|
||||||
|
--accent: 34 5% 31%;
|
||||||
|
--accent-foreground: 180 0% 98.5%;
|
||||||
|
--destructive: 2 60% 45%;
|
||||||
|
--destructive-foreground: 17 50% 97%;
|
||||||
|
--ring: 150 55% 55%;
|
||||||
|
--chart-1: 262 55% 65%;
|
||||||
|
--chart-2: 165 40% 70%;
|
||||||
|
--chart-3: 60 50% 75%;
|
||||||
|
--chart-4: 312 50% 65%;
|
||||||
|
--chart-5: 6 55% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-light {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 180 0% 100%;
|
||||||
|
--foreground: 222 47% 11%;
|
||||||
|
--muted: 210 20% 96%;
|
||||||
|
--muted-foreground: 215 25% 40%;
|
||||||
|
--popover: 180 0% 100%;
|
||||||
|
--popover-foreground: 222 47% 11%;
|
||||||
|
--card: 180 0% 100%;
|
||||||
|
--card-foreground: 222 47% 11%;
|
||||||
|
--border: 214 32% 91%;
|
||||||
|
--input: 214 32% 91%;
|
||||||
|
--primary: 221 83% 53%; /* 经典的 Shadcn 蓝色 */
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
--secondary: 210 40% 96%;
|
||||||
|
--secondary-foreground: 222 47% 11%;
|
||||||
|
--accent: 210 40% 96%;
|
||||||
|
--accent-foreground: 222 47% 11%;
|
||||||
|
--destructive: 0 84% 60%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--ring: 221 83% 53%;
|
||||||
|
--chart-1: 221 83% 53%;
|
||||||
|
--chart-2: 199 89% 48%;
|
||||||
|
--chart-3: 225 35% 35%;
|
||||||
|
--chart-4: 201 96% 32%;
|
||||||
|
--chart-5: 190 90% 40%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-dark {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--background: 222 47% 11%;
|
||||||
|
--foreground: 213 31% 91%;
|
||||||
|
--muted: 223 47% 11%;
|
||||||
|
--muted-foreground: 215 20% 65%;
|
||||||
|
--popover: 222 47% 11%;
|
||||||
|
--popover-foreground: 213 31% 91%;
|
||||||
|
--card: 222 47% 11%;
|
||||||
|
--card-foreground: 213 31% 91%;
|
||||||
|
--border: 216 34% 17%;
|
||||||
|
--input: 216 34% 17%;
|
||||||
|
--primary: 217 91% 60%;
|
||||||
|
--primary-foreground: 222 47% 11%;
|
||||||
|
--secondary: 222 47% 11%;
|
||||||
|
--secondary-foreground: 213 31% 91%;
|
||||||
|
--accent: 216 34% 17%;
|
||||||
|
--accent-foreground: 213 31% 91%;
|
||||||
|
--destructive: 0 63% 31%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--ring: 224 76% 48%;
|
||||||
|
--chart-1: 221 83% 53%;
|
||||||
|
--chart-2: 165 40% 70%;
|
||||||
|
--chart-3: 60 50% 75%;
|
||||||
|
--chart-4: 210 50% 65%;
|
||||||
|
--chart-5: 200 55% 65%;
|
||||||
|
--sidebar: var(--background);
|
||||||
|
--sidebar-foreground: var(--foreground);
|
||||||
|
--sidebar-primary: var(--primary);
|
||||||
|
--sidebar-primary-foreground: var(--primary-foreground);
|
||||||
|
--sidebar-accent: var(--accent);
|
||||||
|
--sidebar-accent-foreground: var(--accent-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user