mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: Add sidebar style options to appearance settings
- Introduced a new section in the Appearance settings to allow users to choose between 'unified' and 'discord' sidebar layouts. - Updated the app state and settings migration to include the new sidebarStyle property. - Enhanced the UI to reflect the selected sidebar style with appropriate visual feedback. - Ensured synchronization of sidebar style settings across the application.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
import { Palette, Moon, Sun, Type, PanelLeft, Columns2 } from 'lucide-react';
|
||||||
import { darkThemes, lightThemes } from '@/config/theme-options';
|
import { darkThemes, lightThemes } from '@/config/theme-options';
|
||||||
import {
|
import {
|
||||||
UI_SANS_FONT_OPTIONS,
|
UI_SANS_FONT_OPTIONS,
|
||||||
@@ -11,6 +11,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { FontSelector } from '@/components/shared';
|
import { FontSelector } from '@/components/shared';
|
||||||
import type { Theme } from '../shared/types';
|
import type { Theme } from '../shared/types';
|
||||||
|
import type { SidebarStyle } from '@automaker/types';
|
||||||
|
|
||||||
interface AppearanceSectionProps {
|
interface AppearanceSectionProps {
|
||||||
effectiveTheme: Theme;
|
effectiveTheme: Theme;
|
||||||
@@ -18,7 +19,14 @@ interface AppearanceSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
|
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
|
||||||
const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore();
|
const {
|
||||||
|
fontFamilySans,
|
||||||
|
fontFamilyMono,
|
||||||
|
setFontSans,
|
||||||
|
setFontMono,
|
||||||
|
sidebarStyle,
|
||||||
|
setSidebarStyle,
|
||||||
|
} = useAppStore();
|
||||||
|
|
||||||
// Determine if current theme is light or dark
|
// Determine if current theme is light or dark
|
||||||
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
|
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
|
||||||
@@ -189,6 +197,94 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar Style Section */}
|
||||||
|
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<PanelLeft className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<Label className="text-foreground font-medium">Sidebar Layout</Label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
||||||
|
Choose between a modern unified sidebar or classic Discord-style layout with a separate
|
||||||
|
project switcher.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* Unified Sidebar Option */}
|
||||||
|
<button
|
||||||
|
onClick={() => setSidebarStyle('unified')}
|
||||||
|
className={cn(
|
||||||
|
'group flex flex-col items-center gap-3 p-4 rounded-xl',
|
||||||
|
'text-sm font-medium transition-all duration-200 ease-out',
|
||||||
|
sidebarStyle === 'unified'
|
||||||
|
? [
|
||||||
|
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
|
||||||
|
'border-2 border-brand-500/40',
|
||||||
|
'text-foreground',
|
||||||
|
'shadow-md shadow-brand-500/10',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'bg-accent/30 hover:bg-accent/50',
|
||||||
|
'border border-border/50 hover:border-border',
|
||||||
|
'text-muted-foreground hover:text-foreground',
|
||||||
|
'hover:shadow-sm',
|
||||||
|
],
|
||||||
|
'hover:scale-[1.02] active:scale-[0.98]'
|
||||||
|
)}
|
||||||
|
data-testid="sidebar-style-unified"
|
||||||
|
>
|
||||||
|
<PanelLeft
|
||||||
|
className={cn(
|
||||||
|
'w-8 h-8 transition-all duration-200',
|
||||||
|
sidebarStyle === 'unified' ? 'text-brand-500' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium">Unified</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
Single sidebar with project dropdown
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Discord-style Sidebar Option */}
|
||||||
|
<button
|
||||||
|
onClick={() => setSidebarStyle('discord')}
|
||||||
|
className={cn(
|
||||||
|
'group flex flex-col items-center gap-3 p-4 rounded-xl',
|
||||||
|
'text-sm font-medium transition-all duration-200 ease-out',
|
||||||
|
sidebarStyle === 'discord'
|
||||||
|
? [
|
||||||
|
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
|
||||||
|
'border-2 border-brand-500/40',
|
||||||
|
'text-foreground',
|
||||||
|
'shadow-md shadow-brand-500/10',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'bg-accent/30 hover:bg-accent/50',
|
||||||
|
'border border-border/50 hover:border-border',
|
||||||
|
'text-muted-foreground hover:text-foreground',
|
||||||
|
'hover:shadow-sm',
|
||||||
|
],
|
||||||
|
'hover:scale-[1.02] active:scale-[0.98]'
|
||||||
|
)}
|
||||||
|
data-testid="sidebar-style-discord"
|
||||||
|
>
|
||||||
|
<Columns2
|
||||||
|
className={cn(
|
||||||
|
'w-8 h-8 transition-all duration-200',
|
||||||
|
sidebarStyle === 'discord' ? 'text-brand-500' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium">Classic</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
Separate project switcher + sidebar
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -698,6 +698,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
fontFamilySans: settings.fontFamilySans ?? null,
|
fontFamilySans: settings.fontFamilySans ?? null,
|
||||||
fontFamilyMono: settings.fontFamilyMono ?? null,
|
fontFamilyMono: settings.fontFamilyMono ?? null,
|
||||||
sidebarOpen: settings.sidebarOpen ?? true,
|
sidebarOpen: settings.sidebarOpen ?? true,
|
||||||
|
sidebarStyle: settings.sidebarStyle ?? 'unified',
|
||||||
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
||||||
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
|
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
|
||||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
|
|||||||
'terminalFontFamily', // Maps to terminalState.fontFamily
|
'terminalFontFamily', // Maps to terminalState.fontFamily
|
||||||
'openTerminalMode', // Maps to terminalState.openTerminalMode
|
'openTerminalMode', // Maps to terminalState.openTerminalMode
|
||||||
'sidebarOpen',
|
'sidebarOpen',
|
||||||
|
'sidebarStyle',
|
||||||
'chatHistoryOpen',
|
'chatHistoryOpen',
|
||||||
'maxConcurrency',
|
'maxConcurrency',
|
||||||
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
|
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
|
||||||
@@ -697,6 +698,7 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
|||||||
useAppStore.setState({
|
useAppStore.setState({
|
||||||
theme: serverSettings.theme as unknown as ThemeMode,
|
theme: serverSettings.theme as unknown as ThemeMode,
|
||||||
sidebarOpen: serverSettings.sidebarOpen,
|
sidebarOpen: serverSettings.sidebarOpen,
|
||||||
|
sidebarStyle: serverSettings.sidebarStyle ?? 'unified',
|
||||||
chatHistoryOpen: serverSettings.chatHistoryOpen,
|
chatHistoryOpen: serverSettings.chatHistoryOpen,
|
||||||
maxConcurrency: serverSettings.maxConcurrency,
|
maxConcurrency: serverSettings.maxConcurrency,
|
||||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import { Sidebar } from '@/components/layout/sidebar';
|
import { Sidebar } from '@/components/layout/sidebar';
|
||||||
|
import { ProjectSwitcher } from '@/components/layout/project-switcher';
|
||||||
import {
|
import {
|
||||||
FileBrowserProvider,
|
FileBrowserProvider,
|
||||||
useFileBrowser,
|
useFileBrowser,
|
||||||
@@ -167,6 +168,7 @@ function RootLayoutContent() {
|
|||||||
theme,
|
theme,
|
||||||
fontFamilySans,
|
fontFamilySans,
|
||||||
fontFamilyMono,
|
fontFamilyMono,
|
||||||
|
sidebarStyle,
|
||||||
skipSandboxWarning,
|
skipSandboxWarning,
|
||||||
setSkipSandboxWarning,
|
setSkipSandboxWarning,
|
||||||
fetchCodexModels,
|
fetchCodexModels,
|
||||||
@@ -860,6 +862,8 @@ function RootLayoutContent() {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* Discord-style layout: narrow project switcher + expandable sidebar */}
|
||||||
|
{sidebarStyle === 'discord' && <ProjectSwitcher />}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div
|
<div
|
||||||
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import type {
|
|||||||
EventHook,
|
EventHook,
|
||||||
ClaudeApiProfile,
|
ClaudeApiProfile,
|
||||||
ClaudeCompatibleProvider,
|
ClaudeCompatibleProvider,
|
||||||
|
SidebarStyle,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
getAllCursorModelIds,
|
getAllCursorModelIds,
|
||||||
@@ -610,6 +611,7 @@ export interface AppState {
|
|||||||
// View state
|
// View state
|
||||||
currentView: ViewMode;
|
currentView: ViewMode;
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
sidebarStyle: SidebarStyle; // 'unified' (modern) or 'discord' (classic two-sidebar layout)
|
||||||
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
||||||
|
|
||||||
// Agent Session state (per-project, keyed by project path)
|
// Agent Session state (per-project, keyed by project path)
|
||||||
@@ -1046,6 +1048,7 @@ export interface AppActions {
|
|||||||
setCurrentView: (view: ViewMode) => void;
|
setCurrentView: (view: ViewMode) => void;
|
||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
setSidebarOpen: (open: boolean) => void;
|
setSidebarOpen: (open: boolean) => void;
|
||||||
|
setSidebarStyle: (style: SidebarStyle) => void;
|
||||||
toggleMobileSidebarHidden: () => void;
|
toggleMobileSidebarHidden: () => void;
|
||||||
setMobileSidebarHidden: (hidden: boolean) => void;
|
setMobileSidebarHidden: (hidden: boolean) => void;
|
||||||
|
|
||||||
@@ -1471,6 +1474,7 @@ const initialState: AppState = {
|
|||||||
projectHistoryIndex: -1,
|
projectHistoryIndex: -1,
|
||||||
currentView: 'welcome',
|
currentView: 'welcome',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
|
sidebarStyle: 'unified', // Default to modern unified sidebar
|
||||||
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
||||||
lastSelectedSessionByProject: {},
|
lastSelectedSessionByProject: {},
|
||||||
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
||||||
@@ -1929,6 +1933,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
setCurrentView: (view) => set({ currentView: view }),
|
setCurrentView: (view) => set({ currentView: view }),
|
||||||
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
|
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
|
||||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||||
|
setSidebarStyle: (style) => set({ sidebarStyle: style }),
|
||||||
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
|
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
|
||||||
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
|
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export { DEFAULT_PROMPT_CUSTOMIZATION } from './prompts.js';
|
|||||||
// Settings types and constants
|
// Settings types and constants
|
||||||
export type {
|
export type {
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
|
SidebarStyle,
|
||||||
PlanningMode,
|
PlanningMode,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
ServerLogLevel,
|
ServerLogLevel,
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export type ServerLogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|||||||
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
|
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
|
||||||
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
|
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SidebarStyle - Sidebar layout style options
|
||||||
|
*
|
||||||
|
* - 'unified': Single sidebar with integrated project dropdown (default, modern)
|
||||||
|
* - 'discord': Two sidebars - narrow project switcher + expandable navigation sidebar (classic)
|
||||||
|
*/
|
||||||
|
export type SidebarStyle = 'unified' | 'discord';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thinking token budget mapping based on Claude SDK documentation.
|
* Thinking token budget mapping based on Claude SDK documentation.
|
||||||
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
||||||
@@ -836,6 +844,8 @@ export interface GlobalSettings {
|
|||||||
// UI State Preferences
|
// UI State Preferences
|
||||||
/** Whether sidebar is currently open */
|
/** Whether sidebar is currently open */
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
/** Sidebar layout style ('unified' = modern single sidebar, 'discord' = classic two-sidebar layout) */
|
||||||
|
sidebarStyle: SidebarStyle;
|
||||||
/** Whether chat history panel is open */
|
/** Whether chat history panel is open */
|
||||||
chatHistoryOpen: boolean;
|
chatHistoryOpen: boolean;
|
||||||
|
|
||||||
@@ -1310,6 +1320,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
|||||||
skipClaudeSetup: false,
|
skipClaudeSetup: false,
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
|
sidebarStyle: 'unified',
|
||||||
chatHistoryOpen: false,
|
chatHistoryOpen: false,
|
||||||
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
||||||
defaultSkipTests: true,
|
defaultSkipTests: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user