feat(ui): update Header and ChannelSidebar components

Refine Header with improved layout, update ChannelSidebar
with channel navigation and ChatPage integration.
This commit is contained in:
ZhenYi 2026-05-14 23:15:40 +08:00
parent c015871024
commit 31e9bb68ac
3 changed files with 69 additions and 49 deletions

View File

@ -10,6 +10,7 @@ import { useProjectInfo } from "@/hooks/useProjectInfo";
import { useConversationQuery } from "@/hooks/useAiChatQuery"; import { useConversationQuery } from "@/hooks/useAiChatQuery";
import { CodePreviewPanel } from "@/components/chat/CodePreviewPanel"; import { CodePreviewPanel } from "@/components/chat/CodePreviewPanel";
import { CodePreviewProvider, type CodePreviewPayload } from "@/components/chat/CodePreviewContext"; import { CodePreviewProvider, type CodePreviewPayload } from "@/components/chat/CodePreviewContext";
import { ProjectJoinBanner, useProjectLayout } from "@/app/project/layout";
interface ChatPageProps { interface ChatPageProps {
scope: "personal" | "project"; scope: "personal" | "project";
@ -27,6 +28,7 @@ export function ChatPage({ scope }: ChatPageProps) {
const [userModel, setSelectedModel] = useState<SelectedModel | null>(null); const [userModel, setSelectedModel] = useState<SelectedModel | null>(null);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(true); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(true);
const [activeCode, setActiveCode] = useState<CodePreviewPayload | null>(null); const [activeCode, setActiveCode] = useState<CodePreviewPayload | null>(null);
const { isProjectPreview } = useProjectLayout();
const { data: conversation } = useConversationQuery(selectedConversationId || ""); const { data: conversation } = useConversationQuery(selectedConversationId || "");
@ -131,24 +133,36 @@ export function ChatPage({ scope }: ChatPageProps) {
{selectedConversationId ? ( {selectedConversationId ? (
<> <>
<ChatMessageList conversationId={selectedConversationId} setIsStreaming={setIsStreaming} /> <ChatMessageList conversationId={selectedConversationId} setIsStreaming={setIsStreaming} />
{scope === "project" && isProjectPreview ? (
<div className="shrink-0 px-4 pb-4">
<div className="mx-auto max-w-3xl">
<ProjectJoinBanner compact message="Join this project to start project chat." />
</div>
</div>
) : (
<ChatMessageInput <ChatMessageInput
conversationId={selectedConversationId} conversationId={selectedConversationId}
isStreaming={isStreaming} isStreaming={isStreaming}
setIsStreaming={setIsStreaming} setIsStreaming={setIsStreaming}
onSelectConversation={handleSelectConversation} onSelectConversation={handleSelectConversation}
/> />
)}
</> </>
) : ( ) : (
<div className="flex-1 flex flex-col items-center justify-center px-4 gap-4"> <div className="flex-1 flex flex-col items-center justify-center px-4 gap-4">
<div className="w-full max-w-3xl"> <div className="w-full max-w-3xl">
<ChatMessageList conversationId={null} setIsStreaming={setIsStreaming} /> <ChatMessageList conversationId={null} setIsStreaming={setIsStreaming} />
<div className="mt-4"> <div className="mt-4">
{scope === "project" && isProjectPreview ? (
<ProjectJoinBanner compact message="Join this project to start project chat." />
) : (
<ChatMessageInput <ChatMessageInput
conversationId={null} conversationId={null}
isStreaming={isStreaming} isStreaming={isStreaming}
setIsStreaming={setIsStreaming} setIsStreaming={setIsStreaming}
onSelectConversation={handleSelectConversation} onSelectConversation={handleSelectConversation}
/> />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -41,8 +41,9 @@ interface ChannelSidebarProps {
export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: ChannelSidebarProps) { export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: ChannelSidebarProps) {
const location = useLocation(); const location = useLocation();
const {projectName} = useParams<{ projectName: string }>(); const {projectName} = useParams<{ projectName: string }>();
const {data: roomsData, isLoading} = useRoomsQuery(projectName);
const {data: projectInfo} = useProjectInfo(projectName); const {data: projectInfo} = useProjectInfo(projectName);
const isProjectMember = !!projectInfo?.role;
const {data: roomsData, isLoading} = useRoomsQuery(isProjectMember ? projectName : undefined);
const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false); const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
const rooms = useMemo(() => roomsData?.rooms ?? [], [roomsData?.rooms]); const rooms = useMemo(() => roomsData?.rooms ?? [], [roomsData?.rooms]);
@ -103,6 +104,7 @@ export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: Channel
> >
<Search className="w-[14px] h-[14px]"/> <Search className="w-[14px] h-[14px]"/>
</button> </button>
{isProjectMember && (
<button <button
onClick={() => setIsCreateMenuOpen(true)} onClick={() => setIsCreateMenuOpen(true)}
className={CHANNEL_SIDEBAR.iconButton} className={CHANNEL_SIDEBAR.iconButton}
@ -111,6 +113,7 @@ export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: Channel
> >
<Plus className="w-[14px] h-[14px]"/> <Plus className="w-[14px] h-[14px]"/>
</button> </button>
)}
{onCollapse && ( {onCollapse && (
<button <button
onClick={onCollapse} onClick={onCollapse}
@ -178,7 +181,7 @@ export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: Channel
</Link> </Link>
)} )}
{isLoading ? ( {!isProjectMember ? null : isLoading ? (
<div className="px-4 py-2 text-[var(--text-muted)]">Loading channels...</div> <div className="px-4 py-2 text-[var(--text-muted)]">Loading channels...</div>
) : ( ) : (
<> <>

View File

@ -39,6 +39,7 @@ const ME_NAV_SIBLINGS: BreadcrumbSibling[] = [
{ label: "Stars", path: "/me/stars" }, { label: "Stars", path: "/me/stars" },
{ label: "Following", path: "/me/following" }, { label: "Following", path: "/me/following" },
{ label: "Followers", path: "/me/followers" }, { label: "Followers", path: "/me/followers" },
{ label: "Invitations", path: "/me/invitations" },
]; ];
function getProjectNavSiblings(projectName: string): BreadcrumbSibling[] { function getProjectNavSiblings(projectName: string): BreadcrumbSibling[] {
@ -167,7 +168,7 @@ const TOOLBAR_ICONS = [
export const Header = memo(function Header() { export const Header = memo(function Header() {
const location = useLocation(); const location = useLocation();
const { segments, projects } = useBreadcrumbs(); const { segments, projects } = useBreadcrumbs();
const { showMembers, setShowMembers } = useProjectLayout(); const { isProjectMember, showMembers, setShowMembers } = useProjectLayout();
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const roomContext = useOptionalRoom(); const roomContext = useOptionalRoom();
@ -254,7 +255,7 @@ export const Header = memo(function Header() {
<div className="flex items-center gap-1 shrink-0"> <div className="flex items-center gap-1 shrink-0">
{location.pathname.startsWith("/me") ? null : ( {location.pathname.startsWith("/me") ? null : (
<> <>
{roomContext?.currentRoom && location.pathname.includes("/channel/") && ( {isProjectMember && roomContext?.currentRoom && location.pathname.includes("/channel/") && (
<button <button
onClick={() => setShowSettings(true)} onClick={() => setShowSettings(true)}
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors hover:bg-hover-bg" className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors hover:bg-hover-bg"
@ -264,6 +265,7 @@ export const Header = memo(function Header() {
<Settings className="w-[18px] h-[18px]" /> <Settings className="w-[18px] h-[18px]" />
</button> </button>
)} )}
{isProjectMember && (
<button <button
onClick={() => setShowMembers(!showMembers)} onClick={() => setShowMembers(!showMembers)}
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors" className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors"
@ -290,6 +292,7 @@ export const Header = memo(function Header() {
/> />
</svg> </svg>
</button> </button>
)}
{TOOLBAR_ICONS.map((icon, i) => ( {TOOLBAR_ICONS.map((icon, i) => (
<button <button