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:
@@ -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 (
|
||||
<div className="h-full w-full bg-secondary flex flex-col overflow-hidden">
|
||||
<SectionHeader title={t('sections.processes')} />
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-panel scrollbar-track-transparent p-base min-h-0">
|
||||
<CollapsibleSectionHeader
|
||||
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 ? (
|
||||
<div className="h-full flex items-center justify-center text-low">
|
||||
<p className="text-sm">{t('processes.noProcesses')}</p>
|
||||
@@ -144,7 +148,7 @@ export function ProcessListContainer({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
{searchBar}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={cn('w-full h-full bg-secondary flex flex-col', className)}>
|
||||
<SectionHeader title="Changes" />
|
||||
<div className="px-base pt-base">
|
||||
<FileTreeSearchBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={onSearchChange}
|
||||
isAllExpanded={isAllExpanded}
|
||||
onToggleExpandAll={onToggleExpandAll}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-base flex-1 min-h-0 overflow-auto scrollbar-thin scrollbar-thumb-panel scrollbar-track-transparent">
|
||||
{nodes.length > 0 ? (
|
||||
renderNodes(nodes)
|
||||
) : (
|
||||
<div className="p-base text-low text-sm">
|
||||
{searchQuery ? t('common:fileTree.noResults') : 'No changed files'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CollapsibleSectionHeader
|
||||
title="Changes"
|
||||
persistKey={PERSIST_KEYS.changesSection}
|
||||
contentClassName="flex flex-col flex-1 min-h-0"
|
||||
>
|
||||
<div className="px-base pt-base">
|
||||
<FileTreeSearchBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={onSearchChange}
|
||||
isAllExpanded={isAllExpanded}
|
||||
onToggleExpandAll={onToggleExpandAll}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-base flex-1 min-h-0 overflow-auto scrollbar-thin scrollbar-thumb-panel scrollbar-track-transparent">
|
||||
{nodes.length > 0 ? (
|
||||
renderNodes(nodes)
|
||||
) : (
|
||||
<div className="p-base text-low text-sm">
|
||||
{searchQuery
|
||||
? t('common:fileTree.noResults')
|
||||
: 'No changed files'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 && <ErrorAlert message={error} />}
|
||||
<SectionHeader title={t('common:sections.repositories')} />
|
||||
<div className="flex flex-col p-base gap-base">
|
||||
<CollapsibleSectionHeader
|
||||
title={t('common:sections.repositories')}
|
||||
persistKey={PERSIST_KEYS.gitPanelRepositories}
|
||||
contentClassName="flex flex-col p-base gap-base"
|
||||
>
|
||||
<div className="flex flex-col gap-base">
|
||||
{repos.map((repo) => (
|
||||
<RepoCard
|
||||
@@ -101,7 +104,7 @@ export function GitPanel({
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
>
|
||||
<SectionHeader title={t('common:sections.project')} />
|
||||
<div className="p-base border-b">
|
||||
<CollapsibleSectionHeader
|
||||
title={t('common:sections.project')}
|
||||
persistKey={PERSIST_KEYS.gitPanelProject}
|
||||
contentClassName="p-base border-b"
|
||||
>
|
||||
<ProjectSelectorContainer
|
||||
projects={projects}
|
||||
selectedProjectId={selectedProjectId}
|
||||
@@ -59,10 +63,13 @@ export function GitPanelCreate({
|
||||
onProjectSelect={onProjectSelect}
|
||||
onCreateProject={onCreateProject}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
|
||||
<SectionHeader title={t('common:sections.repositories')} />
|
||||
<div className="p-base border-b">
|
||||
<CollapsibleSectionHeader
|
||||
title={t('common:sections.repositories')}
|
||||
persistKey={PERSIST_KEYS.gitPanelRepositories}
|
||||
contentClassName="p-base border-b"
|
||||
>
|
||||
{hasNoRepos ? (
|
||||
<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" />
|
||||
@@ -79,9 +86,12 @@ export function GitPanelCreate({
|
||||
onBranchChange={onBranchChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<SectionHeader title={t('common:sections.addRepositories')} />
|
||||
<div className="flex flex-col p-base gap-half">
|
||||
</CollapsibleSectionHeader>
|
||||
<CollapsibleSectionHeader
|
||||
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">
|
||||
{t('common:sections.recent')}
|
||||
</p>
|
||||
@@ -94,7 +104,7 @@ export function GitPanelCreate({
|
||||
</p>
|
||||
<BrowseRepoButtonContainer onRepoRegistered={onRepoRegistered} />
|
||||
<CreateRepoButtonContainer onRepoCreated={onRepoRegistered} />
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
>
|
||||
<SectionHeader title="Dev Server" />
|
||||
|
||||
<div className="flex flex-col flex-1 overflow-hidden">
|
||||
<CollapsibleSectionHeader
|
||||
title="Dev Server"
|
||||
persistKey={PERSIST_KEYS.devServerSection}
|
||||
contentClassName="flex flex-col flex-1 overflow-hidden"
|
||||
>
|
||||
{/* Controls row: URL bar + Start/Stop button */}
|
||||
<div className="flex items-center gap-half p-base">
|
||||
{url && (
|
||||
@@ -139,7 +142,7 @@ export function PreviewControls({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSectionHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user