mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 22:36:19 +00:00
feat(sidebar): §14 sidebar orchestration, filter chips, pinned rail, trailing column (#850)
* feat(sidebar): §14 sidebar orchestration (filter chips, pinned rail, trailing column) - Add All / Up / Down / Updates filter chips with live counts; active chip filters the list; chip-filtered files computed in EditorLayout with useMemo - Surface the PINNED group with a 3px cyan left rail and glow via the sidebarPinnedGroupRail token; reuses the brand token already on the active row - Compact brand row from three stacked elements to a single 44px horizontal bar - Restructure StackRow trailing column as fixed slots: label dots (max 3 + +N overflow), update-dot | git-pending icon (priority order), kebab; add reserved invisible checkbox slot for PR2 bulk mode - Export statusText / statusColor from StackRow and reuse them in StackList remote-results section to remove the duplicate inline logic - Lift filterChip state and chip-filtered files to EditorLayout; remove filterChip from StackListProps to eliminate the dual-path redundancy - Remove unused labels param from buildGroups and the void labels workaround - Wrap filteredFiles in useMemo so filterCounts memo is not defeated on every render * fix(sidebar): move statusText and statusColor to stack-status-utils react-refresh/only-export-components requires component files to export only components. Move the two utility functions and StackRowStatus type to a dedicated stack-status-utils.ts so StackRow.tsx is a pure component module. Update StackList.tsx and EditorLayout.tsx to import from the new source directly.
This commit is contained in:
@@ -73,8 +73,8 @@ import { VulnerabilityScanSheet } from './VulnerabilityScanSheet';
|
||||
import { StackSidebar } from '@/components/sidebar/StackSidebar';
|
||||
import { usePinnedStacks } from '@/hooks/usePinnedStacks';
|
||||
import { useSidebarGroupCollapse } from '@/hooks/useSidebarGroupCollapse';
|
||||
import type { StackRowStatus } from '@/components/sidebar/StackRow';
|
||||
import type { StackMenuCtx } from '@/components/sidebar/sidebar-types';
|
||||
import type { StackRowStatus } from '@/components/sidebar/stack-status-utils';
|
||||
import type { FilterChip, StackMenuCtx } from '@/components/sidebar/sidebar-types';
|
||||
import { StackFileExplorer } from '@/components/files/StackFileExplorer';
|
||||
|
||||
interface ContainerInfo {
|
||||
@@ -1931,9 +1931,9 @@ export default function EditorLayout() {
|
||||
// Stack name is now the same as selectedFile (no extension to strip)
|
||||
const stackName = selectedFile || '';
|
||||
|
||||
// Filter files based on search query
|
||||
const filteredFiles = files.filter(file =>
|
||||
file.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredFiles = useMemo(
|
||||
() => files.filter(file => file.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
[files, searchQuery],
|
||||
);
|
||||
|
||||
// Get display name for stack (now just returns the name as-is since no extension)
|
||||
@@ -1947,6 +1947,23 @@ export default function EditorLayout() {
|
||||
if (evictedOldest) toast.info('Pinned. Unpinned oldest (max 10).');
|
||||
}, [evictedOldest]);
|
||||
|
||||
const [filterChip, setFilterChip] = useState<FilterChip>('all');
|
||||
|
||||
const filterCounts = useMemo(() => ({
|
||||
all: filteredFiles.length,
|
||||
up: filteredFiles.filter(f => stackStatuses[f] === 'running').length,
|
||||
down: filteredFiles.filter(f => stackStatuses[f] === 'exited').length,
|
||||
updates: filteredFiles.filter(f => !!stackUpdates[f]).length,
|
||||
}), [filteredFiles, stackStatuses, stackUpdates]);
|
||||
|
||||
const chipFilteredFiles = useMemo(() => {
|
||||
if (filterChip === 'all') return filteredFiles;
|
||||
if (filterChip === 'up') return filteredFiles.filter(f => stackStatuses[f] === 'running');
|
||||
if (filterChip === 'down') return filteredFiles.filter(f => stackStatuses[f] === 'exited');
|
||||
if (filterChip === 'updates') return filteredFiles.filter(f => !!stackUpdates[f]);
|
||||
return filteredFiles;
|
||||
}, [filteredFiles, filterChip, stackStatuses, stackUpdates]);
|
||||
|
||||
const { isCollapsed, toggle: toggleCollapse } = useSidebarGroupCollapse(activeNode?.id);
|
||||
|
||||
const remoteResults = useMemo(() => {
|
||||
@@ -2286,8 +2303,11 @@ export default function EditorLayout() {
|
||||
canCreate={can('stack:create')}
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
filterChip={filterChip}
|
||||
filterCounts={filterCounts}
|
||||
onFilterChipChange={setFilterChip}
|
||||
list={{
|
||||
files: filteredFiles ?? [],
|
||||
files: chipFilteredFiles,
|
||||
isLoading,
|
||||
isPaid,
|
||||
selectedFile,
|
||||
@@ -2296,7 +2316,6 @@ export default function EditorLayout() {
|
||||
stackStatuses: stackStatuses as Record<string, StackRowStatus | undefined>,
|
||||
stackUpdates,
|
||||
gitSourcePendingMap,
|
||||
labels,
|
||||
pinnedFiles: pinned,
|
||||
isCollapsed,
|
||||
toggleCollapse,
|
||||
|
||||
Reference in New Issue
Block a user