@iloveagents/foundry-web-ui 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/assistant-chat.d.ts +12 -1
- package/dist/components/assistant-chat.js +18 -13
- package/dist/components/chat-bubble.js +9 -6
- package/dist/components/chat-context.d.ts +2 -0
- package/dist/components/chat-context.js +70 -0
- package/dist/components/chat-slots.d.ts +10 -0
- package/dist/components/chat-slots.js +7 -0
- package/dist/components/composer-add-menu.js +3 -2
- package/dist/components/composer-input.d.ts +4 -0
- package/dist/components/composer-input.js +23 -0
- package/dist/components/context-badges.d.ts +4 -1
- package/dist/components/context-badges.js +7 -4
- package/dist/components/context-bar.d.ts +1 -0
- package/dist/components/context-bar.js +11 -3
- package/dist/components/data-table/data-table-frame.d.ts +1 -22
- package/dist/components/data-table/data-table-frame.js +21 -48
- package/dist/components/data-table/data-table-overflow.d.ts +5 -0
- package/dist/components/data-table/data-table-overflow.js +42 -0
- package/dist/components/data-table/data-table.js +88 -87
- package/dist/components/data-table/focus-pane-resize.d.ts +8 -0
- package/dist/components/data-table/focus-pane-resize.js +88 -0
- package/dist/components/focus-chat-pane.d.ts +8 -0
- package/dist/components/focus-chat-pane.js +9 -0
- package/dist/components/focus-dialog.d.ts +8 -0
- package/dist/components/focus-dialog.js +103 -0
- package/dist/components/focus-source-pane.d.ts +7 -0
- package/dist/components/focus-source-pane.js +15 -0
- package/dist/components/loading-indicator.js +1 -1
- package/dist/components/message-timestamp.js +3 -2
- package/dist/components/pin-state-icon.d.ts +5 -0
- package/dist/components/pin-state-icon.js +7 -0
- package/dist/components/pin-toggle.d.ts +7 -0
- package/dist/components/pin-toggle.js +10 -0
- package/dist/components/sidebar.d.ts +18 -1
- package/dist/components/sidebar.js +124 -74
- package/dist/components/tool-panel-layout.d.ts +3 -1
- package/dist/components/tool-panel-layout.js +5 -5
- package/dist/components/tool-panel.d.ts +8 -1
- package/dist/components/tool-panel.js +12 -8
- package/dist/components/user-menu.js +19 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/app-store.d.ts +0 -8
- package/dist/lib/app-store.js +14 -4
- package/dist/lib/chat-context-summary.d.ts +11 -0
- package/dist/lib/chat-context-summary.js +37 -0
- package/dist/lib/chat-lifecycle-store.d.ts +2 -0
- package/dist/lib/chat-lifecycle-store.js +6 -1
- package/dist/lib/client-tools.js +7 -0
- package/dist/lib/create-ui-id.d.ts +2 -0
- package/dist/lib/create-ui-id.js +9 -0
- package/dist/lib/focus-context.d.ts +2 -0
- package/dist/lib/focus-context.js +78 -0
- package/dist/lib/nav-config.d.ts +7 -0
- package/dist/lib/tool-panel-store.d.ts +7 -6
- package/dist/lib/tool-panel-store.js +29 -3
- package/dist/lib/use-new-conversation.js +1 -1
- package/dist/workbench/chat-header.d.ts +14 -0
- package/dist/workbench/chat-header.js +12 -0
- package/dist/workbench/chat-resize-handle.d.ts +4 -0
- package/dist/workbench/chat-resize-handle.js +40 -0
- package/dist/workbench/content-pane.d.ts +14 -0
- package/dist/workbench/content-pane.js +14 -0
- package/dist/workbench/conversation-history.d.ts +11 -0
- package/dist/workbench/conversation-history.js +83 -0
- package/dist/workbench/navigation.d.ts +6 -0
- package/dist/workbench/navigation.js +6 -0
- package/dist/workbench/selection-bridge.d.ts +5 -0
- package/dist/workbench/selection-bridge.js +84 -0
- package/dist/workbench/use-chat-width.d.ts +14 -0
- package/dist/workbench/use-chat-width.js +56 -0
- package/dist/workbench/use-workbench-state.d.ts +50 -0
- package/dist/workbench/use-workbench-state.js +145 -0
- package/dist/workbench/welcome-intro.d.ts +6 -0
- package/dist/workbench/welcome-intro.js +10 -0
- package/dist/workbench/workbench-styles.d.ts +1 -0
- package/dist/workbench/workbench-styles.js +290 -0
- package/dist/workbench/workbench.d.ts +5 -0
- package/dist/workbench/workbench.js +32 -0
- package/dist/workbench.d.ts +2 -0
- package/dist/workbench.js +2 -0
- package/package.json +8 -4
|
@@ -9,10 +9,11 @@ import { cn } from "@iloveagents/foundry-web-primitives";
|
|
|
9
9
|
*/
|
|
10
10
|
export const MessageTimestamp = ({ className }) => {
|
|
11
11
|
const createdAt = useAuiState((s) => s.message.createdAt);
|
|
12
|
-
|
|
12
|
+
const running = useAuiState((s) => s.thread.isRunning);
|
|
13
|
+
if (!createdAt || running)
|
|
13
14
|
return null;
|
|
14
15
|
const d = createdAt instanceof Date ? createdAt : new Date(createdAt);
|
|
15
16
|
if (Number.isNaN(d.getTime()))
|
|
16
17
|
return null;
|
|
17
|
-
return (_jsx("span", { title: d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }), className: cn("text-xs text-muted-foreground/70 tabular-nums select-none", "opacity-0 transition-opacity group-hover:opacity-100", className), children: d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }) }));
|
|
18
|
+
return (_jsx("span", { title: d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }), className: cn("text-xs text-muted-foreground/70 tabular-nums select-none", "opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100", className), children: d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }) }));
|
|
18
19
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Pin } from "lucide-react";
|
|
3
|
+
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
4
|
+
/** State glyph; controls retain explicit Pin/Unpin action labels. */
|
|
5
|
+
export function PinStateIcon({ pinned, className }) {
|
|
6
|
+
return (_jsx(Pin, { "aria-hidden": "true", className: cn("size-4", pinned ? "text-primary fill-primary/25" : "text-muted-foreground", className) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { Button } from "@iloveagents/foundry-web-primitives";
|
|
3
|
+
/** Pin state uses one upright glyph everywhere; the label describes the action. */
|
|
4
|
+
export declare function PinToggle({ pinned, label, className, ...props }: Omit<ComponentProps<typeof Button>, "children"> & {
|
|
5
|
+
pinned: boolean;
|
|
6
|
+
label: string;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PinStateIcon } from "./pin-state-icon.js";
|
|
3
|
+
import { Button, cn } from "@iloveagents/foundry-web-primitives";
|
|
4
|
+
/** Pin state uses one upright glyph everywhere; the label describes the action. */
|
|
5
|
+
export function PinToggle({ pinned, label, className, ...props }) {
|
|
6
|
+
const action = `${pinned ? "Unpin" : "Pin"} ${label}`;
|
|
7
|
+
return (_jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": action, "aria-pressed": pinned, title: action, ...props, className: cn("shrink-0 shadow-none", pinned
|
|
8
|
+
? "bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary"
|
|
9
|
+
: "text-muted-foreground", className), children: _jsx(PinStateIcon, { pinned: pinned }) }));
|
|
10
|
+
}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { NavGroup } from "../lib/nav-config.js";
|
|
1
3
|
declare function scrollNavRowIntoView(nav: HTMLElement, row: HTMLElement): void;
|
|
2
4
|
export declare const _scrollNavRowIntoViewForTest: typeof scrollNavRowIntoView;
|
|
3
|
-
|
|
5
|
+
interface SidebarViewProps {
|
|
6
|
+
/** Display-only navigation override; the navigation registry remains intact. */
|
|
7
|
+
groups?: NavGroup[];
|
|
8
|
+
onNewConversation?: () => void;
|
|
9
|
+
hideFooterActions?: boolean;
|
|
10
|
+
hideNewConversation?: boolean;
|
|
11
|
+
/** Compact rail shows group entry points instead of every child item. */
|
|
12
|
+
collapsedGroupsOnly?: boolean;
|
|
13
|
+
/** Place the primary action beneath a scope heading instead of above navigation. */
|
|
14
|
+
newConversationAfterGroup?: string;
|
|
15
|
+
newConversationLabel?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function Sidebar({ footer, collapsedFooter, ...view }?: SidebarViewProps & {
|
|
18
|
+
footer?: ReactNode;
|
|
19
|
+
collapsedFooter?: ReactNode;
|
|
20
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
4
21
|
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
3
3
|
import { NavLink, useLocation, useNavigate } from "react-router";
|
|
4
|
-
import { Ellipsis, PanelLeft, PanelLeftOpen, SquarePen, ChevronDown, ChevronRight, GripVertical, Plus, Upload, ChevronLeft, } from "lucide-react";
|
|
4
|
+
import { Ellipsis, Layers, PanelLeft, PanelLeftOpen, SquarePen, ChevronDown, ChevronRight, GripVertical, Plus, Upload, ChevronLeft, } from "lucide-react";
|
|
5
5
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
6
|
-
import { useSidebarStore, RAIL_WIDTH, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH, } from "../lib/sidebar-store.js";
|
|
6
|
+
import { useSidebarStore, RAIL_WIDTH, SIDEBAR_WIDTH, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH, } from "../lib/sidebar-store.js";
|
|
7
7
|
import { useToolPanelStore } from "../lib/tool-panel-store.js";
|
|
8
8
|
import { useNewConversation } from "../lib/use-new-conversation.js";
|
|
9
9
|
import { useNavStore } from "../lib/nav-store.js";
|
|
@@ -405,11 +405,11 @@ function ContainerDropZone({ dnd }) {
|
|
|
405
405
|
return;
|
|
406
406
|
}
|
|
407
407
|
await dnd.onDrop(draggedData);
|
|
408
|
-
}, className: cn("mx-2 rounded-md
|
|
408
|
+
}, className: cn("relative mx-2 rounded-md overflow-hidden", isDragOver
|
|
409
409
|
? cn("h-7 flex items-center justify-center gap-1", isFileDragOver
|
|
410
410
|
? "bg-primary/10 border border-dashed border-primary"
|
|
411
411
|
: "bg-primary/10 border border-dashed border-primary/30")
|
|
412
|
-
: "h-1"), children: isDragOver && (_jsxs(_Fragment, { children: [isFileDragOver && _jsx(Upload, { className: "size-3 text-primary" }), _jsx("span", { className: "text-[10px] text-primary/70", children: getContainerDropZoneLabel(isFileDragOver) })] })) }));
|
|
412
|
+
: "-mt-1 h-1"), children: isDragOver && (_jsxs(_Fragment, { children: [isFileDragOver && _jsx(Upload, { className: "size-3 text-primary" }), _jsx("span", { className: "text-[10px] text-primary/70", children: getContainerDropZoneLabel(isFileDragOver) })] })) }));
|
|
413
413
|
}
|
|
414
414
|
/**
|
|
415
415
|
* Leading disclosure cell on every nav row — the Confluence page-tree
|
|
@@ -437,7 +437,7 @@ function ContainerDropZone({ dnd }) {
|
|
|
437
437
|
function NavTwisty({ hasChildren, childrenUnloaded = false, isExpanded, onToggle, label, isActive, depth, inTree = true, }) {
|
|
438
438
|
const cell = cn(NAV_TWISTY_CLASS, getNavDepthOffsets(depth).column);
|
|
439
439
|
if (!hasChildren) {
|
|
440
|
-
return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && !childrenUnloaded && (_jsx("span", { "data-nav-leaf-dot": true, className: cn("size-1 rounded-full", isActive ? "bg-primary/70" : "bg-sidebar-foreground/30") })) }));
|
|
440
|
+
return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && !childrenUnloaded && (_jsx("span", { "data-nav-leaf-dot": true, className: cn("block size-1 shrink-0 rounded-full", isActive ? "bg-primary/70" : "bg-sidebar-foreground/30") })) }));
|
|
441
441
|
}
|
|
442
442
|
return (_jsx("span", { "data-nav-twisty-cell": true, className: cell, children: _jsx("button", { type: "button", "data-nav-twisty": true, "aria-expanded": isExpanded, "aria-label": isExpanded ? `Collapse ${label}` : `Expand ${label}`, onClick: (e) => {
|
|
443
443
|
e.preventDefault();
|
|
@@ -478,7 +478,7 @@ function NavLeafItem({ item, isActive, onClick, depth = 0, }) {
|
|
|
478
478
|
const activeButtonRef = useActiveNavRowScroll(isActive && !hasActions);
|
|
479
479
|
// Items with actions use the group layout (button + action controls)
|
|
480
480
|
if (hasActions) {
|
|
481
|
-
return (_jsxs("div", { ref: activeRowRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, ...(isDisabled ? {} : dropTargetProps), ...(isDisabled ? {} : draggableProps), className: cn("group relative flex items-center w-full rounded-xl border border-transparent transition-colors", isDisabled
|
|
481
|
+
return (_jsxs("div", { ref: activeRowRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, style: { "--nav-depth": depth }, ...(isDisabled ? {} : dropTargetProps), ...(isDisabled ? {} : draggableProps), className: cn("group relative flex items-center w-full rounded-xl border border-transparent transition-colors", isDisabled
|
|
482
482
|
? "opacity-40"
|
|
483
483
|
: isActive
|
|
484
484
|
? ACTIVE_NAV_ROW_CLASS
|
|
@@ -495,7 +495,7 @@ function NavLeafItem({ item, isActive, onClick, depth = 0, }) {
|
|
|
495
495
|
? ACTIVE_NAV_BADGE_CLASS
|
|
496
496
|
: "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }));
|
|
497
497
|
}
|
|
498
|
-
return (_jsxs("button", { ref: activeButtonRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, type: "button", disabled: isDisabled, onClick: isDisabled ? undefined : onClick, ...(isDisabled ? {} : draggableProps), ...(isDisabled ? {} : dropTargetProps), className: cn("relative flex items-center gap-2 w-full", "rounded-lg border border-transparent pr-3 py-1 text-sm text-left", bodyPl, isDisabled
|
|
498
|
+
return (_jsxs("button", { ref: activeButtonRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, style: { "--nav-depth": depth }, type: "button", disabled: isDisabled, onClick: isDisabled ? undefined : onClick, ...(isDisabled ? {} : draggableProps), ...(isDisabled ? {} : dropTargetProps), className: cn("relative flex items-center gap-2 w-full", "rounded-lg border border-transparent pr-3 py-1 text-sm text-left", bodyPl, isDisabled
|
|
499
499
|
? "opacity-40 cursor-default"
|
|
500
500
|
: "text-sidebar-foreground/80 transition-colors hover:bg-sidebar-accent/70", !isDisabled && isActive && ACTIVE_NAV_ROW_CLASS, isDragOver &&
|
|
501
501
|
!isFileDragOver &&
|
|
@@ -515,7 +515,7 @@ function SubNavItems({ items, parentPath, depth = 1, }) {
|
|
|
515
515
|
// children sharing the parent path (e.g., Documents sub-filters) use label matching.
|
|
516
516
|
const allShareParent = items.every((c) => c.to === parentPath);
|
|
517
517
|
return (_jsx("div", { children: items.map((child) => {
|
|
518
|
-
const hasNestedChildren = child.children && child.children.length > 0;
|
|
518
|
+
const hasNestedChildren = !child.childrenHidden && child.children && child.children.length > 0;
|
|
519
519
|
// Folders with children render as collapsible nested items
|
|
520
520
|
if (hasNestedChildren) {
|
|
521
521
|
return _jsx(NestedFolderItem, { item: child, depth: depth }, child.to + child.label);
|
|
@@ -525,7 +525,7 @@ function SubNavItems({ items, parentPath, depth = 1, }) {
|
|
|
525
525
|
// parent path (e.g., folders) are never highlighted on the parent page.
|
|
526
526
|
const isActive = allShareParent
|
|
527
527
|
? isOnParent && activeLabel === child.label
|
|
528
|
-
: child.to !== parentPath && currentPath
|
|
528
|
+
: child.to !== parentPath && isSelectedNavItem(currentPath, child);
|
|
529
529
|
return (_jsx(NavLeafItem, { item: child, isActive: isActive, depth: depth, onClick: () => {
|
|
530
530
|
setActiveLabel(child.label);
|
|
531
531
|
setNavContext({
|
|
@@ -547,6 +547,11 @@ function isInSubtree(currentPage, item) {
|
|
|
547
547
|
return true;
|
|
548
548
|
return item.children?.some((c) => isInSubtree(currentPage, c)) ?? false;
|
|
549
549
|
}
|
|
550
|
+
/** A list owns the sidebar selection of descendants it deliberately does not draw.
|
|
551
|
+
* The route lookup still resolves the actual descendant for chat and page actions. */
|
|
552
|
+
function isSelectedNavItem(currentPage, item) {
|
|
553
|
+
return currentPage === item.to || Boolean(item.childrenHidden && isInSubtree(currentPage, item));
|
|
554
|
+
}
|
|
550
555
|
/** Nested folder with collapsible children (used inside SubNavItems). */
|
|
551
556
|
function NestedFolderItem({ item, depth }) {
|
|
552
557
|
const navigate = useNavigate();
|
|
@@ -554,16 +559,15 @@ function NestedFolderItem({ item, depth }) {
|
|
|
554
559
|
const setNavContext = useAppStore((s) => s.setNavContext);
|
|
555
560
|
const navContext = useAppStore((s) => s.navContext);
|
|
556
561
|
const isInsideSubtree = isInSubtree(currentPath, item);
|
|
557
|
-
const isActive = currentPath
|
|
562
|
+
const isActive = isSelectedNavItem(currentPath, item);
|
|
558
563
|
const isAncestorActive = isInsideSubtree && !isActive;
|
|
559
|
-
const [
|
|
560
|
-
const isExpanded = manualOverride ?? isInsideSubtree;
|
|
564
|
+
const [isExpanded, setExpanded] = useTreeExpanded(item.to, isInsideSubtree);
|
|
561
565
|
const { draggableProps, dropTargetProps, isDragOver, isFileDragOver } = useNavItemDnd(item.dnd);
|
|
562
566
|
const bodyPl = getNavDepthOffsets(depth).body;
|
|
563
567
|
const isDisabled = item.disabled === true;
|
|
564
568
|
const hasActions = Boolean(!isDisabled && item.actions && item.actions.length > 0);
|
|
565
569
|
const activeRowRef = useActiveNavRowScroll(isActive);
|
|
566
|
-
return (_jsxs("div", { ...(isDisabled ? {} : dropTargetProps), children: [_jsxs("div", { ref: activeRowRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, ...(isDisabled ? {} : draggableProps), className: cn("group relative flex items-center w-full", "rounded-xl border border-transparent transition-colors", isDisabled
|
|
570
|
+
return (_jsxs("div", { ...(isDisabled ? {} : dropTargetProps), children: [_jsxs("div", { ref: activeRowRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": depth, style: { "--nav-depth": depth }, ...(isDisabled ? {} : draggableProps), className: cn("group relative flex items-center w-full", "rounded-xl border border-transparent transition-colors", isDisabled
|
|
567
571
|
? "opacity-40"
|
|
568
572
|
: isActive
|
|
569
573
|
? ACTIVE_NAV_ROW_CLASS
|
|
@@ -572,10 +576,10 @@ function NestedFolderItem({ item, depth }) {
|
|
|
572
576
|
!isDisabled &&
|
|
573
577
|
"ring-2 ring-inset ring-sidebar-ring/50 bg-sidebar-selected", isFileDragOver &&
|
|
574
578
|
!isDisabled &&
|
|
575
|
-
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: true, isExpanded: isExpanded, onToggle: () =>
|
|
579
|
+
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: true, isExpanded: isExpanded, onToggle: () => setExpanded(!isExpanded), label: item.label, isActive: (isActive || isAncestorActive) && !isDisabled, depth: depth }), _jsxs("button", { type: "button", disabled: isDisabled, onClick: () => {
|
|
576
580
|
if (isDisabled)
|
|
577
581
|
return;
|
|
578
|
-
|
|
582
|
+
setExpanded(true);
|
|
579
583
|
setNavContext({
|
|
580
584
|
...navContext,
|
|
581
585
|
label: item.label,
|
|
@@ -599,11 +603,11 @@ function NestedFolderItem({ item, depth }) {
|
|
|
599
603
|
: "text-sidebar-icon") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
|
|
600
604
|
? ACTIVE_NAV_BADGE_CLASS
|
|
601
605
|
: "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot })] }), isFileDragOver && !isDisabled && (_jsx(Upload, { "aria-hidden": "true", className: "size-3.5 mr-2 shrink-0 text-primary pointer-events-none" })), _jsx(NavRowControls, { item: item })] }), isExpanded && item.children && (_jsxs("div", { children: [item.children.map((child) => {
|
|
602
|
-
const hasNestedChildren = child.children && child.children.length > 0;
|
|
606
|
+
const hasNestedChildren = !child.childrenHidden && child.children && child.children.length > 0;
|
|
603
607
|
if (hasNestedChildren) {
|
|
604
608
|
return (_jsx(NestedFolderItem, { item: child, depth: depth + 1 }, child.to + child.label));
|
|
605
609
|
}
|
|
606
|
-
const isChildActive = currentPath
|
|
610
|
+
const isChildActive = isSelectedNavItem(currentPath, child);
|
|
607
611
|
return (_jsx(NavLeafItem, { item: child, isActive: isChildActive, depth: depth + 1, onClick: () => {
|
|
608
612
|
setNavContext({
|
|
609
613
|
...navContext,
|
|
@@ -624,13 +628,13 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
624
628
|
const setNavContext = useAppStore((s) => s.setNavContext);
|
|
625
629
|
const navContext = useAppStore((s) => s.navContext);
|
|
626
630
|
const isInsideSubtree = isInSubtree(currentPath, item);
|
|
627
|
-
const isActive = currentPath
|
|
631
|
+
const isActive = isSelectedNavItem(currentPath, item);
|
|
628
632
|
const isAncestorActive = isInsideSubtree && !isActive;
|
|
629
|
-
const [
|
|
633
|
+
const [treeExpanded, setExpanded] = useTreeExpanded(item.to, isInsideSubtree);
|
|
630
634
|
// A heading is never collapsed and carries no chevron: its children ARE
|
|
631
635
|
// the tree it stands for.
|
|
632
|
-
const isExpanded = heading ? true :
|
|
633
|
-
const hasChildren = Boolean(item.children && item.children.length > 0);
|
|
636
|
+
const isExpanded = heading ? true : treeExpanded;
|
|
637
|
+
const hasChildren = Boolean(!item.childrenHidden && item.children && item.children.length > 0);
|
|
634
638
|
const isDisabled = item.disabled === true;
|
|
635
639
|
const hasActions = Boolean(!isDisabled && item.actions && item.actions.length > 0);
|
|
636
640
|
const rowInTree = inTree && !heading;
|
|
@@ -654,7 +658,7 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
654
658
|
!isDisabled &&
|
|
655
659
|
"ring-2 ring-inset ring-sidebar-ring/50 bg-sidebar-selected", isFileDragOver &&
|
|
656
660
|
!isDisabled &&
|
|
657
|
-
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: hasChildren && !heading, childrenUnloaded: item.childrenUnloaded, inTree: rowInTree, isExpanded: isExpanded, onToggle: hasChildren && !heading ? () =>
|
|
661
|
+
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && !isDisabled && "opacity-50"), children: [_jsx(NavTwisty, { hasChildren: hasChildren && !heading, childrenUnloaded: item.childrenUnloaded, inTree: rowInTree, isExpanded: isExpanded, onToggle: hasChildren && !heading ? () => setExpanded(!isExpanded) : undefined, label: item.label, isActive: (selected || ancestor) && !isDisabled, depth: 0 }), _jsxs("button", { type: "button", onClick: () => {
|
|
658
662
|
if (isDisabled)
|
|
659
663
|
return;
|
|
660
664
|
setNavContext({
|
|
@@ -667,7 +671,7 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
667
671
|
.join("\n") || null,
|
|
668
672
|
});
|
|
669
673
|
if (hasChildren)
|
|
670
|
-
|
|
674
|
+
setExpanded(true);
|
|
671
675
|
navigate(item.to);
|
|
672
676
|
}, className: cn("flex items-center gap-2 flex-1 min-w-0 py-1.5 text-sm text-left", bodyClass, "pr-3 transition-[padding] duration-150", hasActions && NAV_ROW_ACTIONS_CLEARANCE, isDisabled
|
|
673
677
|
? "text-sidebar-foreground/45 cursor-default"
|
|
@@ -689,7 +693,7 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
689
693
|
// Transparent border for box-model parity — see the NavLink variant.
|
|
690
694
|
"rounded-lg border border-transparent pr-3 py-1.5 text-left text-sm text-sidebar-foreground/45"), children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, inTree: rowInTree, label: item.label, depth: 0 }), _jsx(item.icon, { className: "size-4 shrink-0 text-sidebar-foreground/45" }), _jsx("span", { className: "truncate", children: item.label })] }));
|
|
691
695
|
}
|
|
692
|
-
return (_jsx(NavLink, { ref: activeLinkRef, "data-active-nav-row":
|
|
696
|
+
return (_jsx(NavLink, { ref: activeLinkRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": 0, to: item.to, ...draggableProps, ...dropTargetProps, className: () => cn("relative flex items-center gap-2 w-full", bodyClass,
|
|
693
697
|
// The transparent border is NOT decoration: it is what makes this
|
|
694
698
|
// row's box model match every other variant. An absolutely
|
|
695
699
|
// positioned twisty resolves against the PADDING box, so a row
|
|
@@ -698,7 +702,7 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
698
702
|
"rounded-xl border border-transparent pr-3 py-1.5 text-sm text-left", "text-sidebar-foreground/85 transition-colors hover:bg-sidebar-accent/70", headingClass, isActive && cn(ACTIVE_NAV_ROW_CLASS, ACTIVE_NAV_TEXT_CLASS), isDragOver &&
|
|
699
703
|
!isFileDragOver &&
|
|
700
704
|
"bg-sidebar-selected ring-2 ring-inset ring-sidebar-ring/50", isFileDragOver &&
|
|
701
|
-
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && "opacity-50"), children: (
|
|
705
|
+
"outline-dashed outline-2 -outline-offset-2 outline-primary bg-primary/10", item.dimmed && "opacity-50"), children: () => (_jsxs(_Fragment, { children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, inTree: rowInTree, label: item.label, isActive: isActive, depth: 0 }), _jsx(item.icon, { className: cn("size-4 shrink-0", isActive ? ACTIVE_NAV_ICON_CLASS : "text-sidebar-icon") }), _jsx("span", { className: "truncate", children: item.label }), item.badge && (_jsx("span", { className: cn("shrink-0 rounded px-1 py-0.5 text-[10px] font-medium", isActive
|
|
702
706
|
? ACTIVE_NAV_BADGE_CLASS
|
|
703
707
|
: "bg-sidebar-accent text-sidebar-accent-foreground"), children: item.badge })), _jsx(NavStatusIcon, { statusIcon: item.statusIcon }), _jsx(NavStatusDot, { statusDot: item.statusDot }), isFileDragOver && (_jsx(Upload, { "aria-hidden": "true", className: "ml-auto size-3.5 shrink-0 text-primary pointer-events-none" }))] })) }));
|
|
704
708
|
}
|
|
@@ -729,7 +733,7 @@ function SidebarResizeHandle() {
|
|
|
729
733
|
window.removeEventListener("pointercancel", onPointerUp);
|
|
730
734
|
};
|
|
731
735
|
}, [setWidth]);
|
|
732
|
-
return (_jsx("div", { className: "absolute inset-y-0 right-0 z-20 hidden w-3 cursor-col-resize md:block", onPointerDown: (event) => {
|
|
736
|
+
return (_jsx("div", { "data-panel-resize": true, title: "Drag to resize navigation. Double-click to reset.", onDoubleClick: () => setWidth(SIDEBAR_WIDTH), className: "absolute inset-y-0 right-0 z-20 hidden w-3 cursor-col-resize md:block", onPointerDown: (event) => {
|
|
733
737
|
event.preventDefault();
|
|
734
738
|
dragState.current = {
|
|
735
739
|
startX: event.clientX,
|
|
@@ -741,11 +745,11 @@ function SidebarResizeHandle() {
|
|
|
741
745
|
}, onKeyDown: (event) => {
|
|
742
746
|
if (event.key === "ArrowLeft") {
|
|
743
747
|
event.preventDefault();
|
|
744
|
-
setWidth(width - 16);
|
|
748
|
+
setWidth(width - (event.shiftKey ? 40 : 16));
|
|
745
749
|
}
|
|
746
750
|
if (event.key === "ArrowRight") {
|
|
747
751
|
event.preventDefault();
|
|
748
|
-
setWidth(width + 16);
|
|
752
|
+
setWidth(width + (event.shiftKey ? 40 : 16));
|
|
749
753
|
}
|
|
750
754
|
if (event.key === "Home") {
|
|
751
755
|
event.preventDefault();
|
|
@@ -827,6 +831,17 @@ function useGroupCollapsed(label, defaultCollapsed) {
|
|
|
827
831
|
// entry. ``map`` is parsed JSON so its prototype is ``Object``'s.
|
|
828
832
|
return Object.prototype.hasOwnProperty.call(map, label) ? map[label] : Boolean(defaultCollapsed);
|
|
829
833
|
}
|
|
834
|
+
/** Keep opened branches stable while routes and asynchronously loaded children change. */
|
|
835
|
+
function useTreeExpanded(to, inside) {
|
|
836
|
+
const key = `tree:${to}`;
|
|
837
|
+
const collapsed = useGroupCollapsed(key, !inside);
|
|
838
|
+
useEffect(() => {
|
|
839
|
+
if (inside && !Object.prototype.hasOwnProperty.call(readCollapseMap(), key)) {
|
|
840
|
+
writeCollapseMap({ ...readCollapseMap(), [key]: false });
|
|
841
|
+
}
|
|
842
|
+
}, [inside, key]);
|
|
843
|
+
return [!collapsed, (expanded) => writeCollapseMap({ ...readCollapseMap(), [key]: !expanded })];
|
|
844
|
+
}
|
|
830
845
|
function toggleGroupCollapsed(label, current) {
|
|
831
846
|
writeCollapseMap({ ...readCollapseMap(), [label]: !current });
|
|
832
847
|
}
|
|
@@ -862,7 +877,7 @@ function NavGroupSection({ group }) {
|
|
|
862
877
|
// that does not exist and reads as a bullet. The disclosure cell still
|
|
863
878
|
// renders either way, so flat rows keep the same label column as tree
|
|
864
879
|
// rows and the sidebar stays on one grid.
|
|
865
|
-
const groupHasBranches = group.items.some((item) => Boolean(item.children && item.children.length > 0));
|
|
880
|
+
const groupHasBranches = group.items.some((item) => Boolean(!item.childrenHidden && item.children && item.children.length > 0));
|
|
866
881
|
// hideLabel + collapsible are mutually exclusive — a hidden label
|
|
867
882
|
// can't host a chevron. The collapsible flag is the opt-in; legacy
|
|
868
883
|
// groups stay uncollapsable so behaviour is unchanged.
|
|
@@ -944,7 +959,7 @@ function NavGroupSection({ group }) {
|
|
|
944
959
|
// that branches. Decided per GROUP: Workspaces has branches, so
|
|
945
960
|
// its leaves are marked; Chats has none, so a dot on every row
|
|
946
961
|
// would mark a distinction that does not exist.
|
|
947
|
-
inTree: groupHasBranches, heading: focused, underHeader: !group.hideLabel }, item.to + item.label))) }, focusKey))] }));
|
|
962
|
+
inTree: group.tree ?? groupHasBranches, heading: focused, underHeader: !group.hideLabel }, item.to + item.label))) }, focusKey))] }));
|
|
948
963
|
}
|
|
949
964
|
/**
|
|
950
965
|
* The "+" dropdown shown next to a group label when the group declares
|
|
@@ -960,9 +975,10 @@ function CreateActionsMenu({ group }) {
|
|
|
960
975
|
action.handler();
|
|
961
976
|
}, children: action.label }, action.id))) })] }));
|
|
962
977
|
}
|
|
963
|
-
function SidebarContent({ collapsed }) {
|
|
978
|
+
function SidebarContent({ collapsed, groups, onNewConversation, hideFooterActions, hideNewConversation, collapsedGroupsOnly, newConversationAfterGroup, newConversationLabel = "New Thread", }) {
|
|
964
979
|
const toggle = useSidebarStore((s) => s.toggle);
|
|
965
|
-
const
|
|
980
|
+
const defaultNewConversation = useNewConversation();
|
|
981
|
+
const handleNewConversation = onNewConversation ?? defaultNewConversation;
|
|
966
982
|
const rawNavConfig = useNavStore((s) => s.config);
|
|
967
983
|
const navLoading = useNavStore((s) => s.navLoading);
|
|
968
984
|
// Sort by `priority` descending so feature hooks can declare slot
|
|
@@ -970,15 +986,15 @@ function SidebarContent({ collapsed }) {
|
|
|
970
986
|
// order in which addGroup() happened to be called. Stable sort: ties
|
|
971
987
|
// preserve insertion order, so groups that don't set a priority keep
|
|
972
988
|
// the legacy append-where-you-arrive behaviour.
|
|
973
|
-
const navConfig = useMemo(() => [...rawNavConfig]
|
|
989
|
+
const navConfig = useMemo(() => [...(groups ?? rawNavConfig)]
|
|
974
990
|
.map((g, i) => ({ g, i, p: g.priority ?? 0 }))
|
|
975
991
|
.sort((a, b) => b.p - a.p || a.i - b.i)
|
|
976
|
-
.map(({ g }) => g), [rawNavConfig]);
|
|
992
|
+
.map(({ g }) => g), [rawNavConfig, groups]);
|
|
977
993
|
// Show skeleton rows during the cold nav fetch — but only until the
|
|
978
994
|
// integrator's primary dynamic group ("Workspaces") has been published.
|
|
979
995
|
// Once it exists, real rows render even if a later phase (status dots) is
|
|
980
996
|
// still resolving, so the skeleton never flashes over a populated tree.
|
|
981
|
-
const hasWorkspacesGroup = navConfig.some((group) => group.label === "Workspaces");
|
|
997
|
+
const hasWorkspacesGroup = navConfig.some((group) => group.label === "Workspaces" || group.meta?.type === "spaces-workspaces");
|
|
982
998
|
const showNavSkeleton = navLoading && !hasWorkspacesGroup;
|
|
983
999
|
const currentPage = useLocation().pathname;
|
|
984
1000
|
const navContext = useAppStore((s) => s.navContext);
|
|
@@ -989,41 +1005,68 @@ function SidebarContent({ collapsed }) {
|
|
|
989
1005
|
e.preventDefault();
|
|
990
1006
|
}, []);
|
|
991
1007
|
if (collapsed) {
|
|
992
|
-
return (_jsxs("div", { className: "flex flex-col items-center gap-1 py-3 flex-1", children: [_jsx(TooltipIconButton, { tooltip: "Expand sidebar", side: "right", onClick: toggle, children: _jsx(PanelLeftOpen, { className: "size-5" }) }), _jsx(TooltipIconButton, { tooltip:
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1008
|
+
return (_jsxs("div", { className: "flex flex-col items-center gap-1 py-3 flex-1", children: [_jsx(TooltipIconButton, { tooltip: "Expand sidebar", side: "right", onClick: toggle, children: _jsx(PanelLeftOpen, { className: "size-5" }) }), !hideNewConversation && !collapsedGroupsOnly && (_jsx(TooltipIconButton, { tooltip: newConversationLabel, side: "right", onClick: handleNewConversation, children: _jsx(SquarePen, { className: "size-4 text-sidebar-icon" }) })), _jsx("div", { className: "mt-4 flex w-full flex-col items-stretch gap-1", children: collapsedGroupsOnly
|
|
1009
|
+
? navConfig
|
|
1010
|
+
.filter((group) => !group.meta?.hideInCollapsedRail &&
|
|
1011
|
+
(group.to || group.items.length > 0) &&
|
|
1012
|
+
group.collapsedRail?.({ currentPage, navContextMeta: navContext.meta ?? {} })
|
|
1013
|
+
.length !== 0)
|
|
1014
|
+
.map((group) => {
|
|
1015
|
+
const rail = group.collapsedRail?.({
|
|
1000
1016
|
currentPage,
|
|
1001
1017
|
navContextMeta: navContext.meta ?? {},
|
|
1002
|
-
})
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1018
|
+
});
|
|
1019
|
+
const active = currentPage === group.to ||
|
|
1020
|
+
group.items.some((item) => isInSubtree(currentPage, item)) ||
|
|
1021
|
+
Boolean(rail?.some((item) => item.isActive));
|
|
1022
|
+
const Icon = group.icon ??
|
|
1023
|
+
group.collapsedRail?.({ currentPage, navContextMeta: navContext.meta ?? {} })[0]
|
|
1024
|
+
?.icon ??
|
|
1025
|
+
group.items[0]?.icon ??
|
|
1026
|
+
Layers;
|
|
1027
|
+
return group.to ? (_jsx(CollapsedNavShortcut, { to: group.to, label: group.label, icon: Icon, isActive: active }, group.label)) : (_jsx(TooltipIconButton, { tooltip: group.label, side: "right", "aria-label": `Open ${group.label}`, "aria-current": active ? "page" : undefined, className: active ? ACTIVE_NAV_ROW_CLASS : undefined, onClick: () => {
|
|
1028
|
+
toggleGroupCollapsed(group.label, true);
|
|
1029
|
+
toggle();
|
|
1030
|
+
}, children: _jsx(Icon, { className: "size-4" }) }, group.label));
|
|
1031
|
+
})
|
|
1032
|
+
: navConfig
|
|
1033
|
+
.map((group, groupIndex) => {
|
|
1034
|
+
// Modules with a non-flat collapsed rail (e.g. Spaces'
|
|
1035
|
+
// "header + active workspace") supply `collapsedRail`; the
|
|
1036
|
+
// shell stays module-agnostic.
|
|
1037
|
+
const items = group.collapsedRail
|
|
1038
|
+
? group
|
|
1039
|
+
.collapsedRail({
|
|
1040
|
+
currentPage,
|
|
1041
|
+
navContextMeta: navContext.meta ?? {},
|
|
1042
|
+
})
|
|
1043
|
+
.map((it) => ({
|
|
1044
|
+
to: it.to,
|
|
1045
|
+
label: it.label,
|
|
1046
|
+
icon: it.icon,
|
|
1047
|
+
isActive: it.isActive,
|
|
1048
|
+
}))
|
|
1049
|
+
: group.items.map((it) => ({
|
|
1050
|
+
to: it.to,
|
|
1051
|
+
label: it.label,
|
|
1052
|
+
icon: it.icon,
|
|
1053
|
+
isActive: currentPage === it.to,
|
|
1054
|
+
}));
|
|
1055
|
+
return { group, groupIndex, items };
|
|
1056
|
+
})
|
|
1057
|
+
.filter(({ items }) => items.length > 0)
|
|
1058
|
+
.map(({ group, groupIndex, items }, visibleIndex) => (
|
|
1059
|
+
// Each non-first VISIBLE group gets a thin top border —
|
|
1060
|
+
// visually separates groups (e.g. user Workspaces vs
|
|
1061
|
+
// Admin) in the collapsed rail without changing the
|
|
1062
|
+
// CollapsedRailItem shape. The wrapper itself is a centred
|
|
1063
|
+
// column so icons stay aligned with the rail.
|
|
1064
|
+
_jsx("div", { className: cn("flex flex-col items-center gap-1", visibleIndex > 0 && "mt-2 pt-2 border-t border-sidebar-border/40"), children: items.map(({ to, label, icon, isActive }) => (_jsx(CollapsedNavShortcut, { to: to, label: label, icon: icon, isActive: isActive }, to))) }, `${group.label || ""}-${groupIndex}`))) })] }));
|
|
1025
1065
|
}
|
|
1026
|
-
|
|
1066
|
+
const newConversationAction = (_jsx("div", { className: "px-1 mb-0.5", children: _jsxs("button", { type: "button", onClick: handleNewConversation, className: cn("flex items-center gap-2 w-full", "rounded-xl px-3 py-2 text-sm text-left", "text-sidebar-foreground/85 transition-colors hover:bg-sidebar-accent/70"), children: [_jsx(SquarePen, { className: "size-4 shrink-0 text-sidebar-icon" }), newConversationLabel] }) }));
|
|
1067
|
+
return (_jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [_jsxs("div", { "data-navigation-header": true, className: "flex items-center justify-between px-3 py-3", children: [_jsx(NavLink, { to: "/", className: "flex items-center gap-2 hover:opacity-80 transition-opacity", children: _jsx(AppBrand, { iconClassName: "text-primary", labelClassName: "truncate text-sm font-semibold text-sidebar-foreground" }) }), _jsx(TooltipIconButton, { tooltip: "Collapse sidebar", side: "right", onClick: toggle, children: _jsx(PanelLeft, { className: "size-5" }) })] }), !hideNewConversation && !newConversationAfterGroup && newConversationAction, _jsxs("nav", { className: "flex-1 overflow-y-auto px-1 pb-16", onDragOverCapture: preventNativeFileDrop, onDropCapture: preventNativeFileDrop, children: [navConfig.map((group) => (_jsxs(Fragment, { children: [_jsx(NavGroupSection, { group: group }), !hideNewConversation &&
|
|
1068
|
+
group.label === newConversationAfterGroup &&
|
|
1069
|
+
newConversationAction] }, group.label))), showNavSkeleton && (_jsx("div", { className: "space-y-1 px-1 pt-2", "aria-hidden": "true", children: Array.from({ length: 6 }).map((_, index) => (_jsx("div", { className: "h-7 mx-1 rounded-lg animate-pulse bg-sidebar-accent/40" }, index))) }))] }), !hideFooterActions && _jsx(SidebarFooterActions, {})] }));
|
|
1027
1070
|
}
|
|
1028
1071
|
function SidebarFooterActions() {
|
|
1029
1072
|
const footerActions = useNavStore((s) => s.footerActions);
|
|
@@ -1034,9 +1077,16 @@ function SidebarFooterActions() {
|
|
|
1034
1077
|
: "border-sidebar-foreground/30") }), action.active ? (action.activeLabel ?? action.label) : action.label] }, action.id))) }));
|
|
1035
1078
|
}
|
|
1036
1079
|
/** Mobile drawer overlay */
|
|
1037
|
-
function MobileDrawer() {
|
|
1080
|
+
function MobileDrawer({ footer, ...view }) {
|
|
1038
1081
|
const isMobileOpen = useSidebarStore((s) => s.isMobileOpen);
|
|
1039
1082
|
const closeMobile = useSidebarStore((s) => s.closeMobile);
|
|
1083
|
+
const locationKey = useLocation().key;
|
|
1084
|
+
const previousLocationKey = useRef(locationKey);
|
|
1085
|
+
useEffect(() => {
|
|
1086
|
+
if (previousLocationKey.current !== locationKey)
|
|
1087
|
+
closeMobile();
|
|
1088
|
+
previousLocationKey.current = locationKey;
|
|
1089
|
+
}, [locationKey, closeMobile]);
|
|
1040
1090
|
const isToolPanelOpen = useToolPanelStore((s) => s.isOpen);
|
|
1041
1091
|
const width = useSidebarStore((s) => s.width);
|
|
1042
1092
|
const drawerWidth = Math.min(width, typeof window !== "undefined" ? window.innerWidth - 24 : width);
|
|
@@ -1052,14 +1102,14 @@ function MobileDrawer() {
|
|
|
1052
1102
|
window.addEventListener("keydown", handleEscape);
|
|
1053
1103
|
return () => window.removeEventListener("keydown", handleEscape);
|
|
1054
1104
|
}, [isMobileOpen, isToolPanelOpen, closeMobile]);
|
|
1055
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: cn("fixed inset-0 z-50 bg-black/50 transition-opacity duration-200", isMobileOpen ? "opacity-100" : "opacity-0 pointer-events-none"), onClick: closeMobile, "aria-hidden": "true" }),
|
|
1105
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: cn("fixed inset-0 z-50 bg-black/50 transition-opacity duration-200", isMobileOpen ? "opacity-100" : "opacity-0 pointer-events-none"), onClick: closeMobile, "aria-hidden": "true" }), _jsxs("aside", { "data-sidebar-surface": "mobile", className: cn("fixed top-0 left-0 z-50 h-dvh", "flex flex-col", "border-r border-sidebar-border/60 bg-sidebar text-sidebar-foreground shadow-xl", "transition-transform duration-200 ease-in-out", isMobileOpen ? "translate-x-0" : "-translate-x-full"), style: { width: drawerWidth }, role: "complementary", "aria-label": "Navigation sidebar", children: [_jsx(SidebarContent, { collapsed: false, ...view }), footer] })] }));
|
|
1056
1106
|
}
|
|
1057
1107
|
/** Desktop inline sidebar (expanded or collapsed rail) */
|
|
1058
|
-
function DesktopSidebar() {
|
|
1108
|
+
function DesktopSidebar({ footer, collapsedFooter, ...view }) {
|
|
1059
1109
|
const isOpen = useSidebarStore((s) => s.isOpen);
|
|
1060
1110
|
const width = useSidebarStore((s) => s.width);
|
|
1061
|
-
return (_jsxs("aside", { "data-sidebar-surface": "desktop", className: cn("group/sidebar relative hidden md:flex flex-col shrink-0", "h-full", "border-r border-sidebar-border/60 bg-sidebar text-sidebar-foreground", "transition-[width] duration-200 ease-in-out", "overflow-hidden"), style: { width: isOpen ? width : RAIL_WIDTH }, role: "complementary", "aria-label": "Navigation sidebar", children: [_jsx(SidebarContent, { collapsed: !isOpen }), isOpen && _jsx(SidebarResizeHandle, {})] }));
|
|
1111
|
+
return (_jsxs("aside", { "data-sidebar-surface": "desktop", "data-sidebar-collapsed": !isOpen || undefined, className: cn("group/sidebar relative hidden md:flex flex-col shrink-0", "h-full", "border-r border-sidebar-border/60 bg-sidebar text-sidebar-foreground", "transition-[width] duration-200 ease-in-out", "overflow-hidden"), style: { width: isOpen ? width : RAIL_WIDTH }, role: "complementary", "aria-label": "Navigation sidebar", children: [_jsx(SidebarContent, { collapsed: !isOpen, ...view }), isOpen ? footer : collapsedFooter, isOpen && _jsx(SidebarResizeHandle, {})] }));
|
|
1062
1112
|
}
|
|
1063
|
-
export function Sidebar() {
|
|
1064
|
-
return (_jsxs(_Fragment, { children: [_jsx(MobileDrawer, {}), _jsx(DesktopSidebar, {})] }));
|
|
1113
|
+
export function Sidebar({ footer, collapsedFooter, ...view } = {}) {
|
|
1114
|
+
return (_jsxs(_Fragment, { children: [_jsx(MobileDrawer, { footer: footer, ...view }), _jsx(DesktopSidebar, { footer: footer, collapsedFooter: collapsedFooter, ...view })] }));
|
|
1065
1115
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
interface ToolPanelLayoutProps {
|
|
7
7
|
children: React.ReactNode;
|
|
8
|
+
/** Keep tools inside the working area, leaving sibling chat accessible. */
|
|
9
|
+
contained?: boolean;
|
|
8
10
|
}
|
|
9
|
-
export declare function ToolPanelLayout({ children }: ToolPanelLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function ToolPanelLayout({ children, contained }: ToolPanelLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
10
12
|
export {};
|
|
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
3
3
|
import { useToolPanelStore } from "../lib/tool-panel-store.js";
|
|
4
4
|
import { ToolPanel } from "./tool-panel.js";
|
|
5
|
-
export function ToolPanelLayout({ children }) {
|
|
6
|
-
const isOpen = useToolPanelStore((state) => state.isOpen);
|
|
5
|
+
export function ToolPanelLayout({ children, contained = false }) {
|
|
6
|
+
const isOpen = useToolPanelStore((state) => state.isOpen && !state.isWindowHosted);
|
|
7
7
|
const panelWidth = useToolPanelStore((state) => state.panelWidth);
|
|
8
8
|
const useOverlay = useToolPanelStore((state) => state.useOverlay);
|
|
9
|
-
return (_jsxs("div", { className: "relative h-full flex-1 min-w-0 overflow-hidden", children: [_jsx("div", { className: cn("h-full flex flex-col transition-all duration-300 ease-in-out", useOverlay && isOpen && "hidden"), style: {
|
|
10
|
-
width: isOpen && !useOverlay ? `calc(100% - ${panelWidth}px)` : "100%",
|
|
11
|
-
}, children: children }), _jsx(ToolPanel, {})] }));
|
|
9
|
+
return (_jsxs("div", { className: "relative h-full flex-1 min-w-0 overflow-hidden", children: [_jsx("div", { className: cn("h-full flex flex-col transition-all duration-300 ease-in-out", (contained || useOverlay) && isOpen && "hidden"), style: {
|
|
10
|
+
width: isOpen && !useOverlay && !contained ? `calc(100% - ${panelWidth}px)` : "100%",
|
|
11
|
+
}, children: children }), _jsx(ToolPanel, { contained: contained })] }));
|
|
12
12
|
}
|
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ToolPanelContent } from "../lib/tool-panel-store.js";
|
|
2
|
+
/** Renders panel content using the renderer registry, then built-in markdown/text. */
|
|
3
|
+
export declare function PanelContentRenderer({ content }: {
|
|
4
|
+
content: ToolPanelContent;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function ToolPanel({ contained }: {
|
|
7
|
+
contained?: boolean;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|