mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +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 { 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 {
|
||||
UI_SANS_FONT_OPTIONS,
|
||||
@@ -11,6 +11,7 @@ import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { FontSelector } from '@/components/shared';
|
||||
import type { Theme } from '../shared/types';
|
||||
import type { SidebarStyle } from '@automaker/types';
|
||||
|
||||
interface AppearanceSectionProps {
|
||||
effectiveTheme: Theme;
|
||||
@@ -18,7 +19,14 @@ interface 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
|
||||
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
|
||||
@@ -189,6 +197,94 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -698,6 +698,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
||||
fontFamilySans: settings.fontFamilySans ?? null,
|
||||
fontFamilyMono: settings.fontFamilyMono ?? null,
|
||||
sidebarOpen: settings.sidebarOpen ?? true,
|
||||
sidebarStyle: settings.sidebarStyle ?? 'unified',
|
||||
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
||||
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
|
||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||
|
||||
@@ -53,6 +53,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
|
||||
'terminalFontFamily', // Maps to terminalState.fontFamily
|
||||
'openTerminalMode', // Maps to terminalState.openTerminalMode
|
||||
'sidebarOpen',
|
||||
'sidebarStyle',
|
||||
'chatHistoryOpen',
|
||||
'maxConcurrency',
|
||||
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
|
||||
@@ -697,6 +698,7 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
||||
useAppStore.setState({
|
||||
theme: serverSettings.theme as unknown as ThemeMode,
|
||||
sidebarOpen: serverSettings.sidebarOpen,
|
||||
sidebarStyle: serverSettings.sidebarStyle ?? 'unified',
|
||||
chatHistoryOpen: serverSettings.chatHistoryOpen,
|
||||
maxConcurrency: serverSettings.maxConcurrency,
|
||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { Sidebar } from '@/components/layout/sidebar';
|
||||
import { ProjectSwitcher } from '@/components/layout/project-switcher';
|
||||
import {
|
||||
FileBrowserProvider,
|
||||
useFileBrowser,
|
||||
@@ -167,6 +168,7 @@ function RootLayoutContent() {
|
||||
theme,
|
||||
fontFamilySans,
|
||||
fontFamilyMono,
|
||||
sidebarStyle,
|
||||
skipSandboxWarning,
|
||||
setSkipSandboxWarning,
|
||||
fetchCodexModels,
|
||||
@@ -860,6 +862,8 @@ function RootLayoutContent() {
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{/* Discord-style layout: narrow project switcher + expandable sidebar */}
|
||||
{sidebarStyle === 'discord' && <ProjectSwitcher />}
|
||||
<Sidebar />
|
||||
<div
|
||||
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
EventHook,
|
||||
ClaudeApiProfile,
|
||||
ClaudeCompatibleProvider,
|
||||
SidebarStyle,
|
||||
} from '@automaker/types';
|
||||
import {
|
||||
getAllCursorModelIds,
|
||||
@@ -610,6 +611,7 @@ export interface AppState {
|
||||
// View state
|
||||
currentView: ViewMode;
|
||||
sidebarOpen: boolean;
|
||||
sidebarStyle: SidebarStyle; // 'unified' (modern) or 'discord' (classic two-sidebar layout)
|
||||
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
||||
|
||||
// Agent Session state (per-project, keyed by project path)
|
||||
@@ -1046,6 +1048,7 @@ export interface AppActions {
|
||||
setCurrentView: (view: ViewMode) => void;
|
||||
toggleSidebar: () => void;
|
||||
setSidebarOpen: (open: boolean) => void;
|
||||
setSidebarStyle: (style: SidebarStyle) => void;
|
||||
toggleMobileSidebarHidden: () => void;
|
||||
setMobileSidebarHidden: (hidden: boolean) => void;
|
||||
|
||||
@@ -1471,6 +1474,7 @@ const initialState: AppState = {
|
||||
projectHistoryIndex: -1,
|
||||
currentView: 'welcome',
|
||||
sidebarOpen: true,
|
||||
sidebarStyle: 'unified', // Default to modern unified sidebar
|
||||
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
||||
lastSelectedSessionByProject: {},
|
||||
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 }),
|
||||
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
|
||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||
setSidebarStyle: (style) => set({ sidebarStyle: style }),
|
||||
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
|
||||
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ export { DEFAULT_PROMPT_CUSTOMIZATION } from './prompts.js';
|
||||
// Settings types and constants
|
||||
export type {
|
||||
ThemeMode,
|
||||
SidebarStyle,
|
||||
PlanningMode,
|
||||
ThinkingLevel,
|
||||
ServerLogLevel,
|
||||
|
||||
@@ -78,6 +78,14 @@ export type ServerLogLevel = 'error' | 'warn' | 'info' | 'debug';
|
||||
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
|
||||
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.
|
||||
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
||||
@@ -836,6 +844,8 @@ export interface GlobalSettings {
|
||||
// UI State Preferences
|
||||
/** Whether sidebar is currently open */
|
||||
sidebarOpen: boolean;
|
||||
/** Sidebar layout style ('unified' = modern single sidebar, 'discord' = classic two-sidebar layout) */
|
||||
sidebarStyle: SidebarStyle;
|
||||
/** Whether chat history panel is open */
|
||||
chatHistoryOpen: boolean;
|
||||
|
||||
@@ -1310,6 +1320,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
skipClaudeSetup: false,
|
||||
theme: 'dark',
|
||||
sidebarOpen: true,
|
||||
sidebarStyle: 'unified',
|
||||
chatHistoryOpen: false,
|
||||
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
||||
defaultSkipTests: true,
|
||||
|
||||
Reference in New Issue
Block a user