From 7d75a73fc8a871f1032eb51aaa8a71b44287b9ba Mon Sep 17 00:00:00 2001 From: Anastasiia Solop <35258279+anastasiya1155@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:22:17 +0100 Subject: [PATCH] Make sidebar sections collapsible in workspaces UI (Vibe Kanban) (#1867) * Done! Here's a summary of the changes: ## Summary I made the sections in the right sidebar collapsible by: 1. **Created `CollapsibleSectionHeader` component** (`frontend/src/components/ui-new/primitives/CollapsibleSectionHeader.tsx`): - Combines the `SectionHeader` styling (left border accent, border-bottom) with collapsible functionality - Supports optional icon with action handler (stops propagation to avoid toggling) - Uses persisted expanded state via `usePersistedExpanded` hook - Shows caret icon that rotates when collapsed 2. **Added new persist keys** (`frontend/src/stores/useUiPreferencesStore.ts`): - `gitPanelRepositories` - for Repositories section - `gitPanelProject` - for Project section - `gitPanelAddRepositories` - for Add Repositories section - `processesSection` - for Processes section 3. **Updated `GitPanel.tsx`**: Replaced `SectionHeader` with `CollapsibleSectionHeader` for the Repositories section, wrapping all repo cards and the Advanced section inside 4. **Updated `GitPanelCreate.tsx`**: Made all three sections collapsible: - Project section - Repositories section - Add Repositories section 5. **Updated `ProcessListContainer.tsx`**: Made the Processes section collapsible while keeping the search bar always visible All sections now: - Collapse when clicking on the section header - Expand when clicking again - Persist their expanded/collapsed state across sessions * Done! I've made the Changes and Dev Server sections collapsible as well: **Updated files:** - `frontend/src/stores/useUiPreferencesStore.ts` - Added `changesSection` and `devServerSection` persist keys - `frontend/src/components/ui-new/views/FileTree.tsx` - Made the "Changes" section collapsible - `frontend/src/components/ui-new/views/PreviewControls.tsx` - Made the "Dev Server" section collapsible All sections now use the `CollapsibleSectionHeader` component and persist their collapsed/expanded state. * Cleanup script changes for workspace 05769879-a23f-49ac-81f4-b17e18f14025 --- .../containers/ProcessListContainer.tsx | 12 ++- .../primitives/CollapsibleSectionHeader.tsx | 76 +++++++++++++++++++ .../src/components/ui-new/views/FileTree.tsx | 46 ++++++----- .../src/components/ui-new/views/GitPanel.tsx | 11 ++- .../ui-new/views/GitPanelCreate.tsx | 30 +++++--- .../ui-new/views/PreviewControls.tsx | 13 ++-- frontend/src/stores/useUiPreferencesStore.ts | 17 ++++- 7 files changed, 162 insertions(+), 43 deletions(-) create mode 100644 frontend/src/components/ui-new/primitives/CollapsibleSectionHeader.tsx diff --git a/frontend/src/components/ui-new/containers/ProcessListContainer.tsx b/frontend/src/components/ui-new/containers/ProcessListContainer.tsx index ded07b74..dd07bec6 100644 --- a/frontend/src/components/ui-new/containers/ProcessListContainer.tsx +++ b/frontend/src/components/ui-new/containers/ProcessListContainer.tsx @@ -2,9 +2,10 @@ import { useEffect, useMemo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useExecutionProcessesContext } from '@/contexts/ExecutionProcessesContext'; import { ProcessListItem } from '../primitives/ProcessListItem'; -import { SectionHeader } from '../primitives/SectionHeader'; +import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader'; import { InputField } from '../primitives/InputField'; import { CaretUpIcon, CaretDownIcon } from '@phosphor-icons/react'; +import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; interface ProcessListContainerProps { selectedProcessId: string | null; @@ -124,8 +125,11 @@ export function ProcessListContainer({ return (
- -
+ {sortedProcesses.length === 0 ? (

{t('processes.noProcesses')}

@@ -144,7 +148,7 @@ export function ProcessListContainer({ ))}
)} -
+ {searchBar}
); diff --git a/frontend/src/components/ui-new/primitives/CollapsibleSectionHeader.tsx b/frontend/src/components/ui-new/primitives/CollapsibleSectionHeader.tsx new file mode 100644 index 00000000..67bebd24 --- /dev/null +++ b/frontend/src/components/ui-new/primitives/CollapsibleSectionHeader.tsx @@ -0,0 +1,76 @@ +import type { Icon } from '@phosphor-icons/react'; +import { CaretDownIcon } from '@phosphor-icons/react'; +import { cn } from '@/lib/utils'; +import { + usePersistedExpanded, + type PersistKey, +} from '@/stores/useUiPreferencesStore'; + +interface CollapsibleSectionHeaderProps { + persistKey: PersistKey; + title: string; + defaultExpanded?: boolean; + icon?: Icon; + onIconClick?: () => void; + children?: React.ReactNode; + className?: string; + contentClassName?: string; +} + +export function CollapsibleSectionHeader({ + persistKey, + title, + defaultExpanded = true, + icon: IconComponent, + onIconClick, + children, + className, + contentClassName, +}: CollapsibleSectionHeaderProps) { + const [expanded, toggle] = usePersistedExpanded(persistKey, defaultExpanded); + + const handleIconClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onIconClick?.(); + }; + + return ( +
+ + {expanded &&
{children}
} +
+ ); +} diff --git a/frontend/src/components/ui-new/views/FileTree.tsx b/frontend/src/components/ui-new/views/FileTree.tsx index bf76250d..0fa89eb3 100644 --- a/frontend/src/components/ui-new/views/FileTree.tsx +++ b/frontend/src/components/ui-new/views/FileTree.tsx @@ -3,7 +3,8 @@ import { cn } from '@/lib/utils'; import { FileTreeSearchBar } from './FileTreeSearchBar'; import { FileTreeNode } from './FileTreeNode'; import type { TreeNode } from '../types/fileTree'; -import { SectionHeader } from '../primitives/SectionHeader'; +import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader'; +import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; interface FileTreeProps { nodes: TreeNode[]; @@ -66,24 +67,31 @@ export function FileTree({ return (
- -
- -
-
- {nodes.length > 0 ? ( - renderNodes(nodes) - ) : ( -
- {searchQuery ? t('common:fileTree.noResults') : 'No changed files'} -
- )} -
+ +
+ +
+
+ {nodes.length > 0 ? ( + renderNodes(nodes) + ) : ( +
+ {searchQuery + ? t('common:fileTree.noResults') + : 'No changed files'} +
+ )} +
+
); } diff --git a/frontend/src/components/ui-new/views/GitPanel.tsx b/frontend/src/components/ui-new/views/GitPanel.tsx index 45bd42bd..c7cd66e5 100644 --- a/frontend/src/components/ui-new/views/GitPanel.tsx +++ b/frontend/src/components/ui-new/views/GitPanel.tsx @@ -6,9 +6,9 @@ import { type RepoAction, } from '@/components/ui-new/primitives/RepoCard'; import { InputField } from '@/components/ui-new/primitives/InputField'; -import { SectionHeader } from '@/components/ui-new/primitives/SectionHeader'; import { ErrorAlert } from '@/components/ui-new/primitives/ErrorAlert'; import { CollapsibleSection } from '../primitives/CollapsibleSection'; +import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader'; import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; export interface RepoInfo { @@ -56,8 +56,11 @@ export function GitPanel({ )} > {error && } - -
+
{repos.map((repo) => (
-
+ ); } diff --git a/frontend/src/components/ui-new/views/GitPanelCreate.tsx b/frontend/src/components/ui-new/views/GitPanelCreate.tsx index 65de19ff..42b8333a 100644 --- a/frontend/src/components/ui-new/views/GitPanelCreate.tsx +++ b/frontend/src/components/ui-new/views/GitPanelCreate.tsx @@ -1,12 +1,13 @@ import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; -import { SectionHeader } from '@/components/ui-new/primitives/SectionHeader'; +import { CollapsibleSectionHeader } from '@/components/ui-new/primitives/CollapsibleSectionHeader'; import { SelectedReposList } from '@/components/ui-new/primitives/SelectedReposList'; import { ProjectSelectorContainer } from '@/components/ui-new/containers/ProjectSelectorContainer'; import { RecentReposListContainer } from '@/components/ui-new/containers/RecentReposListContainer'; import { BrowseRepoButtonContainer } from '@/components/ui-new/containers/BrowseRepoButtonContainer'; import { CreateRepoButtonContainer } from '@/components/ui-new/containers/CreateRepoButtonContainer'; import { WarningIcon } from '@phosphor-icons/react'; +import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; import type { Project, GitBranch, Repo } from 'shared/types'; interface GitPanelCreateProps { @@ -50,8 +51,11 @@ export function GitPanelCreate({ className )} > - -
+ -
+ - -
+ {hasNoRepos ? (
@@ -79,9 +86,12 @@ export function GitPanelCreate({ onBranchChange={onBranchChange} /> )} -
- -
+ +

{t('common:sections.recent')}

@@ -94,7 +104,7 @@ export function GitPanelCreate({

-
+
); } diff --git a/frontend/src/components/ui-new/views/PreviewControls.tsx b/frontend/src/components/ui-new/views/PreviewControls.tsx index 55820001..bc5b2af0 100644 --- a/frontend/src/components/ui-new/views/PreviewControls.tsx +++ b/frontend/src/components/ui-new/views/PreviewControls.tsx @@ -8,12 +8,13 @@ import { } from '@phosphor-icons/react'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; -import { SectionHeader } from '../primitives/SectionHeader'; +import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader'; import { PrimaryButton } from '../primitives/PrimaryButton'; import { VirtualizedProcessLogs, type LogEntry, } from '../VirtualizedProcessLogs'; +import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; interface PreviewControlsProps { logs: LogEntry[]; @@ -56,9 +57,11 @@ export function PreviewControls({ className )} > - - -
+ {/* Controls row: URL bar + Start/Stop button */}
{url && ( @@ -139,7 +142,7 @@ export function PreviewControls({ )}
- + ); } diff --git a/frontend/src/stores/useUiPreferencesStore.ts b/frontend/src/stores/useUiPreferencesStore.ts index a6fa29c6..3b2a5e63 100644 --- a/frontend/src/stores/useUiPreferencesStore.ts +++ b/frontend/src/stores/useUiPreferencesStore.ts @@ -16,9 +16,18 @@ export const PERSIST_KEYS = { // Sidebar sections workspacesSidebarActive: 'workspaces-sidebar-active', workspacesSidebarArchived: 'workspaces-sidebar-archived', - // Git panel + // Git panel sections gitAdvancedSettings: 'git-advanced-settings', gitPanelCreateAddRepo: 'git-panel-create-add-repo', + gitPanelRepositories: 'git-panel-repositories', + gitPanelProject: 'git-panel-project', + gitPanelAddRepositories: 'git-panel-add-repositories', + // Process panel sections + processesSection: 'processes-section', + // Changes panel sections + changesSection: 'changes-section', + // Preview panel sections + devServerSection: 'dev-server-section', // Context bar contextBarPosition: 'context-bar-position', // Pane sizes @@ -35,6 +44,12 @@ export type PersistKey = | typeof PERSIST_KEYS.workspacesSidebarArchived | typeof PERSIST_KEYS.gitAdvancedSettings | typeof PERSIST_KEYS.gitPanelCreateAddRepo + | typeof PERSIST_KEYS.gitPanelRepositories + | typeof PERSIST_KEYS.gitPanelProject + | typeof PERSIST_KEYS.gitPanelAddRepositories + | typeof PERSIST_KEYS.processesSection + | typeof PERSIST_KEYS.changesSection + | typeof PERSIST_KEYS.devServerSection | typeof PERSIST_KEYS.sidebarWidth | typeof PERSIST_KEYS.gitPanelWidth | typeof PERSIST_KEYS.changesPanelWidth