Fix sidebar session list responsiveness
This commit is contained in:
@@ -14,7 +14,11 @@ import {
|
|||||||
} from "@/components/assistant-ui/activity-panel";
|
} from "@/components/assistant-ui/activity-panel";
|
||||||
import { Thread } from "@/components/assistant-ui/thread";
|
import { Thread } from "@/components/assistant-ui/thread";
|
||||||
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
|
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
|
||||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
SidebarTrigger,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
import { ClawSessionReplayProvider } from "@/lib/claw-session-replay";
|
import { ClawSessionReplayProvider } from "@/lib/claw-session-replay";
|
||||||
import { ClawAccountGate, useClawAccount } from "./claw-account-gate";
|
import { ClawAccountGate, useClawAccount } from "./claw-account-gate";
|
||||||
|
|
||||||
@@ -85,6 +89,7 @@ export const Assistant = () => {
|
|||||||
<ActivityProvider>
|
<ActivityProvider>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<div className="flex h-dvh w-full">
|
<div className="flex h-dvh w-full">
|
||||||
|
<SidebarTrigger className="fixed top-3 left-3 z-40 size-9 rounded-lg bg-background/90 shadow-sm ring-1 ring-border backdrop-blur sm:hidden" />
|
||||||
<ThreadListSidebar
|
<ThreadListSidebar
|
||||||
account={account}
|
account={account}
|
||||||
onLogout={() => setAccount(null)}
|
onLogout={() => setAccount(null)}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ type ClawStoredToolCall = {
|
|||||||
|
|
||||||
export const ThreadList: FC = () => {
|
export const ThreadList: FC = () => {
|
||||||
return (
|
return (
|
||||||
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col gap-1">
|
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex min-h-0 flex-1 flex-col gap-1">
|
||||||
<ThreadListNew />
|
<ThreadListNew />
|
||||||
<ClawSessionList />
|
<ClawSessionList />
|
||||||
</ThreadListPrimitive.Root>
|
</ThreadListPrimitive.Root>
|
||||||
@@ -146,8 +146,7 @@ const ClawSessionList: FC = () => {
|
|||||||
fetch("/api/claw/sessions")
|
fetch("/api/claw/sessions")
|
||||||
.then((res) => (res.ok ? res.json() : []))
|
.then((res) => (res.ok ? res.json() : []))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
if (Array.isArray(payload))
|
if (Array.isArray(payload)) setSessions(dedupeSessions(payload));
|
||||||
setSessions(dedupeSessions(payload).slice(0, 8));
|
|
||||||
})
|
})
|
||||||
.catch(() => setSessions([]));
|
.catch(() => setSessions([]));
|
||||||
};
|
};
|
||||||
@@ -204,154 +203,158 @@ const ClawSessionList: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
|
||||||
{sessions.map((session) => {
|
<div className="flex flex-col gap-1">
|
||||||
const active = activeSessionId === session.session_id;
|
{sessions.map((session) => {
|
||||||
const loading = loadingSessionId === session.session_id;
|
const active = activeSessionId === session.session_id;
|
||||||
return (
|
const loading = loadingSessionId === session.session_id;
|
||||||
<div
|
return (
|
||||||
key={`claw-session-${session.session_id}-${session.modified_at}`}
|
<div
|
||||||
className="aui-thread-list-item group relative flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted data-[active=true]:bg-muted"
|
key={`claw-session-${session.session_id}-${session.modified_at}`}
|
||||||
data-active={active}
|
className="aui-thread-list-item group relative flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted data-[active=true]:bg-muted"
|
||||||
>
|
data-active={active}
|
||||||
{renamingSessionId === session.session_id ? (
|
|
||||||
<div className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center px-3">
|
|
||||||
<input
|
|
||||||
ref={renameInputRef}
|
|
||||||
type="text"
|
|
||||||
className="h-7 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
||||||
value={draftTitle}
|
|
||||||
onChange={(event) => setDraftTitle(event.target.value)}
|
|
||||||
onBlur={() => {
|
|
||||||
saveTitle(
|
|
||||||
session.session_id,
|
|
||||||
session.preview || session.session_id,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
event.preventDefault();
|
|
||||||
event.currentTarget.blur();
|
|
||||||
}
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
event.preventDefault();
|
|
||||||
setRenamingSessionId(null);
|
|
||||||
setDraftTitle("");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
|
|
||||||
onClick={async () => {
|
|
||||||
setOpenMenuSessionId(null);
|
|
||||||
window.localStorage.setItem(
|
|
||||||
"claw.activeSessionId",
|
|
||||||
session.session_id,
|
|
||||||
);
|
|
||||||
setActiveSessionId(session.session_id);
|
|
||||||
setLoadingSessionId(session.session_id);
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/claw/sessions/${session.session_id}`,
|
|
||||||
);
|
|
||||||
if (!response.ok) return;
|
|
||||||
const payload =
|
|
||||||
(await response.json()) as ClawStoredSession;
|
|
||||||
const runStatus = await fetchLatestRunStatus(
|
|
||||||
session.session_id,
|
|
||||||
);
|
|
||||||
replaySession(
|
|
||||||
session.session_id,
|
|
||||||
toReplayRepository(
|
|
||||||
payload,
|
|
||||||
session.session_id,
|
|
||||||
runStatus,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setLoadingSessionId(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="truncate">
|
|
||||||
{loading
|
|
||||||
? "回放中..."
|
|
||||||
: session.preview || session.session_id}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="aui-thread-list-item-more mr-2 flex size-7 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100 data-[busy=true]:opacity-100 data-[open=true]:bg-accent data-[open=true]:opacity-100"
|
|
||||||
data-busy={deletingSessionId === session.session_id}
|
|
||||||
data-open={openMenuSessionId === session.session_id}
|
|
||||||
aria-expanded={openMenuSessionId === session.session_id}
|
|
||||||
aria-label="打开历史会话菜单"
|
|
||||||
title="更多选项"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenMenuSessionId((current) =>
|
|
||||||
current === session.session_id ? null : session.session_id,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon className="size-4" />
|
{renamingSessionId === session.session_id ? (
|
||||||
</button>
|
<div className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center px-3">
|
||||||
{openMenuSessionId === session.session_id ? (
|
<input
|
||||||
<div className="aui-thread-list-item-more-content absolute top-8 right-1 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
|
ref={renameInputRef}
|
||||||
|
type="text"
|
||||||
|
className="h-7 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||||
|
value={draftTitle}
|
||||||
|
onChange={(event) => setDraftTitle(event.target.value)}
|
||||||
|
onBlur={() => {
|
||||||
|
saveTitle(
|
||||||
|
session.session_id,
|
||||||
|
session.preview || session.session_id,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.currentTarget.blur();
|
||||||
|
}
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
setRenamingSessionId(null);
|
||||||
|
setDraftTitle("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus:bg-accent"
|
className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
|
||||||
disabled={renamingSessionId === session.session_id}
|
|
||||||
onClick={() => {
|
|
||||||
setRenamingSessionId(session.session_id);
|
|
||||||
setDraftTitle(session.preview || session.session_id);
|
|
||||||
setOpenMenuSessionId(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PencilIcon className="size-4" />
|
|
||||||
重命名
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none hover:bg-accent focus:bg-accent"
|
|
||||||
disabled={deletingSessionId === session.session_id}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setDeletingSessionId(session.session_id);
|
setOpenMenuSessionId(null);
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"claw.activeSessionId",
|
||||||
|
session.session_id,
|
||||||
|
);
|
||||||
|
setActiveSessionId(session.session_id);
|
||||||
|
setLoadingSessionId(session.session_id);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/claw/sessions/${session.session_id}`,
|
`/api/claw/sessions/${session.session_id}`,
|
||||||
{ method: "DELETE" },
|
|
||||||
);
|
);
|
||||||
if (!response.ok) return;
|
if (!response.ok) return;
|
||||||
setSessions((current) =>
|
const payload =
|
||||||
current.filter(
|
(await response.json()) as ClawStoredSession;
|
||||||
(item) => item.session_id !== session.session_id,
|
const runStatus = await fetchLatestRunStatus(
|
||||||
|
session.session_id,
|
||||||
|
);
|
||||||
|
replaySession(
|
||||||
|
session.session_id,
|
||||||
|
toReplayRepository(
|
||||||
|
payload,
|
||||||
|
session.session_id,
|
||||||
|
runStatus,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setOpenMenuSessionId(null);
|
|
||||||
if (activeSessionId === session.session_id) {
|
|
||||||
window.localStorage.removeItem("claw.activeSessionId");
|
|
||||||
setActiveSessionId(null);
|
|
||||||
window.dispatchEvent(
|
|
||||||
new Event("claw-active-session-cleared"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setDeletingSessionId(null);
|
setLoadingSessionId(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2Icon className="size-4" />
|
<span className="truncate">
|
||||||
删除
|
{loading
|
||||||
|
? "回放中..."
|
||||||
|
: session.preview || session.session_id}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
) : null}
|
<button
|
||||||
</div>
|
type="button"
|
||||||
);
|
className="aui-thread-list-item-more mr-2 flex size-7 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100 data-[busy=true]:opacity-100 data-[open=true]:bg-accent data-[open=true]:opacity-100"
|
||||||
})}
|
data-busy={deletingSessionId === session.session_id}
|
||||||
|
data-open={openMenuSessionId === session.session_id}
|
||||||
|
aria-expanded={openMenuSessionId === session.session_id}
|
||||||
|
aria-label="打开历史会话菜单"
|
||||||
|
title="更多选项"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenMenuSessionId((current) =>
|
||||||
|
current === session.session_id ? null : session.session_id,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MoreHorizontalIcon className="size-4" />
|
||||||
|
</button>
|
||||||
|
{openMenuSessionId === session.session_id ? (
|
||||||
|
<div className="aui-thread-list-item-more-content absolute top-8 right-1 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus:bg-accent"
|
||||||
|
disabled={renamingSessionId === session.session_id}
|
||||||
|
onClick={() => {
|
||||||
|
setRenamingSessionId(session.session_id);
|
||||||
|
setDraftTitle(session.preview || session.session_id);
|
||||||
|
setOpenMenuSessionId(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilIcon className="size-4" />
|
||||||
|
重命名
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none hover:bg-accent focus:bg-accent"
|
||||||
|
disabled={deletingSessionId === session.session_id}
|
||||||
|
onClick={async () => {
|
||||||
|
setDeletingSessionId(session.session_id);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/claw/sessions/${session.session_id}`,
|
||||||
|
{ method: "DELETE" },
|
||||||
|
);
|
||||||
|
if (!response.ok) return;
|
||||||
|
setSessions((current) =>
|
||||||
|
current.filter(
|
||||||
|
(item) => item.session_id !== session.session_id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setOpenMenuSessionId(null);
|
||||||
|
if (activeSessionId === session.session_id) {
|
||||||
|
window.localStorage.removeItem(
|
||||||
|
"claw.activeSessionId",
|
||||||
|
);
|
||||||
|
setActiveSessionId(null);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new Event("claw-active-session-cleared"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setDeletingSessionId(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2Icon className="size-4" />
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function ThreadListSidebar({
|
|||||||
<SidebarTrigger className="size-8 shrink-0" />
|
<SidebarTrigger className="size-8 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent className="aui-sidebar-content px-2">
|
<SidebarContent className="aui-sidebar-content overflow-hidden px-2">
|
||||||
<ThreadList />
|
<ThreadList />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarRail />
|
<SidebarRail />
|
||||||
@@ -341,7 +341,7 @@ function useSidebarSessions(open: boolean) {
|
|||||||
.then((res) => (res.ok ? res.json() : []))
|
.then((res) => (res.ok ? res.json() : []))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
if (cancelled || !Array.isArray(payload)) return;
|
if (cancelled || !Array.isArray(payload)) return;
|
||||||
setSessions(dedupeSidebarSessions(payload).slice(0, 12));
|
setSessions(dedupeSidebarSessions(payload));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!cancelled) setSessions([]);
|
if (!cancelled) setSessions([]);
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ function Sidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="group peer hidden text-sidebar-foreground md:block"
|
className="group peer hidden text-sidebar-foreground sm:block"
|
||||||
data-state={state}
|
data-state={state}
|
||||||
data-collapsible={state === "collapsed" ? collapsible : ""}
|
data-collapsible={state === "collapsed" ? collapsible : ""}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
@@ -227,7 +227,7 @@ function Sidebar({
|
|||||||
<div
|
<div
|
||||||
data-slot="sidebar-container"
|
data-slot="sidebar-container"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear sm:flex",
|
||||||
side === "left"
|
side === "left"
|
||||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||||
@@ -308,7 +308,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|||||||
data-slot="sidebar-inset"
|
data-slot="sidebar-inset"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full flex-1 flex-col bg-background",
|
"relative flex w-full flex-1 flex-col bg-background",
|
||||||
"md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
|
"sm:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 sm:peer-data-[variant=inset]:m-2 sm:peer-data-[variant=inset]:ml-0 sm:peer-data-[variant=inset]:rounded-xl sm:peer-data-[variant=inset]:shadow-sm",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -426,7 +426,7 @@ function SidebarGroupAction({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
// Increases the hit area of the button on mobile.
|
// Increases the hit area of the button on mobile.
|
||||||
"after:absolute after:-inset-2 md:after:hidden",
|
"after:absolute after:-inset-2 sm:after:hidden",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -561,13 +561,13 @@ function SidebarMenuAction({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
// Increases the hit area of the button on mobile.
|
// Increases the hit area of the button on mobile.
|
||||||
"after:absolute after:-inset-2 md:after:hidden",
|
"after:absolute after:-inset-2 sm:after:hidden",
|
||||||
"peer-data-[size=sm]/menu-button:top-1",
|
"peer-data-[size=sm]/menu-button:top-1",
|
||||||
"peer-data-[size=default]/menu-button:top-1.5",
|
"peer-data-[size=default]/menu-button:top-1.5",
|
||||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
showOnHover &&
|
showOnHover &&
|
||||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground sm:opacity-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768;
|
const MOBILE_BREAKPOINT = 640;
|
||||||
|
|
||||||
export function useIsMobile() {
|
export function useIsMobile() {
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||||
|
|||||||
Reference in New Issue
Block a user