fix(components): update shell components and settings modal null check
This commit is contained in:
parent
a96222cc28
commit
0f7b05f7ef
@ -315,9 +315,9 @@ export function CommandPalette() {
|
|||||||
<span
|
<span
|
||||||
className={`grid size-8 shrink-0 place-items-center rounded-lg ${
|
className={`grid size-8 shrink-0 place-items-center rounded-lg ${
|
||||||
item.type === "issue"
|
item.type === "issue"
|
||||||
? "bg-amber-500/10 text-amber-600 dark:text-amber-400"
|
? "bg-warning/10 text-warning dark:text-warning"
|
||||||
: item.type === "repo"
|
: item.type === "repo"
|
||||||
? "bg-blue-500/10 text-blue-600 dark:text-blue-400"
|
? "bg-info/10 text-info dark:text-info"
|
||||||
: item.type === "room"
|
: item.type === "room"
|
||||||
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||||
: "bg-primary/10 text-primary"
|
: "bg-primary/10 text-primary"
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ export default function RightDrawer({
|
|||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
return (
|
return createPortal(
|
||||||
<div className="fixed inset-y-0 right-0 z-50 flex w-full justify-end">
|
<div className="fixed inset-y-0 right-0 z-50 flex w-full justify-end">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
@ -80,8 +81,11 @@ export default function RightDrawer({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
{children}
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,9 +110,10 @@ export function SettingsModal({ open, onClose, initialSection }: SettingsModalPr
|
|||||||
localStorage.setItem(SETTINGS_RETURN_PATH_KEY, location.pathname);
|
localStorage.setItem(SETTINGS_RETURN_PATH_KEY, location.pathname);
|
||||||
|
|
||||||
const el = document.querySelector('[data-slot="dialog-content"]') as HTMLDivElement | null;
|
const el = document.querySelector('[data-slot="dialog-content"]') as HTMLDivElement | null;
|
||||||
const overlayEl = document.querySelector('[data-slot="dialog-overlay"]') as HTMLDivElement | null;
|
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
|
const overlayEl = document.querySelector('[data-slot="dialog-overlay"]') as HTMLDivElement | null;
|
||||||
|
|
||||||
setIsExpanding(true);
|
setIsExpanding(true);
|
||||||
|
|
||||||
const duration = 350;
|
const duration = 350;
|
||||||
@ -135,6 +136,7 @@ export function SettingsModal({ open, onClose, initialSection }: SettingsModalPr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetEl = el;
|
||||||
function frame(now: number) {
|
function frame(now: number) {
|
||||||
const elapsed = now - start;
|
const elapsed = now - start;
|
||||||
const progress = Math.min(elapsed / duration, 1);
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
@ -144,11 +146,11 @@ export function SettingsModal({ open, onClose, initialSection }: SettingsModalPr
|
|||||||
const h = startH + (endH - startH) * eased;
|
const h = startH + (endH - startH) * eased;
|
||||||
const r = startR + (endR - startR) * eased;
|
const r = startR + (endR - startR) * eased;
|
||||||
|
|
||||||
el.style.setProperty("width", `${w}vw`, "important");
|
targetEl.style.setProperty("width", `${w}vw`, "important");
|
||||||
el.style.setProperty("height", `${h}vh`, "important");
|
targetEl.style.setProperty("height", `${h}vh`, "important");
|
||||||
el.style.setProperty("max-width", `${w}vw`, "important");
|
targetEl.style.setProperty("max-width", `${w}vw`, "important");
|
||||||
el.style.setProperty("max-height", `${h}vh`, "important");
|
targetEl.style.setProperty("max-height", `${h}vh`, "important");
|
||||||
el.style.setProperty("border-radius", `${r}px`, "important");
|
targetEl.style.setProperty("border-radius", `${r}px`, "important");
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
requestAnimationFrame(frame);
|
requestAnimationFrame(frame);
|
||||||
|
|||||||
@ -223,9 +223,9 @@ function AgentChatSidebar() {
|
|||||||
aria-current={active ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
aria-label={conv.title || "Untitled chat"}
|
aria-label={conv.title || "Untitled chat"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex w-full cursor-pointer items-center gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
"group relative flex w-full cursor-pointer items-center gap-2.5 rounded-lg px-2.5 py-2 text-left transition-[background-color,color,box-shadow] duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
active
|
active
|
||||||
? "bg-primary/[0.08] text-foreground shadow-[inset_0_0_0_1px_var(--color-primary)/15]"
|
? "bg-primary/[0.08] text-foreground ring-1 ring-primary/15"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground hover:shadow-sm",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground hover:shadow-sm",
|
||||||
)}
|
)}
|
||||||
onClick={() => navigate(`/me/chat/${conv.id}`)}
|
onClick={() => navigate(`/me/chat/${conv.id}`)}
|
||||||
@ -262,7 +262,7 @@ function AgentChatSidebar() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="shrink-0 cursor-pointer rounded-md p-1 text-muted-foreground/30 opacity-0 transition-all duration-200 hover:bg-destructive/10 hover:text-destructive focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"
|
className="shrink-0 cursor-pointer rounded-md p-1 text-muted-foreground/30 opacity-0 transition-[background-color,color,opacity] duration-200 hover:bg-destructive/10 hover:text-destructive focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDelete(conv.id);
|
handleDelete(conv.id);
|
||||||
@ -300,7 +300,7 @@ function PersonalSidebar() {
|
|||||||
<aside className="flex h-full w-[240px] shrink-0 flex-col overflow-hidden border-r border-border bg-sidebar">
|
<aside className="flex h-full w-[240px] shrink-0 flex-col overflow-hidden border-r border-border bg-sidebar">
|
||||||
<header className="flex h-[48px] shrink-0 items-center gap-3 border-b border-border px-4">
|
<header className="flex h-[48px] shrink-0 items-center gap-3 border-b border-border px-4">
|
||||||
<PersonalAvatar />
|
<PersonalAvatar />
|
||||||
<button className="flex min-w-0 flex-1 items-center gap-2 text-left text-base font-semibold text-foreground">
|
<button aria-label="Personal menu" className="flex min-w-0 flex-1 items-center gap-2 rounded-lg text-left text-base font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30" type="button">
|
||||||
<span className="truncate">{me?.display_name || me?.username || "User"}</span>
|
<span className="truncate">{me?.display_name || me?.username || "User"}</span>
|
||||||
<ChevronDown className="size-4 shrink-0 text-muted-foreground" />
|
<ChevronDown className="size-4 shrink-0 text-muted-foreground" />
|
||||||
</button>
|
</button>
|
||||||
@ -321,9 +321,9 @@ function PersonalSidebar() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<section className="mb-5">
|
<section className="mb-5">
|
||||||
<button className="mb-2 flex w-full items-center justify-between px-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
<div className="mb-2 flex w-full items-center justify-between px-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||||
Personal
|
Personal
|
||||||
</button>
|
</div>
|
||||||
<nav className="space-y-1">
|
<nav className="space-y-1">
|
||||||
{personalItems.map((item) => (
|
{personalItems.map((item) => (
|
||||||
<PersonalNavLink
|
<PersonalNavLink
|
||||||
@ -336,9 +336,9 @@ function PersonalSidebar() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="mb-5">
|
<section className="mb-5">
|
||||||
<button className="mb-2 flex w-full items-center justify-between px-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
<div className="mb-2 flex w-full items-center justify-between px-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||||
Social
|
Social
|
||||||
</button>
|
</div>
|
||||||
<nav className="space-y-1">
|
<nav className="space-y-1">
|
||||||
{socialItems.map((item) => (
|
{socialItems.map((item) => (
|
||||||
<PersonalNavLink
|
<PersonalNavLink
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { Link, useLocation } from "react-router";
|
import { Link, useLocation } from "react-router";
|
||||||
import { Home, Plus, Settings } from "lucide-react";
|
import { Home, LogOut, Plus, Settings } from "lucide-react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { client, type WorkspaceResponse } from "@/client";
|
import { client, type WorkspaceResponse } from "@/client";
|
||||||
import { CreateWorkspaceDialog } from "@/page/workspace/create";
|
import { CreateWorkspaceDialog } from "@/page/workspace/create";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { workspaceColor, workspaceInitial } from "./shared";
|
import { workspaceColor, workspaceInitial } from "./shared";
|
||||||
import { useSettingsModal } from "@/components/settings/SettingsModalContext";
|
import { useSettingsModal } from "@/components/settings/SettingsModalContext";
|
||||||
|
import { useAuth } from "@/context/auth-context";
|
||||||
|
|
||||||
function WorkspaceIcon({
|
function WorkspaceIcon({
|
||||||
active,
|
active,
|
||||||
@ -25,13 +34,13 @@ function WorkspaceIcon({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute -left-[11px] h-2 w-[3px] rounded-r-full bg-foreground opacity-0 transition-all group-hover:h-4 group-hover:opacity-60",
|
"absolute -left-[11px] h-2 w-[3px] rounded-r-full bg-foreground opacity-0 transition-[height,opacity] group-hover:h-4 group-hover:opacity-60",
|
||||||
active && "h-6 opacity-100",
|
active && "h-6 opacity-100",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid size-9 place-items-center overflow-hidden rounded-xl bg-gradient-to-br text-[12px] font-bold text-white ring-1 ring-black/[0.06] transition-all",
|
"grid size-9 place-items-center overflow-hidden rounded-xl bg-gradient-to-br text-[12px] font-bold text-white ring-1 ring-black/[0.06] transition-[border-radius,box-shadow]",
|
||||||
workspaceColor(workspace.name),
|
workspaceColor(workspace.name),
|
||||||
active && "rounded-lg ring-2 ring-primary/20",
|
active && "rounded-lg ring-2 ring-primary/20",
|
||||||
)}
|
)}
|
||||||
@ -50,17 +59,127 @@ function WorkspaceIcon({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsRailButton() {
|
function UserAvatar({ name, avatarUrl }: { name: string; avatarUrl?: string | null }) {
|
||||||
const { openSettingsModal } = useSettingsModal();
|
const initial = name.charAt(0).toUpperCase();
|
||||||
|
|
||||||
|
if (avatarUrl) {
|
||||||
|
return <img alt={name} className="size-full object-cover" src={avatarUrl} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<span className="flex size-full items-center justify-center bg-gradient-to-br from-purple-500 via-pink-500 to-orange-400 text-[12px] font-bold text-white">
|
||||||
aria-label="Settings"
|
{initial}
|
||||||
className="inline-flex size-9 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-all hover:bg-accent hover:text-foreground cursor-pointer"
|
</span>
|
||||||
title="Settings"
|
);
|
||||||
onClick={() => openSettingsModal()}
|
}
|
||||||
>
|
|
||||||
<Settings className="size-[18px]" />
|
function SettingsRailButton() {
|
||||||
</button>
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { openSettingsModal } = useSettingsModal();
|
||||||
|
const { me } = useAuth();
|
||||||
|
|
||||||
|
const userName = me?.display_name || me?.username || "User";
|
||||||
|
const userHandle = me?.username ? `@${me.username}` : "Personal account";
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
void client.authLogout().finally(() => {
|
||||||
|
window.location.href = "/";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu onOpenChange={setIsOpen}>
|
||||||
|
<div className="group relative">
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
render={
|
||||||
|
<button
|
||||||
|
aria-label="Account menu"
|
||||||
|
className="inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-xl border border-border bg-card p-1 transition-[background-color,border-color,box-shadow] duration-200 hover:bg-accent hover:shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
|
||||||
|
title="Account menu"
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="size-7 overflow-hidden rounded-full ring-1 ring-black/[0.06] dark:ring-white/[0.06]">
|
||||||
|
<UserAvatar avatarUrl={me?.avatar_url} name={userName} />
|
||||||
|
</span>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute top-1/2 -right-2.5 -translate-y-1/2 transition-opacity duration-200",
|
||||||
|
isOpen ? "opacity-100" : "opacity-0 group-hover:opacity-60",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"transition-[color,transform] duration-200",
|
||||||
|
isOpen
|
||||||
|
? "scale-110 text-primary"
|
||||||
|
: "text-muted-foreground/50 group-hover:text-muted-foreground",
|
||||||
|
)}
|
||||||
|
fill="none"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 12 24"
|
||||||
|
width="10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2 4C6 8 6 16 2 20"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
className="w-64 origin-bottom-left rounded-2xl border border-border bg-popover/95 p-2 text-popover-foreground shadow-xl shadow-foreground/5 backdrop-blur-sm"
|
||||||
|
side="right"
|
||||||
|
sideOffset={12}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 rounded-xl p-3">
|
||||||
|
<div className="size-10 shrink-0 overflow-hidden rounded-full ring-1 ring-black/[0.06] dark:ring-white/[0.06]">
|
||||||
|
<UserAvatar avatarUrl={me?.avatar_url} name={userName} />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="truncate text-sm font-medium leading-tight tracking-tight text-foreground">
|
||||||
|
{userName}
|
||||||
|
</div>
|
||||||
|
<div className="truncate text-xs leading-tight tracking-tight text-muted-foreground">
|
||||||
|
{userHandle}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator className="my-2 bg-gradient-to-r from-transparent via-zinc-200 to-transparent dark:via-zinc-800" />
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="group flex cursor-pointer items-center gap-3 rounded-xl border border-transparent p-3 transition-[background-color,border-color,box-shadow] duration-200 hover:border-border hover:bg-accent hover:shadow-sm"
|
||||||
|
onClick={() => window.setTimeout(() => openSettingsModal(), 0)}
|
||||||
|
>
|
||||||
|
<Settings className="size-4 text-muted-foreground transition-colors group-hover:text-foreground" />
|
||||||
|
<span className="text-sm font-medium leading-tight tracking-tight text-foreground transition-colors group-hover:text-foreground">
|
||||||
|
Settings
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator className="my-2 bg-gradient-to-r from-transparent via-zinc-200 to-transparent dark:via-zinc-800" />
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="group flex cursor-pointer items-center gap-3 rounded-xl border border-transparent bg-destructive/10 p-3 transition-[background-color,border-color,box-shadow] duration-200 hover:border-destructive/30 hover:bg-destructive/15 hover:shadow-sm"
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<LogOut className="size-4 text-destructive transition-colors" />
|
||||||
|
<span className="text-sm font-medium leading-tight text-destructive">
|
||||||
|
Logout
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +195,7 @@ function RailButton({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className="inline-flex size-9 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-all hover:bg-accent hover:text-foreground"
|
className="inline-flex size-9 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-[background-color,color,border-color] hover:bg-accent hover:text-foreground"
|
||||||
title={label}
|
title={label}
|
||||||
to={to}
|
to={to}
|
||||||
>
|
>
|
||||||
@ -136,7 +255,7 @@ export function WorkspaceRail() {
|
|||||||
<CreateWorkspaceDialog>
|
<CreateWorkspaceDialog>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Create workspace"
|
aria-label="Create workspace"
|
||||||
className="size-9 rounded-xl border-border bg-card text-muted-foreground transition-all hover:bg-accent hover:text-foreground"
|
className="size-9 rounded-xl border-border bg-card text-muted-foreground transition-[background-color,color,border-color] hover:bg-accent hover:text-foreground"
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useMemo, useState, createContext, useContext } from "react";
|
import { useMemo, useState, createContext, useContext } from "react";
|
||||||
import { Link, Outlet, useLocation, useParams } from "react-router";
|
import { Link, Outlet, useLocation, useParams, useSearchParams } from "react-router";
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@ -101,9 +101,9 @@ function ChannelLink({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex h-[32px] items-center gap-2 rounded-lg px-2.5 text-[13px] transition-all duration-150",
|
"group flex h-[36px] items-center gap-2 rounded-lg px-2.5 text-[13px] transition-[background-color,color,box-shadow] duration-150",
|
||||||
active
|
active
|
||||||
? "bg-primary/[0.08] font-semibold text-foreground shadow-[inset_0_0_0_1px_rgba(var(--color-primary),0.06)]"
|
? "bg-primary/[0.08] font-semibold text-foreground ring-1 ring-primary/10"
|
||||||
: "text-muted-foreground/70 hover:bg-accent/50 hover:text-foreground",
|
: "text-muted-foreground/70 hover:bg-accent/50 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
to={to}
|
to={to}
|
||||||
@ -256,14 +256,15 @@ function WorkspaceSidebar() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="flex h-[48px] shrink-0 items-center gap-2.5 border-b border-border px-4">
|
<header className="flex h-[48px] shrink-0 items-center gap-2.5 border-b border-border px-4">
|
||||||
<WorkspaceAvatar workspace={currentWorkspace} />
|
<WorkspaceAvatar workspace={currentWorkspace} />
|
||||||
<button className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
|
<button aria-label="Workspace menu" className="flex min-w-0 flex-1 items-center gap-1.5 rounded-lg text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30" type="button">
|
||||||
<span className="truncate text-sm font-semibold text-foreground">
|
<span className="truncate text-sm font-semibold text-foreground">
|
||||||
{currentWorkspace?.name ?? projectName}
|
{currentWorkspace?.name ?? projectName}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground/40" />
|
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground/40" />
|
||||||
</button>
|
</button>
|
||||||
<Button
|
<Button
|
||||||
className="size-7 text-muted-foreground/30 hover:bg-accent/50 hover:text-muted-foreground"
|
aria-label="Search workspace"
|
||||||
|
className="size-8 text-muted-foreground/30 hover:bg-accent/50 hover:text-muted-foreground"
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
@ -315,7 +316,7 @@ function WorkspaceSidebar() {
|
|||||||
onCreated={refreshChannels}
|
onCreated={refreshChannels}
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
>
|
>
|
||||||
<div className="mt-1.5 flex h-[32px] w-full cursor-pointer items-center gap-2 rounded-md px-2 text-[13px] text-muted-foreground/60 transition-all duration-150 hover:bg-accent/50 hover:text-muted-foreground">
|
<div className="mt-1.5 flex h-[36px] w-full cursor-pointer items-center gap-2 rounded-lg px-2 text-[13px] text-muted-foreground/60 transition-[background-color,color] duration-150 hover:bg-accent/50 hover:text-muted-foreground">
|
||||||
<Plus className="size-[15px] shrink-0" />
|
<Plus className="size-[15px] shrink-0" />
|
||||||
<span>Add channel</span>
|
<span>Add channel</span>
|
||||||
</div>
|
</div>
|
||||||
@ -324,10 +325,10 @@ function WorkspaceSidebar() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<section className="mb-5">
|
<section className="mb-5">
|
||||||
<button className="mb-2 flex w-full items-center justify-between px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/35">
|
<div className="mb-2 flex w-full items-center justify-between px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/35">
|
||||||
Workplan
|
Workplan
|
||||||
<ChevronDown className="size-3.5" />
|
<ChevronDown className="size-3.5" />
|
||||||
</button>
|
</div>
|
||||||
<nav className="space-y-[1px]">
|
<nav className="space-y-[1px]">
|
||||||
<ShellNavLink
|
<ShellNavLink
|
||||||
item={{
|
item={{
|
||||||
@ -446,28 +447,34 @@ function TopBar() {
|
|||||||
<div className="flex items-center gap-1 text-muted-foreground/30">
|
<div className="flex items-center gap-1 text-muted-foreground/30">
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<button
|
<button
|
||||||
|
aria-label="Open search panel"
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid size-8 place-items-center rounded-xl transition-all duration-150 hover:bg-accent/50 hover:text-muted-foreground/70",
|
"grid size-10 place-items-center rounded-xl transition-[background-color,color] duration-150 hover:bg-accent/50 hover:text-muted-foreground/70",
|
||||||
panel.showSearch && "bg-accent/50 text-foreground",
|
panel.showSearch && "bg-accent/50 text-foreground",
|
||||||
)}
|
)}
|
||||||
onClick={panel.toggleSearch}
|
onClick={panel.toggleSearch}
|
||||||
title="Search"
|
title="Search"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Search className="size-[14px]" />
|
<Search className="size-[14px]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
aria-label="Open members panel"
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid size-8 place-items-center rounded-xl transition-all duration-150 hover:bg-accent/50 hover:text-muted-foreground/70",
|
"grid size-10 place-items-center rounded-xl transition-[background-color,color] duration-150 hover:bg-accent/50 hover:text-muted-foreground/70",
|
||||||
panel.showMembers && "bg-accent/50 text-foreground",
|
panel.showMembers && "bg-accent/50 text-foreground",
|
||||||
)}
|
)}
|
||||||
onClick={panel.toggleMembers}
|
onClick={panel.toggleMembers}
|
||||||
title="Members"
|
title="Members"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Users className="size-[14px]" />
|
<Users className="size-[14px]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="grid size-8 place-items-center rounded-xl transition-all duration-150 hover:bg-accent/50 hover:text-muted-foreground/70"
|
aria-label="Open notifications"
|
||||||
|
className="grid size-10 place-items-center rounded-xl transition-[background-color,color] duration-150 hover:bg-accent/50 hover:text-muted-foreground/70"
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Bell className="size-[14px]" />
|
<Bell className="size-[14px]" />
|
||||||
</button>
|
</button>
|
||||||
@ -499,7 +506,8 @@ function MembersPanel() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="size-7 text-muted-foreground/40 hover:bg-accent/50 hover:text-foreground"
|
aria-label="Close members panel"
|
||||||
|
className="size-8 text-muted-foreground/40 hover:bg-accent/50 hover:text-foreground"
|
||||||
onClick={panel.closeMembers}
|
onClick={panel.closeMembers}
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -548,6 +556,8 @@ function MembersPanel() {
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
export function WorkspaceShell() {
|
export function WorkspaceShell() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isEmbed = searchParams.get("embed") === "1";
|
||||||
const [showMembers, setShowMembers] = useState(false);
|
const [showMembers, setShowMembers] = useState(false);
|
||||||
const [showSearch, setShowSearch] = useState(false);
|
const [showSearch, setShowSearch] = useState(false);
|
||||||
|
|
||||||
@ -597,6 +607,15 @@ export function WorkspaceShell() {
|
|||||||
[showMembers, showSearch, members, membersLoading],
|
[showMembers, showSearch, members, membersLoading],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Embed mode: render only the page content, no shell chrome
|
||||||
|
if (isEmbed) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full flex-col overflow-hidden bg-background">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavShell>
|
<NavShell>
|
||||||
<TooltipProvider delay={300}>
|
<TooltipProvider delay={300}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user