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
This commit is contained in:
Anastasiia Solop
2026-01-09 09:22:17 +01:00
committed by GitHub
parent 3d46edc579
commit 7d75a73fc8
7 changed files with 162 additions and 43 deletions

View File

@@ -2,9 +2,10 @@ import { useEffect, useMemo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useExecutionProcessesContext } from '@/contexts/ExecutionProcessesContext'; import { useExecutionProcessesContext } from '@/contexts/ExecutionProcessesContext';
import { ProcessListItem } from '../primitives/ProcessListItem'; import { ProcessListItem } from '../primitives/ProcessListItem';
import { SectionHeader } from '../primitives/SectionHeader'; import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader';
import { InputField } from '../primitives/InputField'; import { InputField } from '../primitives/InputField';
import { CaretUpIcon, CaretDownIcon } from '@phosphor-icons/react'; import { CaretUpIcon, CaretDownIcon } from '@phosphor-icons/react';
import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore';
interface ProcessListContainerProps { interface ProcessListContainerProps {
selectedProcessId: string | null; selectedProcessId: string | null;
@@ -124,8 +125,11 @@ export function ProcessListContainer({
return ( return (
<div className="h-full w-full bg-secondary flex flex-col overflow-hidden"> <div className="h-full w-full bg-secondary flex flex-col overflow-hidden">
<SectionHeader title={t('sections.processes')} /> <CollapsibleSectionHeader
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-panel scrollbar-track-transparent p-base min-h-0"> title={t('sections.processes')}
persistKey={PERSIST_KEYS.processesSection}
contentClassName="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-panel scrollbar-track-transparent p-base min-h-0"
>
{sortedProcesses.length === 0 ? ( {sortedProcesses.length === 0 ? (
<div className="h-full flex items-center justify-center text-low"> <div className="h-full flex items-center justify-center text-low">
<p className="text-sm">{t('processes.noProcesses')}</p> <p className="text-sm">{t('processes.noProcesses')}</p>
@@ -144,7 +148,7 @@ export function ProcessListContainer({
))} ))}
</div> </div>
)} )}
</div> </CollapsibleSectionHeader>
{searchBar} {searchBar}
</div> </div>
); );

View File

@@ -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 (
<div className={cn('flex flex-col', className)}>
<button
type="button"
onClick={() => toggle()}
className={cn(
'flex items-center justify-between w-full border-b px-base py-half bg-secondary border-l-half border-l-low cursor-pointer'
)}
>
<span className="font-medium truncate text-normal">{title}</span>
<div className="flex items-center gap-half">
{IconComponent && onIconClick && (
<span
role="button"
tabIndex={0}
onClick={handleIconClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleIconClick(e as unknown as React.MouseEvent);
}
}}
className="text-low hover:text-normal"
>
<IconComponent className="size-icon-xs" weight="bold" />
</span>
)}
<CaretDownIcon
weight="fill"
className={cn(
'size-icon-xs text-low transition-transform',
!expanded && '-rotate-90'
)}
/>
</div>
</button>
{expanded && <div className={contentClassName}>{children}</div>}
</div>
);
}

View File

@@ -3,7 +3,8 @@ import { cn } from '@/lib/utils';
import { FileTreeSearchBar } from './FileTreeSearchBar'; import { FileTreeSearchBar } from './FileTreeSearchBar';
import { FileTreeNode } from './FileTreeNode'; import { FileTreeNode } from './FileTreeNode';
import type { TreeNode } from '../types/fileTree'; import type { TreeNode } from '../types/fileTree';
import { SectionHeader } from '../primitives/SectionHeader'; import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader';
import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore';
interface FileTreeProps { interface FileTreeProps {
nodes: TreeNode[]; nodes: TreeNode[];
@@ -66,7 +67,11 @@ export function FileTree({
return ( return (
<div className={cn('w-full h-full bg-secondary flex flex-col', className)}> <div className={cn('w-full h-full bg-secondary flex flex-col', className)}>
<SectionHeader title="Changes" /> <CollapsibleSectionHeader
title="Changes"
persistKey={PERSIST_KEYS.changesSection}
contentClassName="flex flex-col flex-1 min-h-0"
>
<div className="px-base pt-base"> <div className="px-base pt-base">
<FileTreeSearchBar <FileTreeSearchBar
searchQuery={searchQuery} searchQuery={searchQuery}
@@ -80,10 +85,13 @@ export function FileTree({
renderNodes(nodes) renderNodes(nodes)
) : ( ) : (
<div className="p-base text-low text-sm"> <div className="p-base text-low text-sm">
{searchQuery ? t('common:fileTree.noResults') : 'No changed files'} {searchQuery
? t('common:fileTree.noResults')
: 'No changed files'}
</div> </div>
)} )}
</div> </div>
</CollapsibleSectionHeader>
</div> </div>
); );
} }

View File

@@ -6,9 +6,9 @@ import {
type RepoAction, type RepoAction,
} from '@/components/ui-new/primitives/RepoCard'; } from '@/components/ui-new/primitives/RepoCard';
import { InputField } from '@/components/ui-new/primitives/InputField'; 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 { ErrorAlert } from '@/components/ui-new/primitives/ErrorAlert';
import { CollapsibleSection } from '../primitives/CollapsibleSection'; import { CollapsibleSection } from '../primitives/CollapsibleSection';
import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader';
import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore'; import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore';
export interface RepoInfo { export interface RepoInfo {
@@ -56,8 +56,11 @@ export function GitPanel({
)} )}
> >
{error && <ErrorAlert message={error} />} {error && <ErrorAlert message={error} />}
<SectionHeader title={t('common:sections.repositories')} /> <CollapsibleSectionHeader
<div className="flex flex-col p-base gap-base"> title={t('common:sections.repositories')}
persistKey={PERSIST_KEYS.gitPanelRepositories}
contentClassName="flex flex-col p-base gap-base"
>
<div className="flex flex-col gap-base"> <div className="flex flex-col gap-base">
{repos.map((repo) => ( {repos.map((repo) => (
<RepoCard <RepoCard
@@ -101,7 +104,7 @@ export function GitPanel({
/> />
</CollapsibleSection> </CollapsibleSection>
</div> </div>
</div> </CollapsibleSectionHeader>
</div> </div>
); );
} }

View File

@@ -1,12 +1,13 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils'; 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 { SelectedReposList } from '@/components/ui-new/primitives/SelectedReposList';
import { ProjectSelectorContainer } from '@/components/ui-new/containers/ProjectSelectorContainer'; import { ProjectSelectorContainer } from '@/components/ui-new/containers/ProjectSelectorContainer';
import { RecentReposListContainer } from '@/components/ui-new/containers/RecentReposListContainer'; import { RecentReposListContainer } from '@/components/ui-new/containers/RecentReposListContainer';
import { BrowseRepoButtonContainer } from '@/components/ui-new/containers/BrowseRepoButtonContainer'; import { BrowseRepoButtonContainer } from '@/components/ui-new/containers/BrowseRepoButtonContainer';
import { CreateRepoButtonContainer } from '@/components/ui-new/containers/CreateRepoButtonContainer'; import { CreateRepoButtonContainer } from '@/components/ui-new/containers/CreateRepoButtonContainer';
import { WarningIcon } from '@phosphor-icons/react'; import { WarningIcon } from '@phosphor-icons/react';
import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore';
import type { Project, GitBranch, Repo } from 'shared/types'; import type { Project, GitBranch, Repo } from 'shared/types';
interface GitPanelCreateProps { interface GitPanelCreateProps {
@@ -50,8 +51,11 @@ export function GitPanelCreate({
className className
)} )}
> >
<SectionHeader title={t('common:sections.project')} /> <CollapsibleSectionHeader
<div className="p-base border-b"> title={t('common:sections.project')}
persistKey={PERSIST_KEYS.gitPanelProject}
contentClassName="p-base border-b"
>
<ProjectSelectorContainer <ProjectSelectorContainer
projects={projects} projects={projects}
selectedProjectId={selectedProjectId} selectedProjectId={selectedProjectId}
@@ -59,10 +63,13 @@ export function GitPanelCreate({
onProjectSelect={onProjectSelect} onProjectSelect={onProjectSelect}
onCreateProject={onCreateProject} onCreateProject={onCreateProject}
/> />
</div> </CollapsibleSectionHeader>
<SectionHeader title={t('common:sections.repositories')} /> <CollapsibleSectionHeader
<div className="p-base border-b"> title={t('common:sections.repositories')}
persistKey={PERSIST_KEYS.gitPanelRepositories}
contentClassName="p-base border-b"
>
{hasNoRepos ? ( {hasNoRepos ? (
<div className="flex items-center gap-2 p-base rounded bg-warning/10 border border-warning/20"> <div className="flex items-center gap-2 p-base rounded bg-warning/10 border border-warning/20">
<WarningIcon className="h-4 w-4 text-warning shrink-0" /> <WarningIcon className="h-4 w-4 text-warning shrink-0" />
@@ -79,9 +86,12 @@ export function GitPanelCreate({
onBranchChange={onBranchChange} onBranchChange={onBranchChange}
/> />
)} )}
</div> </CollapsibleSectionHeader>
<SectionHeader title={t('common:sections.addRepositories')} /> <CollapsibleSectionHeader
<div className="flex flex-col p-base gap-half"> title={t('common:sections.addRepositories')}
persistKey={PERSIST_KEYS.gitPanelAddRepositories}
contentClassName="flex flex-col p-base gap-half"
>
<p className="text-xs text-low font-medium"> <p className="text-xs text-low font-medium">
{t('common:sections.recent')} {t('common:sections.recent')}
</p> </p>
@@ -94,7 +104,7 @@ export function GitPanelCreate({
</p> </p>
<BrowseRepoButtonContainer onRepoRegistered={onRepoRegistered} /> <BrowseRepoButtonContainer onRepoRegistered={onRepoRegistered} />
<CreateRepoButtonContainer onRepoCreated={onRepoRegistered} /> <CreateRepoButtonContainer onRepoCreated={onRepoRegistered} />
</div> </CollapsibleSectionHeader>
</div> </div>
); );
} }

View File

@@ -8,12 +8,13 @@ import {
} from '@phosphor-icons/react'; } from '@phosphor-icons/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { SectionHeader } from '../primitives/SectionHeader'; import { CollapsibleSectionHeader } from '../primitives/CollapsibleSectionHeader';
import { PrimaryButton } from '../primitives/PrimaryButton'; import { PrimaryButton } from '../primitives/PrimaryButton';
import { import {
VirtualizedProcessLogs, VirtualizedProcessLogs,
type LogEntry, type LogEntry,
} from '../VirtualizedProcessLogs'; } from '../VirtualizedProcessLogs';
import { PERSIST_KEYS } from '@/stores/useUiPreferencesStore';
interface PreviewControlsProps { interface PreviewControlsProps {
logs: LogEntry[]; logs: LogEntry[];
@@ -56,9 +57,11 @@ export function PreviewControls({
className className
)} )}
> >
<SectionHeader title="Dev Server" /> <CollapsibleSectionHeader
title="Dev Server"
<div className="flex flex-col flex-1 overflow-hidden"> persistKey={PERSIST_KEYS.devServerSection}
contentClassName="flex flex-col flex-1 overflow-hidden"
>
{/* Controls row: URL bar + Start/Stop button */} {/* Controls row: URL bar + Start/Stop button */}
<div className="flex items-center gap-half p-base"> <div className="flex items-center gap-half p-base">
{url && ( {url && (
@@ -139,7 +142,7 @@ export function PreviewControls({
)} )}
</div> </div>
</div> </div>
</div> </CollapsibleSectionHeader>
</div> </div>
); );
} }

View File

@@ -16,9 +16,18 @@ export const PERSIST_KEYS = {
// Sidebar sections // Sidebar sections
workspacesSidebarActive: 'workspaces-sidebar-active', workspacesSidebarActive: 'workspaces-sidebar-active',
workspacesSidebarArchived: 'workspaces-sidebar-archived', workspacesSidebarArchived: 'workspaces-sidebar-archived',
// Git panel // Git panel sections
gitAdvancedSettings: 'git-advanced-settings', gitAdvancedSettings: 'git-advanced-settings',
gitPanelCreateAddRepo: 'git-panel-create-add-repo', 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 // Context bar
contextBarPosition: 'context-bar-position', contextBarPosition: 'context-bar-position',
// Pane sizes // Pane sizes
@@ -35,6 +44,12 @@ export type PersistKey =
| typeof PERSIST_KEYS.workspacesSidebarArchived | typeof PERSIST_KEYS.workspacesSidebarArchived
| typeof PERSIST_KEYS.gitAdvancedSettings | typeof PERSIST_KEYS.gitAdvancedSettings
| typeof PERSIST_KEYS.gitPanelCreateAddRepo | 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.sidebarWidth
| typeof PERSIST_KEYS.gitPanelWidth | typeof PERSIST_KEYS.gitPanelWidth
| typeof PERSIST_KEYS.changesPanelWidth | typeof PERSIST_KEYS.changesPanelWidth