@parallelworks/ui 0.29.0 → 0.30.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.
@@ -0,0 +1,57 @@
1
+ import { createContext, useCallback, useContext, useRef, useState } from "react";
2
+ //#region src/ai/core/sidebarState.ts
3
+ var SIDEBAR_STORAGE_KEY = "aiChatSidebar";
4
+ var LEGACY_COLLAPSED_STORAGE_KEY = "aiChatSidebarCollapsed";
5
+ function readStoredSidebar() {
6
+ try {
7
+ const stored = globalThis.localStorage?.getItem(SIDEBAR_STORAGE_KEY);
8
+ if (stored === "expanded" || stored === "collapsed") return stored;
9
+ return globalThis.localStorage?.getItem(LEGACY_COLLAPSED_STORAGE_KEY) === "1" ? "collapsed" : "expanded";
10
+ } catch {
11
+ return "expanded";
12
+ }
13
+ }
14
+ function storeSidebar(state) {
15
+ if (state === "hidden") return;
16
+ try {
17
+ globalThis.localStorage?.setItem(SIDEBAR_STORAGE_KEY, state);
18
+ } catch {}
19
+ }
20
+ /** A value the host may own through a prop. Without the prop the provider
21
+ * keeps it; either way the change callback hears every change. The ref
22
+ * reads the latest value from callbacks that should not re-create. */
23
+ function useControllable(controlled, initial, onChange, remember) {
24
+ const [own, setOwn] = useState(initial);
25
+ const value = controlled === void 0 ? own : controlled;
26
+ const latest = useRef({
27
+ value,
28
+ controlled: controlled !== void 0,
29
+ onChange,
30
+ remember
31
+ });
32
+ latest.current = {
33
+ value,
34
+ controlled: controlled !== void 0,
35
+ onChange,
36
+ remember
37
+ };
38
+ return [
39
+ value,
40
+ useCallback((next) => {
41
+ const l = latest.current;
42
+ if (Object.is(next, l.value)) return;
43
+ if (!l.controlled) {
44
+ setOwn(next);
45
+ l.remember?.(next);
46
+ }
47
+ l.onChange?.(next);
48
+ }, []),
49
+ latest
50
+ ];
51
+ }
52
+ var ReportSidebarPresentationContext = createContext(() => {});
53
+ function useReportSidebarPresentation() {
54
+ return useContext(ReportSidebarPresentationContext);
55
+ }
56
+ //#endregion
57
+ export { ReportSidebarPresentationContext, readStoredSidebar, storeSidebar, useControllable, useReportSidebarPresentation };
@@ -11,7 +11,7 @@ export { default as BlockedGroupBanner } from './components/BlockedGroupBanner.j
11
11
  export { default as BranchNavigator } from './components/BranchNavigator.js';
12
12
  export { default as ChatEmptyState } from './components/ChatEmptyState.js';
13
13
  export { type ChatInputHandle, default as ChatInput, } from './components/ChatInput.js';
14
- export { default as ChatLayout } from './components/ChatLayout.js';
14
+ export { DRAWER_BELOW_PX, default as ChatLayout, type SidebarMode, } from './components/ChatLayout.js';
15
15
  export { default as ChatMessageView } from './components/ChatMessage.js';
16
16
  export { default as ChatMessageList } from './components/ChatMessageList.js';
17
17
  export { default as ChatSidebar } from './components/ChatSidebar.js';
@@ -34,6 +34,7 @@ export { KeyboardShortcutsProvider } from './core/KeyboardShortcutsProvider.js';
34
34
  export { applyPartDelta, finalizeParts } from './core/parts.js';
35
35
  export { pasteInlineMaxBytes } from './core/pastes.js';
36
36
  export { providerIssueFor } from './core/providerIssues.js';
37
+ export type { SidebarPresentation, SidebarState } from './core/sidebarState.js';
37
38
  export { default as useDragDrop } from './core/useDragDrop.js';
38
39
  export { useKeyboardShortcuts } from './core/useKeyboardShortcuts.js';
39
40
  export { type ChatStrings, defaultChatStrings } from './strings.js';
@@ -41,5 +42,5 @@ export * from './types.js';
41
42
  export { ConversationSidebar, type ConversationSidebarGroup, useResizableSidebarWidth, } from './ui/ConversationSidebar.js';
42
43
  export { type DropdownOption, default as Dropdown } from './ui/Dropdown.js';
43
44
  export { NoticeBar, NoticeCard, type NoticeTone, OutputCard } from './ui/Notice.js';
44
- export { RenameDialog, type RowDialogStrings, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
45
+ export { RenameDialog, type RowDialogStrings, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
45
46
  export { formatFileSize, isMac, isOrgModel } from './utils.js';
package/dist/ai/index.js CHANGED
@@ -35,14 +35,14 @@ import ChatEmptyState from "./components/ChatEmptyState.js";
35
35
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./core/events.js";
36
36
  import { useKeyboardShortcuts } from "./core/useKeyboardShortcuts.js";
37
37
  import { KeyboardShortcutsProvider } from "./core/KeyboardShortcutsProvider.js";
38
- import { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
38
+ import { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
39
39
  import { ConversationSidebar, useResizableSidebarWidth } from "./ui/ConversationSidebar.js";
40
40
  import ShareDialog, { ShareButton } from "./components/ShareDialog.js";
41
41
  import ChatSidebar from "./components/ChatSidebar.js";
42
- import ChatLayout from "./components/ChatLayout.js";
42
+ import ChatLayout, { DRAWER_BELOW_PX } from "./components/ChatLayout.js";
43
43
  import ChatMessage_default from "./components/ChatMessage.js";
44
44
  import ChatMessageList from "./components/ChatMessageList.js";
45
45
  import ChatThread from "./components/ChatThread.js";
46
46
  import ComposerFrame from "./components/ComposerFrame.js";
47
47
  import { NoticeBar, NoticeCard, OutputCard } from "./ui/Notice.js";
48
- export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, ConversationSidebar, DEFAULT_PERMISSION_MODE, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
48
+ export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, ConversationSidebar, DEFAULT_PERMISSION_MODE, DRAWER_BELOW_PX, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
@@ -33,6 +33,10 @@ export interface ChatStrings {
33
33
  bannerKeyRejected: (provider: string) => string;
34
34
  bannerUnreachable: (provider: string) => string;
35
35
  };
36
+ modelPicker: {
37
+ /** Beside a provider another user registered and shared with the reader. */
38
+ sharedBy: (owner: string) => string;
39
+ };
36
40
  queue: {
37
41
  remove: string;
38
42
  /** Under a message that goes out once the running reply finishes. */
@@ -96,6 +100,8 @@ export interface ChatStrings {
96
100
  sharedWithYou: string;
97
101
  openSidebar: string;
98
102
  closeSidebar: string;
103
+ /** Names the drawer the list opens in on a narrow layout. */
104
+ drawerLabel: string;
99
105
  resizeLabel: string;
100
106
  resizeHint: string;
101
107
  newChat: string;
@@ -47,6 +47,7 @@ var defaultChatStrings = {
47
47
  bannerKeyRejected: (provider) => `API key rejected for \u201c${provider}\u201d`,
48
48
  bannerUnreachable: (provider) => `\u201c${provider}\u201d is unreachable`
49
49
  },
50
+ modelPicker: { sharedBy: (owner) => `Shared by ${owner}` },
50
51
  activity: {
51
52
  tokens: (input, output) => `↑${input} ↓${output} tokens`,
52
53
  stop: "Stop"
@@ -100,6 +101,7 @@ var defaultChatStrings = {
100
101
  sharedWithYou: "Shared with you",
101
102
  openSidebar: "Open sidebar",
102
103
  closeSidebar: "Close sidebar",
104
+ drawerLabel: "Conversations",
103
105
  resizeLabel: "Resize conversation list",
104
106
  resizeHint: "Drag to resize; double-click to reset",
105
107
  newChat: "New chat",
@@ -19,7 +19,7 @@ export interface ConversationSidebarGroup<T> {
19
19
  /** A list of conversations beside the thread: headed groups of rows that
20
20
  * collapse to a rail, with an optional drag handle for the width. The host
21
21
  * decides what is listed, how it is grouped and how each row reads. */
22
- export declare function ConversationSidebar<T>({ label, collapsed, onToggle, toggleLabels, resize, groups, getKey, renderRow, loading, placeholder, top, rail, footer, cycle, groupTestId, children, }: {
22
+ export declare function ConversationSidebar<T>({ label, collapsed: collapsedProp, onToggle, toggleLabels, resize, fill, groups, getKey, renderRow, loading, placeholder, top, rail, footer, cycle, groupTestId, children, }: {
23
23
  label?: string;
24
24
  collapsed: boolean;
25
25
  onToggle: () => void;
@@ -33,6 +33,9 @@ export declare function ConversationSidebar<T>({ label, collapsed, onToggle, tog
33
33
  label: string;
34
34
  hint: string;
35
35
  };
36
+ /** Takes the width of its container, as inside a drawer, with no rail and
37
+ * no drag handle. */
38
+ fill?: boolean;
36
39
  groups: ConversationSidebarGroup<T>[];
37
40
  getKey: (item: T) => string;
38
41
  /** One `SidebarRow`. */
@@ -90,18 +90,21 @@ function useItemCycling(ordered, cycle) {
90
90
  /** A list of conversations beside the thread: headed groups of rows that
91
91
  * collapse to a rail, with an optional drag handle for the width. The host
92
92
  * decides what is listed, how it is grouped and how each row reads. */
93
- function ConversationSidebar({ label, collapsed, onToggle, toggleLabels, resize, groups, getKey, renderRow, loading = false, placeholder, top, rail, footer, cycle, groupTestId, children }) {
93
+ function ConversationSidebar({ label, collapsed: collapsedProp, onToggle, toggleLabels, resize, fill = false, groups, getKey, renderRow, loading = false, placeholder, top, rail, footer, cycle, groupTestId, children }) {
94
94
  const sized = useResizableSidebarWidth(resize?.storageKey ?? "");
95
95
  useItemCycling(groups.flatMap((g) => g.items), cycle);
96
+ const collapsed = collapsedProp && !fill;
97
+ const resizable = resize && !fill;
96
98
  return /* @__PURE__ */ jsxs(SidebarPanel, {
97
99
  collapsed,
98
100
  label,
99
- ...resize ? {
101
+ ...resizable ? {
100
102
  width: sized.width,
101
103
  resizing: sized.resizing
102
104
  } : {},
105
+ ...fill ? { style: { width: "100%" } } : {},
103
106
  children: [
104
- resize && !collapsed && /* @__PURE__ */ jsx("div", {
107
+ resizable && !collapsed && /* @__PURE__ */ jsx("div", {
105
108
  role: "separator",
106
109
  tabIndex: 0,
107
110
  "aria-orientation": "vertical",
@@ -13,6 +13,17 @@ export declare function SidebarPanel({ collapsed, width, railWidth, resizing, la
13
13
  style?: CSSProperties;
14
14
  children: ReactNode;
15
15
  }): import("react").JSX.Element;
16
+ /** The list over the content of a layout too narrow to keep it beside the
17
+ * content. Place it in a positioned container: the drawer covers that
18
+ * container, not the window, so an embedded chat keeps to its own panel.
19
+ * Tapping outside or Escape closes it, and focus returns where it was. */
20
+ export declare function SidebarDrawer({ open, onClose, label, closeLabel, children, }: {
21
+ open: boolean;
22
+ onClose: () => void;
23
+ label: string;
24
+ closeLabel: string;
25
+ children: ReactNode;
26
+ }): import("react").JSX.Element;
16
27
  export declare function SidebarToggle({ collapsed, onToggle, openLabel, closeLabel, className, }: {
17
28
  collapsed: boolean;
18
29
  onToggle: () => void;
@@ -3,7 +3,7 @@ import { ConfirmModal } from "../../components/ConfirmModal.js";
3
3
  import { focusOnMount } from "../../components/focus.js";
4
4
  import { MoreButton, rowContextMenuProps, useRowMenuActive } from "../../list/RowContextMenu.js";
5
5
  import cx from "classnames";
6
- import { useState } from "react";
6
+ import { useEffect, useRef, useState } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/ai/ui/sidebar.tsx
9
9
  var SIDEBAR_RAIL_WIDTH_PX = 48;
@@ -22,6 +22,41 @@ function SidebarPanel({ collapsed, width = 256, railWidth = SIDEBAR_RAIL_WIDTH_P
22
22
  children
23
23
  });
24
24
  }
25
+ /** The list over the content of a layout too narrow to keep it beside the
26
+ * content. Place it in a positioned container: the drawer covers that
27
+ * container, not the window, so an embedded chat keeps to its own panel.
28
+ * Tapping outside or Escape closes it, and focus returns where it was. */
29
+ function SidebarDrawer({ open, onClose, label, closeLabel, children }) {
30
+ const panelRef = useRef(null);
31
+ useEffect(() => {
32
+ if (!open) return;
33
+ const returnTo = document.activeElement instanceof HTMLElement ? document.activeElement : null;
34
+ panelRef.current?.focus();
35
+ return () => returnTo?.focus();
36
+ }, [open]);
37
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
38
+ type: "button",
39
+ tabIndex: -1,
40
+ "aria-label": closeLabel,
41
+ onClick: onClose,
42
+ className: cx("absolute inset-0 z-20 bg-black/40 transition-opacity duration-300", open ? "opacity-100" : "pointer-events-none opacity-0")
43
+ }), /* @__PURE__ */ jsx("div", {
44
+ ref: panelRef,
45
+ role: "dialog",
46
+ "aria-modal": "true",
47
+ "aria-label": label,
48
+ tabIndex: -1,
49
+ inert: !open,
50
+ onKeyDown: (e) => {
51
+ if (e.key === "Escape") {
52
+ e.stopPropagation();
53
+ onClose();
54
+ }
55
+ },
56
+ className: cx("absolute inset-y-0 left-0 z-30 flex w-[min(20rem,85%)] shadow-xl transition-transform duration-300 ease-in-out focus:outline-none", open ? "translate-x-0" : "-translate-x-full"),
57
+ children
58
+ })] });
59
+ }
25
60
  function SidebarToggle({ collapsed, onToggle, openLabel, closeLabel, className }) {
26
61
  const label = collapsed ? openLabel : closeLabel;
27
62
  return /* @__PURE__ */ jsx("button", {
@@ -147,4 +182,4 @@ function useRowDialogs({ strings, nameOf, onDelete, onRename }) {
147
182
  };
148
183
  }
149
184
  //#endregion
150
- export { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };
185
+ export { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };