From e6b990a7f9f2cc24324b16be11b52ed78cbaed2f Mon Sep 17 00:00:00 2001 From: Grishma Dhakal Date: Sun, 26 Jul 2026 12:15:40 +0545 Subject: [PATCH 1/8] feat(sidebar): implement tree skeleton loaders and focus-management on project mutations --- .../components/sidebar/sidebar-skeleton.tsx | 58 +++++++++++ .../ui/components/sidebar/sidebar-tree.tsx | 97 ++++++++++--------- .../components/sidebar/workspace-sidebar.tsx | 40 +++++++- 3 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 src/modules/workspace/ui/components/sidebar/sidebar-skeleton.tsx diff --git a/src/modules/workspace/ui/components/sidebar/sidebar-skeleton.tsx b/src/modules/workspace/ui/components/sidebar/sidebar-skeleton.tsx new file mode 100644 index 0000000..480d663 --- /dev/null +++ b/src/modules/workspace/ui/components/sidebar/sidebar-skeleton.tsx @@ -0,0 +1,58 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +// ─── Sidebar-level skeleton (shown while the projects list is loading) ───────── + +export function SidebarSkeleton() { + return ( +
+ {[1, 2, 3].map((i) => ( + + ))} +
+ ); +} + +/** A single project-row placeholder that mimics the TreeNodeTrigger shape. */ +function ProjectRowSkeleton() { + return ( +
+ {/* Expander icon placeholder */} + + {/* Folder icon placeholder */} + + {/* Label */} + +
+ ); +} + +// ─── Project-node skeleton (shown inside an expanded project while its ───────── +// folders / endpoints are being fetched) ───────── + +export function ProjectTreeNodeSkeleton({ level }: { level: number }) { + const indent = level * 16 + 8; + + return ( +
+ {[1, 2, 3].map((i) => ( + + ))} +
+ ); +} + +function TreeRowSkeleton({ indent }: { indent: number }) { + return ( +
+ {/* Expander placeholder */} + + {/* File/folder icon placeholder */} + + {/* Label – vary widths for a natural look */} + +
+ ); +} diff --git a/src/modules/workspace/ui/components/sidebar/sidebar-tree.tsx b/src/modules/workspace/ui/components/sidebar/sidebar-tree.tsx index 49c9bc8..980c0ee 100644 --- a/src/modules/workspace/ui/components/sidebar/sidebar-tree.tsx +++ b/src/modules/workspace/ui/components/sidebar/sidebar-tree.tsx @@ -23,6 +23,7 @@ import { } from "./types"; import { useEndpoints } from "~/hooks/use-endpoints"; import { useFolders } from "~/hooks/use-folders"; +import { ProjectTreeNodeSkeleton } from "./sidebar-skeleton"; function commitRename( currentName: string, @@ -280,8 +281,10 @@ export function ProjectTreeNode({ onDeleteFolder: (id: string) => void; onDeleteEndpoint: (id: string) => void; }) { - const { data: rawFolders = [] } = useFolders(project.id); - const { data: rawEndpoints = [] } = useEndpoints(project.id); + const { data: rawFolders = [], isLoading: isLoadingFolders } = useFolders(project.id); + const { data: rawEndpoints = [], isLoading: isLoadingEndpoints } = useEndpoints(project.id); + + const isLoading = isLoadingFolders || isLoadingEndpoints; const folderTree = buildFolderTree(rawFolders as FolderRow[]); @@ -347,48 +350,54 @@ export function ProjectTreeNode({ - {rootEndpoints.map((endpoint) => ( - onSelectEndpoint(endpoint.id, project.id)} - onRenameConfirm={(name) => { - commitRename( - endpoint.name, - name, - (nextName) => { - onRenameEndpoint(endpoint.id, nextName); - }, - () => setRenamingId(null), - ); - }} - onRenameCancel={() => setRenamingId(null)} - onRenameStart={() => setRenamingId(endpoint.id)} - onDelete={() => onDeleteEndpoint(endpoint.id)} - /> - ))} - {tree.map((collection, index) => ( - - ))} + {isLoading ? ( + + ) : ( + <> + {rootEndpoints.map((endpoint) => ( + onSelectEndpoint(endpoint.id, project.id)} + onRenameConfirm={(name) => { + commitRename( + endpoint.name, + name, + (nextName) => { + onRenameEndpoint(endpoint.id, nextName); + }, + () => setRenamingId(null), + ); + }} + onRenameCancel={() => setRenamingId(null)} + onRenameStart={() => setRenamingId(endpoint.id)} + onDelete={() => onDeleteEndpoint(endpoint.id)} + /> + ))} + {tree.map((collection, index) => ( + + ))} + + )} ); diff --git a/src/modules/workspace/ui/components/sidebar/workspace-sidebar.tsx b/src/modules/workspace/ui/components/sidebar/workspace-sidebar.tsx index 1d4682c..c47b695 100644 --- a/src/modules/workspace/ui/components/sidebar/workspace-sidebar.tsx +++ b/src/modules/workspace/ui/components/sidebar/workspace-sidebar.tsx @@ -13,6 +13,7 @@ import { } from "./dialogs"; import { ProjectSettingsDialog } from "./project-settings-dialog"; import { ProjectTreeNode } from "./sidebar-tree"; +import { SidebarSkeleton } from "./sidebar-skeleton"; import { useCreateProject, useDeleteProject, @@ -43,7 +44,7 @@ export const Sidebar = forwardRef< { selectedEndpointId, onSelectEndpoint, selectedProjectId, onSelectProject }, ref, ) { - const { data: projects = [] } = useProjects(); + const { data: projects = [], isLoading: isLoadingProjects } = useProjects(); const [renamingId, setRenamingId] = useState(null); const [modal, setModal] = useState(null); const [confirmState, setConfirmState] = useState<{ @@ -97,7 +98,9 @@ export const Sidebar = forwardRef< <>