diff --git a/apps/ui/src/components/layout/sidebar.tsx b/apps/ui/src/components/layout/sidebar.tsx index 3cafe020..39ffef97 100644 --- a/apps/ui/src/components/layout/sidebar.tsx +++ b/apps/ui/src/components/layout/sidebar.tsx @@ -83,159 +83,18 @@ import { useSensors, closestCenter, } from '@dnd-kit/core'; -import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { getHttpApiClient } from '@/lib/http-api-client'; import type { StarterTemplate } from '@/lib/templates'; -interface NavSection { - label?: string; - items: NavItem[]; -} - -interface NavItem { - id: string; - label: string; - icon: any; - shortcut?: string; -} - -// Sortable Project Item Component -interface SortableProjectItemProps { - project: Project; - currentProjectId: string | undefined; - isHighlighted: boolean; - onSelect: (project: Project) => void; -} - -function SortableProjectItem({ - project, - currentProjectId, - isHighlighted, - onSelect, -}: SortableProjectItemProps) { - const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id: project.id, - }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - opacity: isDragging ? 0.5 : 1, - }; - - return ( -
- {/* Drag Handle */} - - - {/* Project content - clickable area */} -
onSelect(project)}> - - {project.name} - {currentProjectId === project.id && } -
-
- ); -} - -// Theme options for project theme selector - derived from the shared config -import { darkThemes, lightThemes } from '@/config/theme-options'; - -const PROJECT_DARK_THEMES = darkThemes.map((opt) => ({ - value: opt.value, - label: opt.label, - icon: opt.Icon, - color: opt.color, -})); - -const PROJECT_LIGHT_THEMES = lightThemes.map((opt) => ({ - value: opt.value, - label: opt.label, - icon: opt.Icon, - color: opt.color, -})); - -// Memoized theme menu item to prevent re-renders during hover -interface ThemeMenuItemProps { - option: { - value: string; - label: string; - icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>; - color: string; - }; - onPreviewEnter: (value: string) => void; - onPreviewLeave: (e: React.PointerEvent) => void; -} - -const ThemeMenuItem = memo(function ThemeMenuItem({ - option, - onPreviewEnter, - onPreviewLeave, -}: ThemeMenuItemProps) { - const Icon = option.icon; - return ( -
onPreviewEnter(option.value)} - onPointerLeave={onPreviewLeave} - > - - - {option.label} - -
- ); -}); - -// Reusable Bug Report Button Component -const BugReportButton = ({ - sidebarExpanded, - onClick, -}: { - sidebarExpanded: boolean; - onClick: () => void; -}) => { - return ( - - ); -}; +// Local imports from subfolder +import type { NavSection, NavItem } from './sidebar/types'; +import { SortableProjectItem, ThemeMenuItem, BugReportButton } from './sidebar/components'; +import { + PROJECT_DARK_THEMES, + PROJECT_LIGHT_THEMES, + SIDEBAR_FEATURE_FLAGS, +} from './sidebar/constants'; export function Sidebar() { const navigate = useNavigate(); @@ -267,12 +126,8 @@ export function Sidebar() { } = useAppStore(); // Environment variable flags for hiding sidebar items - const hideTerminal = import.meta.env.VITE_HIDE_TERMINAL === 'true'; - const hideWiki = import.meta.env.VITE_HIDE_WIKI === 'true'; - const hideRunningAgents = import.meta.env.VITE_HIDE_RUNNING_AGENTS === 'true'; - const hideContext = import.meta.env.VITE_HIDE_CONTEXT === 'true'; - const hideSpecEditor = import.meta.env.VITE_HIDE_SPEC_EDITOR === 'true'; - const hideAiProfiles = import.meta.env.VITE_HIDE_AI_PROFILES === 'true'; + const { hideTerminal, hideWiki, hideRunningAgents, hideContext, hideSpecEditor, hideAiProfiles } = + SIDEBAR_FEATURE_FLAGS; // Get customizable keyboard shortcuts const shortcuts = useKeyboardShortcutsConfig(); diff --git a/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx b/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx new file mode 100644 index 00000000..68a413c4 --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/components/bug-report-button.tsx @@ -0,0 +1,23 @@ +import { Bug } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { BugReportButtonProps } from '../types'; + +export function BugReportButton({ sidebarExpanded, onClick }: BugReportButtonProps) { + return ( + + ); +} diff --git a/apps/ui/src/components/layout/sidebar/components/index.ts b/apps/ui/src/components/layout/sidebar/components/index.ts new file mode 100644 index 00000000..ecc7861e --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/components/index.ts @@ -0,0 +1,3 @@ +export { SortableProjectItem } from './sortable-project-item'; +export { ThemeMenuItem } from './theme-menu-item'; +export { BugReportButton } from './bug-report-button'; diff --git a/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx b/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx new file mode 100644 index 00000000..9d1e567e --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/components/sortable-project-item.tsx @@ -0,0 +1,54 @@ +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Folder, Check, GripVertical } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { SortableProjectItemProps } from '../types'; + +export function SortableProjectItem({ + project, + currentProjectId, + isHighlighted, + onSelect, +}: SortableProjectItemProps) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: project.id, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + + return ( +
+ {/* Drag Handle */} + + + {/* Project content - clickable area */} +
onSelect(project)}> + + {project.name} + {currentProjectId === project.id && } +
+
+ ); +} diff --git a/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx b/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx new file mode 100644 index 00000000..5d9749b2 --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/components/theme-menu-item.tsx @@ -0,0 +1,27 @@ +import { memo } from 'react'; +import { DropdownMenuRadioItem } from '@/components/ui/dropdown-menu'; +import type { ThemeMenuItemProps } from '../types'; + +export const ThemeMenuItem = memo(function ThemeMenuItem({ + option, + onPreviewEnter, + onPreviewLeave, +}: ThemeMenuItemProps) { + const Icon = option.icon; + return ( +
onPreviewEnter(option.value)} + onPointerLeave={onPreviewLeave} + > + + + {option.label} + +
+ ); +}); diff --git a/apps/ui/src/components/layout/sidebar/constants.ts b/apps/ui/src/components/layout/sidebar/constants.ts new file mode 100644 index 00000000..4beca953 --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/constants.ts @@ -0,0 +1,24 @@ +import { darkThemes, lightThemes } from '@/config/theme-options'; + +export const PROJECT_DARK_THEMES = darkThemes.map((opt) => ({ + value: opt.value, + label: opt.label, + icon: opt.Icon, + color: opt.color, +})); + +export const PROJECT_LIGHT_THEMES = lightThemes.map((opt) => ({ + value: opt.value, + label: opt.label, + icon: opt.Icon, + color: opt.color, +})); + +export const SIDEBAR_FEATURE_FLAGS = { + hideTerminal: import.meta.env.VITE_HIDE_TERMINAL === 'true', + hideWiki: import.meta.env.VITE_HIDE_WIKI === 'true', + hideRunningAgents: import.meta.env.VITE_HIDE_RUNNING_AGENTS === 'true', + hideContext: import.meta.env.VITE_HIDE_CONTEXT === 'true', + hideSpecEditor: import.meta.env.VITE_HIDE_SPEC_EDITOR === 'true', + hideAiProfiles: import.meta.env.VITE_HIDE_AI_PROFILES === 'true', +} as const; diff --git a/apps/ui/src/components/layout/sidebar/types.ts b/apps/ui/src/components/layout/sidebar/types.ts new file mode 100644 index 00000000..e76e4917 --- /dev/null +++ b/apps/ui/src/components/layout/sidebar/types.ts @@ -0,0 +1,36 @@ +import type { Project } from '@/lib/electron'; + +export interface NavSection { + label?: string; + items: NavItem[]; +} + +export interface NavItem { + id: string; + label: string; + icon: React.ComponentType<{ className?: string }>; + shortcut?: string; +} + +export interface SortableProjectItemProps { + project: Project; + currentProjectId: string | undefined; + isHighlighted: boolean; + onSelect: (project: Project) => void; +} + +export interface ThemeMenuItemProps { + option: { + value: string; + label: string; + icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>; + color: string; + }; + onPreviewEnter: (value: string) => void; + onPreviewLeave: (e: React.PointerEvent) => void; +} + +export interface BugReportButtonProps { + sidebarExpanded: boolean; + onClick: () => void; +}