From c39ee1ce2ad5ca274e9a8e4198020b0811d14c29 Mon Sep 17 00:00:00 2001
From: ZhenYi <434836402@qq.com>
Date: Mon, 18 May 2026 20:45:00 +0800
Subject: [PATCH] refactor(ui): update shared layout and UI components for new
theme system
Update Header, ProjectMessageFavoritesDrawer, RepoHeader, sheet,
EmptyState, ErrorState, LoadingState, PageHeader. Add ConfirmDialog
component.
---
src/components/layout/Header.tsx | 486 +++++++++---------
.../layout/ProjectMessageFavoritesDrawer.tsx | 132 +++--
src/components/repo/RepoHeader.tsx | 115 +++--
src/components/ui/ConfirmDialog.tsx | 55 ++
src/components/ui/EmptyState.tsx | 40 +-
src/components/ui/ErrorState.tsx | 52 +-
src/components/ui/LoadingState.tsx | 20 +-
src/components/ui/PageHeader.tsx | 24 +-
src/components/ui/sheet.tsx | 9 +-
9 files changed, 552 insertions(+), 381 deletions(-)
create mode 100644 src/components/ui/ConfirmDialog.tsx
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index 79f5d15..546c7fc 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -1,37 +1,45 @@
-import { memo, useCallback, useState } from "react";
-import { Link, useLocation, useParams } from "react-router-dom";
-import { Bookmark, ChevronRight, ChevronDown, Home, Settings } from "lucide-react";
-import { useProjectLayout } from "@/app/project/layout";
-import { useProjectsQuery } from "@/hooks/useProjectsQuery";
-import { useOptionalRoom } from "@/contexts/room";
-import { RoomSettingsModal } from "@/app/project/channel/RoomSettingsModal";
-import { ProjectMessageFavoritesDrawer } from "@/components/layout/ProjectMessageFavoritesDrawer";
-import { isProjectAdminRole } from "@/lib/project-permissions";
-import { modKey, altKey } from "@/lib/utils";
-import { openGlobalSearch } from "@/components/search/global-search-events";
+import { memo, useCallback, useState } from "react"
+import { Link, useLocation, useParams } from "react-router-dom"
+import {
+ Bookmark,
+ ChevronRight,
+ ChevronDown,
+ Home,
+ Settings,
+} from "lucide-react"
+import { useProjectLayout } from "@/app/project/layout"
+import { useProjectsQuery } from "@/hooks/useProjectsQuery"
+import { useOptionalRoom } from "@/contexts/room"
+import { RoomSettingsModal } from "@/app/project/channel/RoomSettingsModal"
+import { ProjectMessageFavoritesDrawer } from "@/components/layout/ProjectMessageFavoritesDrawer"
+import { isProjectAdminRole } from "@/lib/project-permissions"
+import { modKey, altKey } from "@/lib/utils"
+import { openGlobalSearch } from "@/components/search/global-search-events"
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
-} from "@/components/ui/dropdown-menu";
+} from "@/components/ui/dropdown-menu"
+import { Button } from "@/components/ui/button"
-const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+const UUID_RE =
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
function truncateUuid(s: string): string {
- return s.slice(0, 8);
+ return s.slice(0, 8)
}
interface BreadcrumbSegment {
- label: string;
- path: string;
- isLast: boolean;
- fullUuid?: string;
+ label: string
+ path: string
+ isLast: boolean
+ fullUuid?: string
}
interface BreadcrumbSibling {
- label: string;
- path: string;
+ label: string
+ path: string
}
const ME_NAV_SIBLINGS: BreadcrumbSibling[] = [
@@ -44,20 +52,23 @@ const ME_NAV_SIBLINGS: BreadcrumbSibling[] = [
{ label: "Following", path: "/me/following" },
{ label: "Followers", path: "/me/followers" },
{ label: "Invitations", path: "/me/invitations" },
-];
+]
-function getProjectNavSiblings(projectName: string, showSettings: boolean): BreadcrumbSibling[] {
+function getProjectNavSiblings(
+ projectName: string,
+ showSettings: boolean
+): BreadcrumbSibling[] {
const items = [
{ label: "Repository", path: `/${projectName}/repos` },
{ label: "Issues", path: `/${projectName}/issues` },
{ label: "Skills", path: `/${projectName}/skills` },
{ label: "Board", path: `/${projectName}/board` },
{ label: "Chat", path: `/${projectName}/chat` },
- ];
+ ]
if (showSettings) {
- items.push({ label: "Settings", path: `/${projectName}/settings` });
+ items.push({ label: "Settings", path: `/${projectName}/settings` })
}
- return items;
+ return items
}
function getRepoTabSiblings(repoBasePath: string): BreadcrumbSibling[] {
@@ -68,92 +79,92 @@ function getRepoTabSiblings(repoBasePath: string): BreadcrumbSibling[] {
{ label: "Branches", path: `${repoBasePath}/branches` },
{ label: "Tags", path: `${repoBasePath}/tags` },
{ label: "Settings", path: `${repoBasePath}/settings` },
- ];
+ ]
}
function getSegmentSiblings(
segment: BreadcrumbSegment,
projects: Array<{ name: string; display_name: string }>,
- canManageProject = false,
+ canManageProject = false
): BreadcrumbSibling[] | null {
- if (segment.isLast) return null;
+ if (segment.isLast) return null
- const parts = segment.path.split("/").filter(Boolean);
- const depth = parts.length;
+ const parts = segment.path.split("/").filter(Boolean)
+ const depth = parts.length
if (depth === 1) {
- if (parts[0] === "me") return ME_NAV_SIBLINGS;
+ if (parts[0] === "me") return ME_NAV_SIBLINGS
if (projects.length > 1) {
return projects.map((p) => ({
label: p.display_name || p.name,
path: `/${p.name}`,
- }));
+ }))
}
- return null;
+ return null
}
if (depth === 2) {
- if (parts[0] === "me") return ME_NAV_SIBLINGS;
- return getProjectNavSiblings(parts[0], canManageProject);
+ if (parts[0] === "me") return ME_NAV_SIBLINGS
+ return getProjectNavSiblings(parts[0], canManageProject)
}
if (depth >= 3 && parts[1] === "repo") {
- const repoBase = `/${parts[0]}/repo/${parts[2]}`;
- return getRepoTabSiblings(repoBase);
+ const repoBase = `/${parts[0]}/repo/${parts[2]}`
+ return getRepoTabSiblings(repoBase)
}
- return null;
+ return null
}
function useBreadcrumbs() {
- const location = useLocation();
+ const location = useLocation()
const params = useParams<{
- projectName?: string;
- repoName?: string;
- issueNumber?: string;
- skillSlug?: string;
- roomId?: string;
- }>();
- const roomContext = useOptionalRoom();
- const currentRoom = roomContext?.currentRoom;
+ projectName?: string
+ repoName?: string
+ issueNumber?: string
+ skillSlug?: string
+ roomId?: string
+ }>()
+ const roomContext = useOptionalRoom()
+ const currentRoom = roomContext?.currentRoom
- const { data: projects = [] } = useProjectsQuery();
- const activeProject = projects.find((p) => p.name === params.projectName);
+ const { data: projects = [] } = useProjectsQuery()
+ const activeProject = projects.find((p) => p.name === params.projectName)
- const segments: BreadcrumbSegment[] = [];
- const pathParts = location.pathname.split("/").filter(Boolean);
+ const segments: BreadcrumbSegment[] = []
+ const pathParts = location.pathname.split("/").filter(Boolean)
for (let i = 0; i < pathParts.length; i++) {
- const part = pathParts[i];
- const path = "/" + pathParts.slice(0, i + 1).join("/");
- const isLast = i === pathParts.length - 1;
+ const part = pathParts[i]
+ const path = "/" + pathParts.slice(0, i + 1).join("/")
+ const isLast = i === pathParts.length - 1
- let label = part;
- let fullUuid: string | undefined;
+ let label = part
+ let fullUuid: string | undefined
if (part === params.roomId && currentRoom) {
- label = `#${currentRoom.room_name}`;
+ label = `#${currentRoom.room_name}`
} else if (UUID_RE.test(part)) {
- fullUuid = part;
- label = truncateUuid(part);
+ fullUuid = part
+ label = truncateUuid(part)
} else if (part === params.projectName && activeProject) {
- label = activeProject.display_name;
+ label = activeProject.display_name
} else if (part === params.repoName) {
- label = part;
+ label = part
} else if (part === params.issueNumber) {
- label = `#${part}`;
+ label = `#${part}`
} else if (part === params.skillSlug) {
- label = part;
+ label = part
} else if (part === "channel" && params.roomId && currentRoom) {
- label = currentRoom.room_name;
+ label = currentRoom.room_name
} else {
- label = part.charAt(0).toUpperCase() + part.slice(1);
+ label = part.charAt(0).toUpperCase() + part.slice(1)
}
- segments.push({ label, path, isLast, fullUuid });
+ segments.push({ label, path, isLast, fullUuid })
}
- return { segments, projects };
+ return { segments, projects }
}
const TOOLBAR_ICONS = [
@@ -173,189 +184,194 @@ const TOOLBAR_ICONS = [
label: "Help",
path: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
},
-];
+]
export const Header = memo(function Header() {
- const location = useLocation();
- const params = useParams<{ projectName?: string }>();
- const { segments, projects } = useBreadcrumbs();
- const { isProjectMember, projectInfo, showMembers, setShowMembers } = useProjectLayout();
- const [showSettings, setShowSettings] = useState(false);
- const [showFavorites, setShowFavorites] = useState(false);
- const roomContext = useOptionalRoom();
- const canManageProject = isProjectAdminRole(projectInfo?.role);
+ const location = useLocation()
+ const params = useParams<{ projectName?: string }>()
+ const { segments, projects } = useBreadcrumbs()
+ const { isProjectMember, projectInfo, showMembers, setShowMembers } =
+ useProjectLayout()
+ const [showSettings, setShowSettings] = useState(false)
+ const [showFavorites, setShowFavorites] = useState(false)
+ const roomContext = useOptionalRoom()
+ const canManageProject = isProjectAdminRole(projectInfo?.role)
const handleCopy = useCallback((e: React.MouseEvent, text: string) => {
- e.preventDefault();
- navigator.clipboard.writeText(text);
- }, []);
+ e.preventDefault()
+ navigator.clipboard.writeText(text)
+ }, [])
return (
<>
-
+
{repo.description}
)} -+ {description} +
+ )} +{description}
- )} -+ {message} +
+{message}
-{message}
+{message}
+{description}
++ {description} +
)}