mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 18:06:06 +00:00
feat: improve sidebar usability
This commit is contained in:
committed by
huanghuoguoguo
parent
6dd296612b
commit
d0f8f080e9
@@ -83,6 +83,7 @@ import {
|
|||||||
SidebarMenuSub,
|
SidebarMenuSub,
|
||||||
SidebarMenuSubButton,
|
SidebarMenuSubButton,
|
||||||
SidebarMenuSubItem,
|
SidebarMenuSubItem,
|
||||||
|
SidebarRail,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from '@/components/ui/sidebar';
|
} from '@/components/ui/sidebar';
|
||||||
import {
|
import {
|
||||||
@@ -726,7 +727,12 @@ function NavItems({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{item.name}</span>
|
<span className="min-w-0 flex-1 truncate">{item.name}</span>
|
||||||
|
{isBot && item.legacyAdapter && (
|
||||||
|
<span className="ml-auto shrink-0 rounded border border-amber-200 bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium leading-none text-amber-700 dark:border-amber-900/50 dark:bg-amber-950/30 dark:text-amber-300">
|
||||||
|
{t('bots.legacyAdapterBadge')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -789,7 +795,14 @@ function NavItems({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{item.name}</span>
|
<span className="min-w-0 flex-1 truncate">
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
{isBot && item.legacyAdapter && (
|
||||||
|
<span className="shrink-0 rounded border border-amber-200 bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium leading-none text-amber-700 dark:border-amber-900/50 dark:bg-amber-950/30 dark:text-amber-300">
|
||||||
|
{t('bots.legacyAdapterBadge')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{item.kind && (
|
{item.kind && (
|
||||||
<span
|
<span
|
||||||
className="ml-auto flex shrink-0 items-center text-muted-foreground"
|
className="ml-auto flex shrink-0 items-center text-muted-foreground"
|
||||||
@@ -2241,6 +2254,7 @@ export default function HomeSidebar({
|
|||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
|
<SidebarRail />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<Dialog open={feedbackOpen} onOpenChange={setFeedbackOpen}>
|
<Dialog open={feedbackOpen} onOpenChange={setFeedbackOpen}>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface SidebarEntityItem {
|
|||||||
updatedAt?: string; // ISO timestamp for sorting by most recently edited
|
updatedAt?: string; // ISO timestamp for sorting by most recently edited
|
||||||
// Bot-specific fields
|
// Bot-specific fields
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
legacyAdapter?: boolean;
|
||||||
// MCP-specific fields
|
// MCP-specific fields
|
||||||
runtimeStatus?: 'connecting' | 'connected' | 'error';
|
runtimeStatus?: 'connecting' | 'connected' | 'error';
|
||||||
// Plugin-specific fields
|
// Plugin-specific fields
|
||||||
@@ -117,7 +118,15 @@ export function SidebarDataProvider({
|
|||||||
|
|
||||||
const refreshBots = useCallback(async () => {
|
const refreshBots = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await httpClient.getBots();
|
const [resp, adaptersResp] = await Promise.all([
|
||||||
|
httpClient.getBots(),
|
||||||
|
httpClient.getAdapters().catch(() => ({ adapters: [] })),
|
||||||
|
]);
|
||||||
|
const legacyAdapterNames = new Set(
|
||||||
|
adaptersResp.adapters
|
||||||
|
.filter((adapter) => adapter.spec.legacy)
|
||||||
|
.map((adapter) => adapter.name),
|
||||||
|
);
|
||||||
setBots(
|
setBots(
|
||||||
resp.bots.map((bot) => ({
|
resp.bots.map((bot) => ({
|
||||||
id: bot.uuid || '',
|
id: bot.uuid || '',
|
||||||
@@ -126,6 +135,7 @@ export function SidebarDataProvider({
|
|||||||
iconURL: httpClient.getAdapterIconURL(bot.adapter),
|
iconURL: httpClient.getAdapterIconURL(bot.adapter),
|
||||||
updatedAt: bot.updated_at,
|
updatedAt: bot.updated_at,
|
||||||
enabled: bot.enable ?? true,
|
enabled: bot.enable ?? true,
|
||||||
|
legacyAdapter: legacyAdapterNames.has(bot.adapter),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -24,15 +24,23 @@ import {
|
|||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
|
|
||||||
const SIDEBAR_STORAGE_KEY = 'sidebar_state';
|
const SIDEBAR_STORAGE_KEY = 'sidebar_state';
|
||||||
const SIDEBAR_WIDTH = '16rem';
|
const SIDEBAR_WIDTH_STORAGE_KEY = 'sidebar_width';
|
||||||
|
const SIDEBAR_WIDTH_DEFAULT = 256;
|
||||||
|
const SIDEBAR_WIDTH_MIN = 220;
|
||||||
|
const SIDEBAR_WIDTH_MAX = 420;
|
||||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||||
|
const SIDEBAR_RESIZE_DRAG_THRESHOLD = 4;
|
||||||
|
|
||||||
type SidebarContextProps = {
|
type SidebarContextProps = {
|
||||||
state: 'expanded' | 'collapsed';
|
state: 'expanded' | 'collapsed';
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean | ((open: boolean) => boolean)) => void;
|
||||||
|
sidebarWidth: number;
|
||||||
|
setSidebarWidth: (width: number | ((width: number) => number)) => void;
|
||||||
|
isSidebarResizing: boolean;
|
||||||
|
setSidebarResizing: (isResizing: boolean) => void;
|
||||||
openMobile: boolean;
|
openMobile: boolean;
|
||||||
setOpenMobile: (open: boolean) => void;
|
setOpenMobile: (open: boolean) => void;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
@@ -50,6 +58,24 @@ function useSidebar() {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampSidebarWidth(width: number) {
|
||||||
|
if (!Number.isFinite(width)) return SIDEBAR_WIDTH_DEFAULT;
|
||||||
|
|
||||||
|
return Math.min(
|
||||||
|
SIDEBAR_WIDTH_MAX,
|
||||||
|
Math.max(SIDEBAR_WIDTH_MIN, Math.round(width)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStoredSidebarWidth() {
|
||||||
|
if (typeof window === 'undefined') return SIDEBAR_WIDTH_DEFAULT;
|
||||||
|
|
||||||
|
const stored = localStorage.getItem(SIDEBAR_WIDTH_STORAGE_KEY);
|
||||||
|
if (stored === null) return SIDEBAR_WIDTH_DEFAULT;
|
||||||
|
|
||||||
|
return clampSidebarWidth(Number.parseInt(stored, 10));
|
||||||
|
}
|
||||||
|
|
||||||
function SidebarProvider({
|
function SidebarProvider({
|
||||||
defaultOpen = true,
|
defaultOpen = true,
|
||||||
open: openProp,
|
open: openProp,
|
||||||
@@ -65,6 +91,10 @@ function SidebarProvider({
|
|||||||
}) {
|
}) {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const [openMobile, setOpenMobile] = React.useState(false);
|
const [openMobile, setOpenMobile] = React.useState(false);
|
||||||
|
const [sidebarWidth, _setSidebarWidth] = React.useState(
|
||||||
|
getStoredSidebarWidth,
|
||||||
|
);
|
||||||
|
const [isSidebarResizing, setSidebarResizing] = React.useState(false);
|
||||||
|
|
||||||
// This is the internal state of the sidebar.
|
// This is the internal state of the sidebar.
|
||||||
// We use openProp and setOpenProp for control from outside the component.
|
// We use openProp and setOpenProp for control from outside the component.
|
||||||
@@ -93,6 +123,23 @@ function SidebarProvider({
|
|||||||
[setOpenProp, open],
|
[setOpenProp, open],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setSidebarWidth = React.useCallback(
|
||||||
|
(value: number | ((width: number) => number)) => {
|
||||||
|
_setSidebarWidth((currentWidth) => {
|
||||||
|
const nextWidth =
|
||||||
|
typeof value === 'function' ? value(currentWidth) : value;
|
||||||
|
const clampedWidth = clampSidebarWidth(nextWidth);
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(SIDEBAR_WIDTH_STORAGE_KEY, String(clampedWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
return clampedWidth;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// Helper to toggle the sidebar.
|
// Helper to toggle the sidebar.
|
||||||
const toggleSidebar = React.useCallback(() => {
|
const toggleSidebar = React.useCallback(() => {
|
||||||
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
||||||
@@ -123,12 +170,28 @@ function SidebarProvider({
|
|||||||
state,
|
state,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
|
sidebarWidth,
|
||||||
|
setSidebarWidth,
|
||||||
|
isSidebarResizing,
|
||||||
|
setSidebarResizing,
|
||||||
isMobile,
|
isMobile,
|
||||||
openMobile,
|
openMobile,
|
||||||
setOpenMobile,
|
setOpenMobile,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
}),
|
}),
|
||||||
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
|
[
|
||||||
|
state,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
sidebarWidth,
|
||||||
|
setSidebarWidth,
|
||||||
|
isSidebarResizing,
|
||||||
|
setSidebarResizing,
|
||||||
|
isMobile,
|
||||||
|
openMobile,
|
||||||
|
setOpenMobile,
|
||||||
|
toggleSidebar,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -138,7 +201,7 @@ function SidebarProvider({
|
|||||||
data-slot="sidebar-wrapper"
|
data-slot="sidebar-wrapper"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
'--sidebar-width': SIDEBAR_WIDTH,
|
'--sidebar-width': `${sidebarWidth}px`,
|
||||||
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
||||||
...style,
|
...style,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
@@ -168,7 +231,8 @@ function Sidebar({
|
|||||||
variant?: 'sidebar' | 'floating' | 'inset';
|
variant?: 'sidebar' | 'floating' | 'inset';
|
||||||
collapsible?: 'offcanvas' | 'icon' | 'none';
|
collapsible?: 'offcanvas' | 'icon' | 'none';
|
||||||
}) {
|
}) {
|
||||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
const { isMobile, state, openMobile, setOpenMobile, isSidebarResizing } =
|
||||||
|
useSidebar();
|
||||||
|
|
||||||
if (collapsible === 'none') {
|
if (collapsible === 'none') {
|
||||||
return (
|
return (
|
||||||
@@ -224,6 +288,7 @@ function Sidebar({
|
|||||||
data-slot="sidebar-gap"
|
data-slot="sidebar-gap"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
|
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
|
||||||
|
isSidebarResizing && 'transition-none',
|
||||||
'group-data-[collapsible=offcanvas]:w-0',
|
'group-data-[collapsible=offcanvas]:w-0',
|
||||||
'group-data-[side=right]:rotate-180',
|
'group-data-[side=right]:rotate-180',
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
@@ -235,6 +300,7 @@ function Sidebar({
|
|||||||
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 md:flex',
|
||||||
|
isSidebarResizing && 'transition-none',
|
||||||
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)]',
|
||||||
@@ -284,8 +350,132 @@ function SidebarTrigger({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
|
type SidebarResizeDragState = {
|
||||||
const { toggleSidebar } = useSidebar();
|
pointerId: number;
|
||||||
|
startX: number;
|
||||||
|
startWidth: number;
|
||||||
|
side: 'left' | 'right';
|
||||||
|
hasDragged: boolean;
|
||||||
|
captureTarget: HTMLElement;
|
||||||
|
previousCursor: string;
|
||||||
|
previousUserSelect: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSidebarSide(element: HTMLElement): 'left' | 'right' {
|
||||||
|
const sidebarRoot = element.closest('[data-slot="sidebar"]');
|
||||||
|
return sidebarRoot?.getAttribute('data-side') === 'right' ? 'right' : 'left';
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSidebarResizeBodyState(dragState: SidebarResizeDragState) {
|
||||||
|
document.body.style.cursor = dragState.previousCursor;
|
||||||
|
document.body.style.userSelect = dragState.previousUserSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SidebarRail({
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
onPointerCancel,
|
||||||
|
onPointerDown,
|
||||||
|
onPointerMove,
|
||||||
|
onPointerUp,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'button'>) {
|
||||||
|
const {
|
||||||
|
setOpen,
|
||||||
|
sidebarWidth,
|
||||||
|
setSidebarResizing,
|
||||||
|
setSidebarWidth,
|
||||||
|
toggleSidebar,
|
||||||
|
} = useSidebar();
|
||||||
|
const dragStateRef = React.useRef<SidebarResizeDragState | null>(null);
|
||||||
|
const removeDragListenersRef = React.useRef<(() => void) | null>(null);
|
||||||
|
const ignoreNextClickRef = React.useRef(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
removeDragListenersRef.current?.();
|
||||||
|
removeDragListenersRef.current = null;
|
||||||
|
|
||||||
|
if (dragStateRef.current) {
|
||||||
|
restoreSidebarResizeBodyState(dragStateRef.current);
|
||||||
|
dragStateRef.current = null;
|
||||||
|
}
|
||||||
|
setSidebarResizing(false);
|
||||||
|
};
|
||||||
|
}, [setSidebarResizing]);
|
||||||
|
|
||||||
|
function clearDragState() {
|
||||||
|
const dragState = dragStateRef.current;
|
||||||
|
if (!dragState) return null;
|
||||||
|
|
||||||
|
removeDragListenersRef.current?.();
|
||||||
|
removeDragListenersRef.current = null;
|
||||||
|
|
||||||
|
if (dragState.captureTarget.hasPointerCapture(dragState.pointerId)) {
|
||||||
|
dragState.captureTarget.releasePointerCapture(dragState.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreSidebarResizeBodyState(dragState);
|
||||||
|
dragStateRef.current = null;
|
||||||
|
setSidebarResizing(false);
|
||||||
|
|
||||||
|
return dragState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResizePointerMove(event: PointerEvent | React.PointerEvent) {
|
||||||
|
const dragState = dragStateRef.current;
|
||||||
|
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
||||||
|
|
||||||
|
const delta =
|
||||||
|
dragState.side === 'left'
|
||||||
|
? event.clientX - dragState.startX
|
||||||
|
: dragState.startX - event.clientX;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!dragState.hasDragged &&
|
||||||
|
Math.abs(delta) >= SIDEBAR_RESIZE_DRAG_THRESHOLD
|
||||||
|
) {
|
||||||
|
dragState.hasDragged = true;
|
||||||
|
setSidebarResizing(true);
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dragState.hasDragged) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
setSidebarWidth(dragState.startWidth + delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResizePointerEnd(event: PointerEvent | React.PointerEvent) {
|
||||||
|
const currentDragState = dragStateRef.current;
|
||||||
|
if (!currentDragState || currentDragState.pointerId !== event.pointerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const dragState = clearDragState();
|
||||||
|
if (!dragState) return;
|
||||||
|
|
||||||
|
ignoreNextClickRef.current = dragState.hasDragged;
|
||||||
|
if (dragState.hasDragged) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.setTimeout(() => {
|
||||||
|
ignoreNextClickRef.current = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResizePointerCancel(event: PointerEvent | React.PointerEvent) {
|
||||||
|
const currentDragState = dragStateRef.current;
|
||||||
|
if (!currentDragState || currentDragState.pointerId !== event.pointerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const dragState = clearDragState();
|
||||||
|
if (dragState?.hasDragged) {
|
||||||
|
ignoreNextClickRef.current = true;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
ignoreNextClickRef.current = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -293,12 +483,74 @@ function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
|
|||||||
data-slot="sidebar-rail"
|
data-slot="sidebar-rail"
|
||||||
aria-label="Toggle Sidebar"
|
aria-label="Toggle Sidebar"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={toggleSidebar}
|
onClick={(event) => {
|
||||||
title="Toggle Sidebar"
|
onClick?.(event);
|
||||||
|
if (event.defaultPrevented) return;
|
||||||
|
|
||||||
|
if (ignoreNextClickRef.current) {
|
||||||
|
ignoreNextClickRef.current = false;
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSidebar();
|
||||||
|
}}
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
onPointerDown?.(event);
|
||||||
|
if (event.defaultPrevented || event.button !== 0) return;
|
||||||
|
|
||||||
|
const target = event.currentTarget;
|
||||||
|
dragStateRef.current = {
|
||||||
|
pointerId: event.pointerId,
|
||||||
|
startX: event.clientX,
|
||||||
|
startWidth: sidebarWidth,
|
||||||
|
side: getSidebarSide(target),
|
||||||
|
hasDragged: false,
|
||||||
|
captureTarget: target,
|
||||||
|
previousCursor: document.body.style.cursor,
|
||||||
|
previousUserSelect: document.body.style.userSelect,
|
||||||
|
};
|
||||||
|
|
||||||
|
target.setPointerCapture(event.pointerId);
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
|
||||||
|
const handleDocumentPointerMove = (pointerEvent: PointerEvent) =>
|
||||||
|
handleResizePointerMove(pointerEvent);
|
||||||
|
const handleDocumentPointerEnd = (pointerEvent: PointerEvent) =>
|
||||||
|
handleResizePointerEnd(pointerEvent);
|
||||||
|
const handleDocumentPointerCancel = (pointerEvent: PointerEvent) =>
|
||||||
|
handleResizePointerCancel(pointerEvent);
|
||||||
|
|
||||||
|
document.addEventListener('pointermove', handleDocumentPointerMove);
|
||||||
|
document.addEventListener('pointerup', handleDocumentPointerEnd);
|
||||||
|
document.addEventListener('pointercancel', handleDocumentPointerCancel);
|
||||||
|
|
||||||
|
removeDragListenersRef.current = () => {
|
||||||
|
document.removeEventListener(
|
||||||
|
'pointermove',
|
||||||
|
handleDocumentPointerMove,
|
||||||
|
);
|
||||||
|
document.removeEventListener('pointerup', handleDocumentPointerEnd);
|
||||||
|
document.removeEventListener(
|
||||||
|
'pointercancel',
|
||||||
|
handleDocumentPointerCancel,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
onPointerMove={(event) => {
|
||||||
|
onPointerMove?.(event);
|
||||||
|
}}
|
||||||
|
onPointerUp={(event) => {
|
||||||
|
onPointerUp?.(event);
|
||||||
|
}}
|
||||||
|
onPointerCancel={(event) => {
|
||||||
|
onPointerCancel?.(event);
|
||||||
|
}}
|
||||||
|
title="Drag to resize or click to toggle sidebar"
|
||||||
className={cn(
|
className={cn(
|
||||||
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
|
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 cursor-col-resize touch-none transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
|
||||||
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
|
'[[data-state=collapsed]_&]:cursor-pointer',
|
||||||
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
||||||
'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
|
'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
|
||||||
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
||||||
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
||||||
|
|||||||
Reference in New Issue
Block a user