feat: add gradient effects settings and enhance theme customization options

This commit is contained in:
catlog22
2026-02-04 23:24:16 +08:00
parent 369b470969
commit 0664937b98
8 changed files with 435 additions and 21 deletions

View File

@@ -20,7 +20,21 @@ import { generateThemeFromHue } from '@/lib/colorGenerator';
*/
export function ThemeSelector() {
const { formatMessage } = useIntl();
const { colorScheme, resolvedTheme, customHue, isCustomTheme, setColorScheme, setTheme, setCustomHue } = useTheme();
const {
colorScheme,
resolvedTheme,
customHue,
isCustomTheme,
gradientLevel,
enableHoverGlow,
enableBackgroundAnimation,
setColorScheme,
setTheme,
setCustomHue,
setGradientLevel,
setEnableHoverGlow,
setEnableBackgroundAnimation,
} = useTheme();
// Local state for preview hue (uncommitted changes)
const [previewHue, setPreviewHue] = useState<number | null>(customHue);
@@ -251,6 +265,74 @@ export function ThemeSelector() {
</div>
)}
{/* Gradient Effects Settings */}
<div>
<h3 className="text-sm font-medium text-text mb-3">
{formatMessage({ id: 'theme.gradient.title' })}
</h3>
{/* Gradient Level Selection */}
<div className="space-y-4">
<div
className="flex gap-2"
role="radiogroup"
aria-label={formatMessage({ id: 'theme.gradient.title' })}
>
{(['off', 'standard', 'enhanced'] as const).map((level) => (
<button
key={level}
onClick={() => setGradientLevel(level)}
role="radio"
aria-checked={gradientLevel === level}
className={`
flex-1 px-3 py-2 rounded-lg text-sm font-medium
transition-all duration-200 border-2
${gradientLevel === level
? 'border-accent bg-surface shadow-md'
: 'border-border bg-bg hover:bg-surface'
}
focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2
`}
>
{formatMessage({ id: `theme.gradient.${level}` })}
</button>
))}
</div>
{/* Hover Glow Checkbox */}
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={enableHoverGlow}
onChange={(e) => setEnableHoverGlow(e.target.checked)}
className="
w-4 h-4 rounded border-border text-accent
focus:ring-2 focus:ring-accent focus:ring-offset-2
"
/>
<span className="text-sm text-text">
{formatMessage({ id: 'theme.gradient.hoverGlow' })}
</span>
</label>
{/* Background Animation Checkbox */}
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={enableBackgroundAnimation}
onChange={(e) => setEnableBackgroundAnimation(e.target.checked)}
className="
w-4 h-4 rounded border-border text-accent
focus:ring-2 focus:ring-accent focus:ring-offset-2
"
/>
<span className="text-sm text-text">
{formatMessage({ id: 'theme.gradient.bgAnimation' })}
</span>
</label>
</div>
</div>
{/* Theme Mode Selection */}
<div>
<h3 className="text-sm font-medium text-text mb-3">

View File

@@ -4,8 +4,17 @@
// Convenient hook for theme management with multi-color scheme support
import { useCallback } from 'react';
import { useAppStore, selectTheme, selectResolvedTheme, selectCustomHue, selectIsCustomTheme } from '../stores/appStore';
import type { Theme, ColorScheme } from '../types/store';
import {
useAppStore,
selectTheme,
selectResolvedTheme,
selectCustomHue,
selectIsCustomTheme,
selectGradientLevel,
selectEnableHoverGlow,
selectEnableBackgroundAnimation,
} from '../stores/appStore';
import type { Theme, ColorScheme, GradientLevel } from '../types/store';
export interface UseThemeReturn {
/** Current theme preference ('light', 'dark', 'system') */
@@ -20,6 +29,12 @@ export interface UseThemeReturn {
customHue: number | null;
/** Whether the current theme is a custom theme */
isCustomTheme: boolean;
/** Gradient level: 'off', 'standard', or 'enhanced' */
gradientLevel: GradientLevel;
/** Whether hover glow effects are enabled */
enableHoverGlow: boolean;
/** Whether background gradient animation is enabled */
enableBackgroundAnimation: boolean;
/** Set theme preference */
setTheme: (theme: Theme) => void;
/** Set color scheme */
@@ -28,6 +43,12 @@ export interface UseThemeReturn {
setCustomHue: (hue: number | null) => void;
/** Toggle between light and dark (ignores system) */
toggleTheme: () => void;
/** Set gradient level */
setGradientLevel: (level: GradientLevel) => void;
/** Set hover glow enabled */
setEnableHoverGlow: (enabled: boolean) => void;
/** Set background animation enabled */
setEnableBackgroundAnimation: (enabled: boolean) => void;
}
/**
@@ -54,10 +75,16 @@ export function useTheme(): UseThemeReturn {
const colorScheme = useAppStore((state) => state.colorScheme);
const customHue = useAppStore(selectCustomHue);
const isCustomTheme = useAppStore(selectIsCustomTheme);
const gradientLevel = useAppStore(selectGradientLevel);
const enableHoverGlow = useAppStore(selectEnableHoverGlow);
const enableBackgroundAnimation = useAppStore(selectEnableBackgroundAnimation);
const setThemeAction = useAppStore((state) => state.setTheme);
const setColorSchemeAction = useAppStore((state) => state.setColorScheme);
const setCustomHueAction = useAppStore((state) => state.setCustomHue);
const toggleThemeAction = useAppStore((state) => state.toggleTheme);
const setGradientLevelAction = useAppStore((state) => state.setGradientLevel);
const setEnableHoverGlowAction = useAppStore((state) => state.setEnableHoverGlow);
const setEnableBackgroundAnimationAction = useAppStore((state) => state.setEnableBackgroundAnimation);
const setTheme = useCallback(
(newTheme: Theme) => {
@@ -84,6 +111,27 @@ export function useTheme(): UseThemeReturn {
toggleThemeAction();
}, [toggleThemeAction]);
const setGradientLevel = useCallback(
(level: GradientLevel) => {
setGradientLevelAction(level);
},
[setGradientLevelAction]
);
const setEnableHoverGlow = useCallback(
(enabled: boolean) => {
setEnableHoverGlowAction(enabled);
},
[setEnableHoverGlowAction]
);
const setEnableBackgroundAnimation = useCallback(
(enabled: boolean) => {
setEnableBackgroundAnimationAction(enabled);
},
[setEnableBackgroundAnimationAction]
);
return {
theme,
resolvedTheme,
@@ -91,9 +139,15 @@ export function useTheme(): UseThemeReturn {
colorScheme,
customHue,
isCustomTheme,
gradientLevel,
enableHoverGlow,
enableBackgroundAnimation,
setTheme,
setColorScheme,
setCustomHue,
toggleTheme,
setGradientLevel,
setEnableHoverGlow,
setEnableBackgroundAnimation,
};
}

View File

@@ -433,3 +433,105 @@
.animate-spin {
animation: spin 1s linear infinite;
}
/* ===========================
Gradient Effects System
Conditional styles based on data attributes
=========================== */
/* Disable gradients when off */
[data-gradient="off"] .bg-gradient-primary,
[data-gradient="off"] .bg-gradient-brand,
[data-gradient="off"] .bg-gradient-accent {
background-image: none !important;
background: inherit;
}
[data-gradient="off"] .gradient-text {
background-image: none !important;
-webkit-background-clip: unset;
background-clip: unset;
-webkit-text-fill-color: inherit;
}
/* Standard gradients (default) */
[data-gradient="standard"] .bg-gradient-primary {
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
}
[data-gradient="standard"] .bg-gradient-brand {
background: linear-gradient(135deg, hsl(var(--accent)) 0%, hsl(var(--primary)) 100%);
}
[data-gradient="standard"] .bg-gradient-accent {
background: linear-gradient(90deg, hsl(var(--accent)) 0%, hsl(var(--primary)) 100%);
}
/* Enhanced gradients - more vibrant with multiple color stops */
[data-gradient="enhanced"] .bg-gradient-primary {
background: linear-gradient(135deg,
hsl(var(--primary)) 0%,
hsl(var(--accent)) 50%,
hsl(var(--secondary)) 100%
);
}
[data-gradient="enhanced"] .bg-gradient-brand {
background: linear-gradient(135deg,
hsl(var(--accent)) 0%,
hsl(var(--primary)) 40%,
hsl(var(--secondary)) 100%
);
}
[data-gradient="enhanced"] .bg-gradient-accent {
background: linear-gradient(90deg,
hsl(var(--accent)) 0%,
hsl(var(--primary)) 50%,
hsl(var(--accent)) 100%
);
}
/* Hover glow effects - disabled when data-hover-glow="false" */
.hover-glow,
.hover-glow-primary {
transition: box-shadow 0.3s ease;
}
.hover-glow:hover {
box-shadow: 0 0 20px hsla(var(--accent), 0.4);
}
.hover-glow-primary:hover {
box-shadow: 0 0 20px hsla(var(--primary), 0.4);
}
[data-hover-glow="false"] .hover-glow:hover,
[data-hover-glow="false"] .hover-glow-primary:hover {
box-shadow: none;
}
/* Background animation keyframes */
@keyframes slow-gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Animated background gradient class */
.animate-slow-gradient {
background-size: 200% 200%;
animation: slow-gradient 15s ease infinite;
}
/* Disable background animation when data-bg-animation="false" */
[data-bg-animation="false"] .animate-slow-gradient {
animation: none;
background-size: 100% 100%;
}

View File

@@ -26,5 +26,13 @@
"preview.surface": "Card",
"preview.accent": "Accent",
"save": "Save Custom Theme",
"reset": "Reset to Preset"
"reset": "Reset to Preset",
"gradient": {
"title": "Gradient Effects",
"off": "Off",
"standard": "Standard",
"enhanced": "Enhanced",
"hoverGlow": "Enable hover glow effects",
"bgAnimation": "Enable background gradient animation"
}
}

View File

@@ -26,5 +26,13 @@
"preview.surface": "卡片",
"preview.accent": "强调色",
"save": "保存自定义主题",
"reset": "重置为预设"
"reset": "重置为预设",
"gradient": {
"title": "渐变效果",
"off": "关闭",
"standard": "标准",
"enhanced": "增强",
"hoverGlow": "启用悬停光晕效果",
"bgAnimation": "启用背景渐变动画"
}
}

View File

@@ -5,7 +5,7 @@
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import type { AppStore, Theme, ColorScheme, Locale, ViewMode, SessionFilter, LiteTaskType, DashboardLayouts, WidgetConfig } from '../types/store';
import type { AppStore, Theme, ColorScheme, GradientLevel, Locale, ViewMode, SessionFilter, LiteTaskType, DashboardLayouts, WidgetConfig } from '../types/store';
import { DEFAULT_DASHBOARD_LAYOUT } from '../components/dashboard/defaultLayouts';
import { getInitialLocale, updateIntl } from '../lib/i18n';
import { getThemeId } from '../lib/theme';
@@ -41,7 +41,10 @@ const resolveTheme = (theme: Theme): 'light' | 'dark' => {
const applyThemeToDocument = (
resolvedTheme: 'light' | 'dark',
colorScheme: ColorScheme,
customHue: number | null
customHue: number | null,
gradientLevel: GradientLevel = 'standard',
enableHoverGlow: boolean = true,
enableBackgroundAnimation: boolean = false
): void => {
if (typeof document === 'undefined') return;
@@ -92,13 +95,16 @@ const applyThemeToDocument = (
// Set color scheme attribute
document.documentElement.setAttribute('data-color-scheme', colorScheme);
// Apply gradient settings
document.documentElement.setAttribute('data-gradient', gradientLevel);
document.documentElement.setAttribute('data-hover-glow', String(enableHoverGlow));
document.documentElement.setAttribute('data-bg-animation', String(enableBackgroundAnimation));
};
// Use View Transition API for smooth transitions (progressive enhancement)
// @ts-expect-error - View Transition API not yet in TypeScript DOM types
if (document.startViewTransition) {
// @ts-expect-error - View Transition API not yet in TypeScript DOM types
document.startViewTransition(performThemeUpdate);
if (typeof document !== 'undefined' && 'startViewTransition' in document) {
(document as unknown as { startViewTransition: (callback: () => void) => void }).startViewTransition(performThemeUpdate);
} else {
// Fallback: apply immediately without transition
performThemeUpdate();
@@ -114,6 +120,11 @@ const initialState = {
customHue: null as number | null,
isCustomTheme: false,
// Gradient settings
gradientLevel: 'standard' as GradientLevel,
enableHoverGlow: true,
enableBackgroundAnimation: false,
// Locale
locale: getInitialLocale() as Locale,
@@ -150,31 +161,31 @@ export const useAppStore = create<AppStore>()(
set({ theme, resolvedTheme: resolved }, false, 'setTheme');
// Apply theme using helper (encapsulates DOM manipulation)
const { colorScheme, customHue } = get();
applyThemeToDocument(resolved, colorScheme, customHue);
const { colorScheme, customHue, gradientLevel, enableHoverGlow, enableBackgroundAnimation } = get();
applyThemeToDocument(resolved, colorScheme, customHue, gradientLevel, enableHoverGlow, enableBackgroundAnimation);
},
setColorScheme: (colorScheme: ColorScheme) => {
set({ colorScheme, customHue: null, isCustomTheme: false }, false, 'setColorScheme');
// Apply color scheme using helper (encapsulates DOM manipulation)
const { resolvedTheme } = get();
applyThemeToDocument(resolvedTheme, colorScheme, null);
const { resolvedTheme, gradientLevel, enableHoverGlow, enableBackgroundAnimation } = get();
applyThemeToDocument(resolvedTheme, colorScheme, null, gradientLevel, enableHoverGlow, enableBackgroundAnimation);
},
setCustomHue: (hue: number | null) => {
if (hue === null) {
// Reset to preset theme
const { colorScheme, resolvedTheme } = get();
const { colorScheme, resolvedTheme, gradientLevel, enableHoverGlow, enableBackgroundAnimation } = get();
set({ customHue: null, isCustomTheme: false }, false, 'setCustomHue');
applyThemeToDocument(resolvedTheme, colorScheme, null);
applyThemeToDocument(resolvedTheme, colorScheme, null, gradientLevel, enableHoverGlow, enableBackgroundAnimation);
return;
}
// Apply custom hue
set({ customHue: hue, isCustomTheme: true }, false, 'setCustomHue');
const { resolvedTheme, colorScheme } = get();
applyThemeToDocument(resolvedTheme, colorScheme, hue);
const { resolvedTheme, colorScheme, gradientLevel, enableHoverGlow, enableBackgroundAnimation } = get();
applyThemeToDocument(resolvedTheme, colorScheme, hue, gradientLevel, enableHoverGlow, enableBackgroundAnimation);
},
toggleTheme: () => {
@@ -183,6 +194,26 @@ export const useAppStore = create<AppStore>()(
get().setTheme(newTheme);
},
// ========== Gradient Settings Actions ==========
setGradientLevel: (level: GradientLevel) => {
set({ gradientLevel: level }, false, 'setGradientLevel');
const { resolvedTheme, colorScheme, customHue, enableHoverGlow, enableBackgroundAnimation } = get();
applyThemeToDocument(resolvedTheme, colorScheme, customHue, level, enableHoverGlow, enableBackgroundAnimation);
},
setEnableHoverGlow: (enabled: boolean) => {
set({ enableHoverGlow: enabled }, false, 'setEnableHoverGlow');
const { resolvedTheme, colorScheme, customHue, gradientLevel, enableBackgroundAnimation } = get();
applyThemeToDocument(resolvedTheme, colorScheme, customHue, gradientLevel, enabled, enableBackgroundAnimation);
},
setEnableBackgroundAnimation: (enabled: boolean) => {
set({ enableBackgroundAnimation: enabled }, false, 'setEnableBackgroundAnimation');
const { resolvedTheme, colorScheme, customHue, gradientLevel, enableHoverGlow } = get();
applyThemeToDocument(resolvedTheme, colorScheme, customHue, gradientLevel, enableHoverGlow, enabled);
},
// ========== Locale Actions ==========
setLocale: (locale: Locale) => {
@@ -279,6 +310,9 @@ export const useAppStore = create<AppStore>()(
theme: state.theme,
colorScheme: state.colorScheme,
customHue: state.customHue,
gradientLevel: state.gradientLevel,
enableHoverGlow: state.enableHoverGlow,
enableBackgroundAnimation: state.enableBackgroundAnimation,
locale: state.locale,
sidebarCollapsed: state.sidebarCollapsed,
expandedNavGroups: state.expandedNavGroups,
@@ -291,7 +325,14 @@ export const useAppStore = create<AppStore>()(
state.resolvedTheme = resolved;
state.isCustomTheme = state.customHue !== null;
// Apply theme using helper (encapsulates DOM manipulation)
applyThemeToDocument(resolved, state.colorScheme, state.customHue);
applyThemeToDocument(
resolved,
state.colorScheme,
state.customHue,
state.gradientLevel ?? 'standard',
state.enableHoverGlow ?? true,
state.enableBackgroundAnimation ?? false
);
}
// Apply locale on rehydration
if (state) {
@@ -313,7 +354,14 @@ if (typeof window !== 'undefined') {
const resolved = getSystemTheme();
useAppStore.setState({ resolvedTheme: resolved });
// Apply theme using helper (encapsulates DOM manipulation)
applyThemeToDocument(resolved, state.colorScheme, state.customHue);
applyThemeToDocument(
resolved,
state.colorScheme,
state.customHue,
state.gradientLevel,
state.enableHoverGlow,
state.enableBackgroundAnimation
);
}
});
}
@@ -324,6 +372,9 @@ export const selectResolvedTheme = (state: AppStore) => state.resolvedTheme;
export const selectColorScheme = (state: AppStore) => state.colorScheme;
export const selectCustomHue = (state: AppStore) => state.customHue;
export const selectIsCustomTheme = (state: AppStore) => state.isCustomTheme;
export const selectGradientLevel = (state: AppStore) => state.gradientLevel;
export const selectEnableHoverGlow = (state: AppStore) => state.enableHoverGlow;
export const selectEnableBackgroundAnimation = (state: AppStore) => state.enableBackgroundAnimation;
export const selectLocale = (state: AppStore) => state.locale;
export const selectSidebarOpen = (state: AppStore) => state.sidebarOpen;
export const selectCurrentView = (state: AppStore) => state.currentView;

View File

@@ -266,3 +266,101 @@ export const NODE_TYPE_CONFIGS: Record<FlowNodeType, NodeTypeConfig> = {
handles: { inputs: 1, outputs: 1 },
},
};
// ========== Quick Templates ==========
/**
* Quick template definition for common prompt patterns
*/
export interface QuickTemplate {
id: string;
label: string;
description: string;
icon: string;
color: string;
data: Partial<PromptTemplateNodeData>;
}
/**
* Predefined quick templates for common workflow patterns
* All use 'prompt-template' type with preset configurations
*/
export const QUICK_TEMPLATES: QuickTemplate[] = [
{
id: 'analysis',
label: 'Analysis',
description: 'Code review, architecture analysis',
icon: 'Search',
color: 'bg-emerald-500',
data: {
label: 'Analyze',
instruction: 'Analyze the code for:\n1. Architecture patterns\n2. Code quality\n3. Potential issues',
tool: 'gemini',
mode: 'analysis',
},
},
{
id: 'implementation',
label: 'Implementation',
description: 'Write code, create files',
icon: 'Code',
color: 'bg-violet-500',
data: {
label: 'Implement',
instruction: 'Implement the following:\n\n[Describe what to implement]',
tool: 'codex',
mode: 'write',
},
},
{
id: 'file-operation',
label: 'File Operation',
description: 'Save, read, or transform files',
icon: 'FileOutput',
color: 'bg-amber-500',
data: {
label: 'Save Output',
instruction: 'Save {{previous_output}} to ./output/result.md\n\nFormat as markdown with summary.',
mode: 'write',
contextRefs: [],
},
},
{
id: 'conditional',
label: 'Conditional',
description: 'Branch based on condition',
icon: 'GitBranch',
color: 'bg-orange-500',
data: {
label: 'Decision',
instruction: 'If {{previous.status}} === "success":\n Continue to next step\nElse:\n Stop and report error',
mode: 'mainprocess',
contextRefs: [],
},
},
{
id: 'parallel',
label: 'Parallel Start',
description: 'Fork into parallel branches',
icon: 'GitFork',
color: 'bg-cyan-500',
data: {
label: 'Parallel Tasks',
instruction: 'Execute the following tasks in parallel:\n1. [Task A]\n2. [Task B]\n3. [Task C]',
mode: 'mainprocess',
},
},
{
id: 'merge',
label: 'Merge Results',
description: 'Combine parallel outputs',
icon: 'GitMerge',
color: 'bg-pink-500',
data: {
label: 'Merge',
instruction: 'Combine results from:\n- {{task_a}}\n- {{task_b}}\n- {{task_c}}\n\nGenerate unified summary.',
mode: 'analysis',
contextRefs: [],
},
},
];

View File

@@ -7,6 +7,7 @@
export type Theme = 'light' | 'dark' | 'system';
export type ColorScheme = 'blue' | 'green' | 'orange' | 'purple';
export type GradientLevel = 'off' | 'standard' | 'enhanced';
export type Locale = 'en' | 'zh';
export type ViewMode = 'sessions' | 'liteTasks' | 'project-overview' | 'sessionDetail' | 'liteTaskDetail' | 'loop-monitor' | 'issue-manager' | 'orchestrator';
export type SessionFilter = 'all' | 'active' | 'archived';
@@ -42,6 +43,11 @@ export interface AppState {
customHue: number | null; // Custom hue value (0-360) for theme customization
isCustomTheme: boolean; // Indicates if custom theme is active
// Gradient settings
gradientLevel: GradientLevel; // Gradient intensity: off, standard, enhanced
enableHoverGlow: boolean; // Enable hover glow effects
enableBackgroundAnimation: boolean; // Enable background gradient animation
// Locale
locale: Locale;
@@ -72,6 +78,11 @@ export interface AppActions {
setColorScheme: (scheme: ColorScheme) => void; // New: set color scheme
setCustomHue: (hue: number | null) => void; // Set custom hue for theme customization
// Gradient settings actions
setGradientLevel: (level: GradientLevel) => void;
setEnableHoverGlow: (enabled: boolean) => void;
setEnableBackgroundAnimation: (enabled: boolean) => void;
// Locale actions
setLocale: (locale: Locale) => void;