@kortix/ui 0.14.14 → 0.14.15

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.
Files changed (37) hide show
  1. package/dist/customize/agents/agent-tabs.d.ts +1 -0
  2. package/dist/customize/agents/agent-tabs.js +1 -0
  3. package/dist/customize/shared/capability-library.js +7 -2
  4. package/dist/files/drives/drive-storage.d.ts +47 -0
  5. package/dist/files/drives/drive-storage.js +144 -0
  6. package/dist/files/drives/session-drives-chip.js +20 -2
  7. package/dist/files/index.d.ts +1 -0
  8. package/dist/files/index.js +1 -0
  9. package/dist/files/project/components/drive-explorer.js +13 -5
  10. package/dist/files/project/components/drive-upload-tray.js +1 -1
  11. package/dist/files/project/upload-batch.d.ts +3 -1
  12. package/dist/files/project/upload-batch.js +5 -2
  13. package/dist/i18n/en.d.ts +32 -12
  14. package/dist/i18n/en.js +33 -13
  15. package/dist/internal/chrome/use-chrome-offered.d.ts +14 -5
  16. package/dist/internal/chrome/use-chrome-offered.js +15 -6
  17. package/dist/session/composer/context-usage.d.ts +2 -0
  18. package/dist/session/composer/context-usage.js +7 -0
  19. package/dist/session/header/compact-modal.js +5 -2
  20. package/dist/session/header/session-site-header.js +5 -2
  21. package/dist/session/thread/transcript.js +2 -2
  22. package/dist/session/tool/tools/kortix-tool-card.js +1 -1
  23. package/dist/session/turn/burst-step-body.js +2 -2
  24. package/dist/session/turn/compaction-card.d.ts +23 -1
  25. package/dist/session/turn/compaction-card.js +20 -8
  26. package/dist/styles.css +1 -1
  27. package/dist/workspace/sidebar/project-nav-items.d.ts +0 -11
  28. package/dist/workspace/sidebar/project-nav-items.js +10 -77
  29. package/dist/workspace/sidebar/project-sidebar.js +4 -3
  30. package/package.json +3 -7
  31. package/skills/kortix-ui/references/modules.md +1 -1
  32. package/dist/internal/stores/sidebar-nav-store.d.ts +0 -78
  33. package/dist/internal/stores/sidebar-nav-store.js +0 -125
  34. package/dist/workspace/sidebar/customize-sidebar-modal.d.ts +0 -9
  35. package/dist/workspace/sidebar/customize-sidebar-modal.js +0 -198
  36. package/dist/workspace/sidebar/nav-row-meta.d.ts +0 -14
  37. package/dist/workspace/sidebar/nav-row-meta.js +0 -94
@@ -1,14 +1,3 @@
1
- /**
2
- * The customizable nav rows under New session, in the order and with the
3
- * visibility chosen in "Customize sidebar" (`customize-sidebar-modal.tsx`).
4
- * Each row still applies its own flag and permission gate. More (rows set to
5
- * "Show in More", plus Customize sidebar) is last. Spaces are the channel
6
- * sidebar under the `project_channels` flag (project-spaces-nav.tsx).
7
- *
8
- * The layout lives in localStorage, which the server cannot read. The first
9
- * client render therefore uses the defaults, the same as the server HTML, and
10
- * the saved layout applies right after hydration.
11
- */
12
1
  export declare function ProjectNavItems({ projectId }: {
13
2
  projectId: string;
14
3
  }): import("react").JSX.Element;
@@ -14,86 +14,21 @@ import { Button } from '../../primitives/button.js';
14
14
  import { ButtonGroup } from '../../primitives/button-group.js';
15
15
  import Hint from '../../primitives/hint.js';
16
16
  import { cn } from '../../lib/utils.js';
17
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../../primitives/dropdown-menu.js';
18
17
  import { SidebarMenuButton, SidebarMenuItem, useSidebar } from '../../primitives/sidebar.js';
19
- import { SidebarGlyph } from '../../icons/sidebar-toggle.js';
20
- import { useNavRowMeta } from './nav-row-meta.js';
21
18
  import { useIsMobile } from '../../internal/hooks/index.js';
22
19
  import { useTranslations } from '../../i18n/index.js';
23
- import { SIDEBAR_NAV_DEFAULTS, openCustomizeSidebar, useSidebarNavStore, } from '../../internal/stores/sidebar-nav-store.js';
24
- import { CaretRightIcon, DotsThreeOutlineIcon, ListIcon, PlusIcon } from '@phosphor-icons/react';
20
+ import { CaretRightIcon, ListIcon, PlusIcon } from '@phosphor-icons/react';
25
21
  import { UiLink as Link, useUiPathname } from '../../providers/ui-provider.js';
26
- import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
27
- const subscribeNoop = () => () => { };
28
- /** Time to cross the gap between the More row and its menu before it closes. */
29
- const HOVER_CLOSE_DELAY_MS = 150;
22
+ import { useEffect, useState } from 'react';
30
23
  /**
31
- * The customizable nav rows under New session, in the order and with the
32
- * visibility chosen in "Customize sidebar" (`customize-sidebar-modal.tsx`).
33
- * Each row still applies its own flag and permission gate. More (rows set to
34
- * "Show in More", plus Customize sidebar) is last. Spaces are the channel
35
- * sidebar under the `project_channels` flag (project-spaces-nav.tsx).
36
- *
37
- * The layout lives in localStorage, which the server cannot read. The first
38
- * client render therefore uses the defaults, the same as the server HTML, and
39
- * the saved layout applies right after hydration.
24
+ * The nav rows under New session, in one fixed order. Each row applies its
25
+ * own flag and permission gate and renders nothing when the project does not
26
+ * offer it. Spaces are the channel sidebar under the `project_channels` flag
27
+ * (project-spaces-nav.tsx).
40
28
  */
29
+ const NAV_ROWS = ['customize', 'files', 'apps', 'rooms', 'goals', 'artifacts'];
41
30
  export function ProjectNavItems({ projectId }) {
42
- const saved = useSidebarNavStore((s) => s.items);
43
- const hydrated = useSyncExternalStore(subscribeNoop, () => true, () => false);
44
- const items = hydrated ? saved : SIDEBAR_NAV_DEFAULTS;
45
- return (_jsxs(_Fragment, { children: [items.map((entry) => (_jsx(NavItem, { entry: entry, projectId: projectId }, entry.id))), _jsx(MoreNavItem, { projectId: projectId, items: items })] }));
46
- }
47
- /**
48
- * The last nav row. Its menu holds every row set to "Show in More", so hiding
49
- * a row never makes its page unreachable, and then the one pointer path into
50
- * "Customize sidebar". Absent when the project offers the caller no row.
51
- */
52
- function MoreNavItem({ projectId, items, }) {
53
- const t = useTranslations('sidebar');
54
- const meta = useNavRowMeta(projectId);
55
- const isMobile = useIsMobile();
56
- const { setOpenMobile } = useSidebar();
57
- const [open, setOpen] = useState(false);
58
- const closeTimer = useRef(undefined);
59
- const hidden = items.filter((e) => e.visibility === 'never' && meta[e.id]);
60
- const closeSheet = () => {
61
- if (isMobile)
62
- setOpenMobile(false);
63
- };
64
- // A mouse opens the menu on hover. Touch and keyboard keep the click and
65
- // Enter/Space toggle from Radix.
66
- const cancelClose = () => clearTimeout(closeTimer.current);
67
- const hoverOpen = (e) => {
68
- if (e.pointerType !== 'mouse')
69
- return;
70
- cancelClose();
71
- setOpen(true);
72
- };
73
- const hoverClose = (e) => {
74
- if (e.pointerType !== 'mouse')
75
- return;
76
- cancelClose();
77
- closeTimer.current = setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS);
78
- };
79
- useEffect(() => cancelClose, []);
80
- // No row this project offers the caller: nothing to arrange and nothing
81
- // hidden to reach, so the row would open a menu with nothing in it.
82
- if (Object.keys(meta).length === 0)
83
- return null;
84
- return (_jsx(SidebarMenuItem, { children: _jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, modal: false, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(SidebarMenuButton, { tooltip: t('more'), className: "group/menu-button text-sidebar-foreground relative", onPointerEnter: hoverOpen, onPointerLeave: hoverClose,
85
- // Hover already opened it: stop the Radix pointerdown toggle from
86
- // closing the menu when the mouse then clicks the row.
87
- onPointerDown: (e) => {
88
- if (e.pointerType === 'mouse' && open)
89
- e.preventDefault();
90
- }, children: [_jsx("span", { className: "shrink-0", children: _jsx(DotsThreeOutlineIcon, { weight: "fill" }) }), _jsx("span", { className: "truncate", children: t('more') })] }) }), _jsxs(DropdownMenuContent, { align: "start", side: "right", sideOffset: 8, className: "w-[15rem]", onPointerEnter: hoverOpen, onPointerLeave: hoverClose, children: [hidden.map((entry) => {
91
- const row = meta[entry.id];
92
- return (_jsx(DropdownMenuItem, { asChild: true, size: "sm", children: _jsxs(Link, { href: row.href, prefetch: false, onClick: closeSheet, children: [row.icon, row.label] }) }, entry.id));
93
- }), hidden.length > 0 ? _jsx(DropdownMenuSeparator, {}) : null, _jsxs(DropdownMenuItem, { size: "sm", onSelect: () => {
94
- setOpen(false);
95
- requestAnimationFrame(openCustomizeSidebar);
96
- }, children: [_jsx(SidebarGlyph, {}), t('customizeSidebar')] })] })] }) }));
31
+ return (_jsx(_Fragment, { children: NAV_ROWS.map((id) => (_jsx(NavItem, { id: id, projectId: projectId }, id))) }));
97
32
  }
98
33
  /**
99
34
  * Customize, for the people who may configure the project. Most configuring
@@ -133,10 +68,8 @@ function CustomizeNavEntry({ projectId }) {
133
68
  openNewAgent();
134
69
  }, children: _jsx(PlusIcon, { className: "size-4" }) }) })) : null, _jsx(Hint, { side: "top", label: tTabs('allAgents'), children: _jsx(Button, { variant: "secondary", size: "icon-xs", asChild: true, children: _jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: false, "aria-label": tTabs('allAgents'), onClick: close, children: _jsx(ListIcon, { className: "size-4" }) }) }) })] })) : null] }), open ? _jsx(ProjectCapabilityNav, { projectId: projectId }) : null] }));
135
70
  }
136
- function NavItem({ entry, projectId }) {
137
- if (entry.visibility === 'never')
138
- return null;
139
- switch (entry.id) {
71
+ function NavItem({ id, projectId }) {
72
+ switch (id) {
140
73
  case 'customize':
141
74
  return _jsx(CustomizeNavEntry, { projectId: projectId });
142
75
  case 'apps':
@@ -13,7 +13,6 @@ import { ProjectComputerNavItem } from './footer/project-computer-nav.js';
13
13
  import { ProjectManifestIssuesAlert } from './footer/project-manifest-issues-alert.js';
14
14
  import { ProjectSandboxAlert } from './footer/project-sandbox-alert.js';
15
15
  import { ProjectRepositoryDisconnectedAlert } from '../repository-disconnected.js';
16
- import { CustomizeSidebarModal } from './customize-sidebar-modal.js';
17
16
  import { NewAgentModalProvider } from './new-agent-modal-provider.js';
18
17
  import { ProjectMemoryNavItem } from './footer/project-memory-nav.js';
19
18
  import { ProjectNavItems } from './project-nav-items.js';
@@ -29,6 +28,7 @@ import { useFeatureFlag } from '@kortix/sdk/react';
29
28
  import { AppsModal } from '../../internal/apps-and-extensions/apps-modal.js';
30
29
  import { useAppsModalStore } from '../../internal/stores/apps-modal-store.js';
31
30
  import { ProjectChromeNavItem } from './footer/project-chrome-nav.js';
31
+ import { CHROME_UI_ENABLED } from '../../internal/chrome/use-chrome-offered.js';
32
32
  import { MagnifyingGlassIcon, NavigationArrowIcon, } from '@phosphor-icons/react';
33
33
  import { UiLink as Link } from '../../providers/ui-provider.js';
34
34
  import { useCallback, useEffect, useRef, useState } from 'react';
@@ -52,7 +52,8 @@ export function ProjectSidebar({ projectId, menu, onOpenSearch, }) {
52
52
  const accountId = useBillingAccountId();
53
53
  // Channels replace New session and the session list (fail-closed: off until the project answers).
54
54
  const channels = useFeatureFlag(projectId, 'project_channels').enabled;
55
- const chrome = useFeatureFlag(projectId, 'chrome').enabled;
55
+ // The project flag and the web app's master switch (`CHROME_UI_ENABLED`) must both be on.
56
+ const chrome = useFeatureFlag(projectId, 'chrome').enabled && CHROME_UI_ENABLED;
56
57
  // Open the project composer without creating a durable session.
57
58
  const newSession = useNewProjectSession(projectId);
58
59
  const creatingSession = useIsCreatingProjectSession(projectId);
@@ -111,5 +112,5 @@ export function ProjectSidebar({ projectId, menu, onOpenSearch, }) {
111
112
  // box clears the 44px floor with the vertical-only extension —
112
113
  // vertical-only because this titlebar row packs several
113
114
  // controls side by side (KRTX-2004).
114
- className: "shrink-0 hit-area-y-3", children: _jsx(MagnifyingGlassIcon, { className: "size-4" }) }) })), isMobile && (_jsx(Button, { type: "button", "aria-label": tCommon('close'), variant: "ghost", size: "icon", onClick: () => setOpenMobile(false), className: "shrink-0 hit-area-y-3", children: _jsx(Close, {}) })), !isMobile && !peek && (_jsx(Hint, { side: "bottom", label: _jsxs("span", { className: "flex items-center gap-1.5", children: [isExpanded ? t('collapse') : t('pin'), _jsxs(KbdGroup, { children: [_jsx(Kbd, { className: "font-mono", children: modSymbol }), _jsx(Kbd, { className: "font-mono", children: "B" })] })] }), children: _jsx(Button, { type: "button", "aria-label": isExpanded ? t('collapse') : t('pin'), variant: "ghost", size: "icon", onClick: toggleSidebar, className: "shrink-0", children: _jsx(PanelLeft, { className: "cn-rtl-flip" }) }) }))] })] }) }), _jsx(SidebarContent, { className: "relative min-h-0 flex-1 [scrollbar-width:'none'] overflow-hidden [-ms-overflow-style:'none'] [&::-webkit-scrollbar]:hidden [&_[data-sidebar=menu-button]]:pl-2", children: _jsxs("div", { className: "flex h-full min-h-0 flex-col space-y-2", children: [_jsx(SidebarGroup, { className: "py-0", children: _jsxs(SidebarMenu, { children: [channels ? null : (_jsx(SidebarMenuItem, { children: creatingSession ? (_jsx(SidebarMenuButton, { disabled: true, "aria-busy": true, className: "group/menu-button text-sidebar-foreground relative", children: newSessionRowBody })) : (_jsx(SidebarMenuButton, { asChild: true, className: "group/menu-button text-sidebar-foreground relative", children: _jsx(Link, { href: uiRoutes().project(projectId), prefetch: true, onClick: handleNewSessionClick, children: newSessionRowBody }) })) })), channels ? _jsx(ProjectMemoryNavItem, { projectId: projectId }) : null, _jsx(ProjectNavItems, { projectId: projectId })] }) }), _jsx(SidebarGroup, { className: "min-h-0 flex-1 flex-col py-0", ref: sessionsGroupRef, children: channels ? (_jsx(ProjectSpacesNav, { projectId: projectId })) : (_jsx(ProjectSessionList, { projectId: projectId })) }), _jsx(SidebarGroup, { className: "mt-auto", children: _jsxs(SidebarMenu, { className: "gap-1", children: [_jsx(ProjectRepositoryDisconnectedAlert, { projectId: projectId }), _jsx(ProjectSandboxAlert, { projectId: projectId }), _jsx(ProjectManifestIssuesAlert, { projectId: projectId }), _jsx(SidebarBalanceWarning, { accountId: accountId }), _jsx("li", { className: "not-has-[li]:hidden", children: _jsxs(SidebarMenu, { className: "gap-px [&>li:not(:first-child)>[data-sidebar=menu-button]]:rounded-t-sm [&>li:not(:last-child)>[data-sidebar=menu-button]]:rounded-b-sm", children: [_jsx(ProjectMcpNavItem, { onOpenConnect: () => setConnectMcpOpen(true) }), _jsx(ProjectComputerNavItem, { projectId: projectId, onOpenConnect: () => setComputerConnectOpen(true) }), chrome ? (_jsx(ProjectChromeNavItem, { onOpenConnect: () => useAppsModalStore.getState().setOpen(true) })) : null] }) }), _jsx(SidebarUpgradeButton, { accountId: accountId })] }) })] }) }), _jsx(SidebarRail, { "aria-label": t('resize'), title: t('resizeHelp') })] }), _jsx(ComputerConnectModal, { projectId: projectId, open: computerConnectOpen, onOpenChange: setComputerConnectOpen }), _jsx(AppsModal, { chrome: chrome, onOpenComputer: () => setComputerConnectOpen(true), onOpenMcp: () => setConnectMcpOpen(true) }), _jsx(ConnectMcpModal, { open: connectMcpOpen, onOpenChange: setConnectMcpOpen }), _jsx(CustomizeSidebarModal, { projectId: projectId })] }));
115
+ className: "shrink-0 hit-area-y-3", children: _jsx(MagnifyingGlassIcon, { className: "size-4" }) }) })), isMobile && (_jsx(Button, { type: "button", "aria-label": tCommon('close'), variant: "ghost", size: "icon", onClick: () => setOpenMobile(false), className: "shrink-0 hit-area-y-3", children: _jsx(Close, {}) })), !isMobile && !peek && (_jsx(Hint, { side: "bottom", label: _jsxs("span", { className: "flex items-center gap-1.5", children: [isExpanded ? t('collapse') : t('pin'), _jsxs(KbdGroup, { children: [_jsx(Kbd, { className: "font-mono", children: modSymbol }), _jsx(Kbd, { className: "font-mono", children: "B" })] })] }), children: _jsx(Button, { type: "button", "aria-label": isExpanded ? t('collapse') : t('pin'), variant: "ghost", size: "icon", onClick: toggleSidebar, className: "shrink-0", children: _jsx(PanelLeft, { className: "cn-rtl-flip" }) }) }))] })] }) }), _jsx(SidebarContent, { className: "relative min-h-0 flex-1 [scrollbar-width:'none'] overflow-hidden [-ms-overflow-style:'none'] [&::-webkit-scrollbar]:hidden [&_[data-sidebar=menu-button]]:pl-2", children: _jsxs("div", { className: "flex h-full min-h-0 flex-col space-y-2", children: [_jsx(SidebarGroup, { className: "py-0", children: _jsxs(SidebarMenu, { children: [channels ? null : (_jsx(SidebarMenuItem, { children: creatingSession ? (_jsx(SidebarMenuButton, { disabled: true, "aria-busy": true, className: "group/menu-button text-sidebar-foreground relative", children: newSessionRowBody })) : (_jsx(SidebarMenuButton, { asChild: true, className: "group/menu-button text-sidebar-foreground relative", children: _jsx(Link, { href: uiRoutes().project(projectId), prefetch: true, onClick: handleNewSessionClick, children: newSessionRowBody }) })) })), channels ? _jsx(ProjectMemoryNavItem, { projectId: projectId }) : null, _jsx(ProjectNavItems, { projectId: projectId })] }) }), _jsx(SidebarGroup, { className: "min-h-0 flex-1 flex-col py-0", ref: sessionsGroupRef, children: channels ? (_jsx(ProjectSpacesNav, { projectId: projectId })) : (_jsx(ProjectSessionList, { projectId: projectId })) }), _jsx(SidebarGroup, { className: "mt-auto", children: _jsxs(SidebarMenu, { className: "gap-1", children: [_jsx(ProjectRepositoryDisconnectedAlert, { projectId: projectId }), _jsx(ProjectSandboxAlert, { projectId: projectId }), _jsx(ProjectManifestIssuesAlert, { projectId: projectId }), _jsx(SidebarBalanceWarning, { accountId: accountId }), _jsx("li", { className: "not-has-[li]:hidden", children: _jsxs(SidebarMenu, { className: "gap-px [&>li:not(:first-child)>[data-sidebar=menu-button]]:rounded-t-sm [&>li:not(:last-child)>[data-sidebar=menu-button]]:rounded-b-sm", children: [_jsx(ProjectMcpNavItem, { onOpenConnect: () => setConnectMcpOpen(true) }), _jsx(ProjectComputerNavItem, { projectId: projectId, onOpenConnect: () => setComputerConnectOpen(true) }), chrome ? (_jsx(ProjectChromeNavItem, { onOpenConnect: () => useAppsModalStore.getState().setOpen(true) })) : null] }) }), _jsx(SidebarUpgradeButton, { accountId: accountId })] }) })] }) }), _jsx(SidebarRail, { "aria-label": t('resize'), title: t('resizeHelp') })] }), _jsx(ComputerConnectModal, { projectId: projectId, open: computerConnectOpen, onOpenChange: setComputerConnectOpen }), _jsx(AppsModal, { chrome: chrome, onOpenComputer: () => setComputerConnectOpen(true), onOpenMcp: () => setConnectMcpOpen(true) }), _jsx(ConnectMcpModal, { open: connectMcpOpen, onOpenChange: setConnectMcpOpen })] }));
115
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/ui",
3
- "version": "0.14.14",
3
+ "version": "0.14.15",
4
4
  "description": "Kortix UI: the shared React component library for apps/web, Kortix Apps and SDK hosts. Tailwind v4 classes, themed by CSS variables.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -1367,10 +1367,6 @@
1367
1367
  "types": "./dist/internal/stores/settings-panel-store.d.ts",
1368
1368
  "import": "./dist/internal/stores/settings-panel-store.js"
1369
1369
  },
1370
- "./internal/stores/sidebar-nav-store": {
1371
- "types": "./dist/internal/stores/sidebar-nav-store.d.ts",
1372
- "import": "./dist/internal/stores/sidebar-nav-store.js"
1373
- },
1374
1370
  "./internal/stores/sound-store": {
1375
1371
  "types": "./dist/internal/stores/sound-store.d.ts",
1376
1372
  "import": "./dist/internal/stores/sound-store.js"
@@ -2189,7 +2185,7 @@
2189
2185
  "@extend-ai/react-docx": "^0.7.5",
2190
2186
  "@extend-ai/react-xlsx": "0.13.4",
2191
2187
  "@glideapps/glide-data-grid": "6.0.4-alpha24",
2192
- "@kortix/llm-catalog": "0.14.14",
2188
+ "@kortix/llm-catalog": "0.14.15",
2193
2189
  "@lezer/highlight": "1.2.3",
2194
2190
  "@paper-design/shaders-react": "^0.0.76",
2195
2191
  "@phosphor-icons/react": "2.1.10",
@@ -2288,7 +2284,7 @@
2288
2284
  "yaml": "^2.8.2"
2289
2285
  },
2290
2286
  "peerDependencies": {
2291
- "@kortix/sdk": "^0.14.14",
2287
+ "@kortix/sdk": "^0.14.15",
2292
2288
  "@tanstack/react-query": "^5.75.2",
2293
2289
  "@types/react": ">=18",
2294
2290
  "react": ">=19",
@@ -179,7 +179,7 @@ export function PreviewHost() {
179
179
  }
180
180
  ```
181
181
 
182
- Names: `saveDriveFile`, `useDismissDriveConflict`, `useDriveAvailability`, `useDriveConflicts`, `useDriveFolder`, `useFolderAccess`, `useFolderPrincipals`, `useInvalidateDrives`, `useProjectDrive`, `useShareFolder`, `useUnshareFolder`, `FilesStoreProvider`, `getFileIcon`, `FilePreviewHost`, `ChangeRequestDetailDialog`, `DiffRenderer`, `DriveExplorer`, `DriveUploadTray`, `FileExplorerPage`, `FileThumbnail`, `ProjectFilesProvider`, `useProjectContext`, `FileExplorerSourceProvider`, `ExplorerQueryResult`, `FileExplorerSource`, `gitRefExplorerSource`, `useChangeRequestRecovery`, `changeRequestKeys`, `openChangeRequestListQuery`, `useChangeRequestDiff`, `useChangeRequestMergePreview`, `useCloseChangeRequest`, `useMergeChangeRequest`, `useRequestChangesOnChangeRequest`, `useSelectedVersion`, `workspaceFileSource`, `useWorkspaceSearch`, `SandboxFileExplorer`, `CodeEditor` (the CodeMirror editor, loaded as its own chunk on first render)
182
+ Names: `saveDriveFile`, `useDismissDriveConflict`, `useDriveAvailability`, `useDriveConflicts`, `useDriveFolder`, `useFolderAccess`, `useFolderPrincipals`, `useInvalidateDrives`, `useProjectDrive`, `useShareFolder`, `useUnshareFolder`, `FilesStoreProvider`, `getFileIcon`, `FilePreviewHost`, `ChangeRequestDetailDialog`, `DiffRenderer`, `DriveExplorer`, `DriveUploadTray`, `FileExplorerPage`, `FileThumbnail`, `ProjectFilesProvider`, `useProjectContext`, `FileExplorerSourceProvider`, `ExplorerQueryResult`, `FileExplorerSource`, `gitRefExplorerSource`, `useChangeRequestRecovery`, `changeRequestKeys`, `openChangeRequestListQuery`, `useChangeRequestDiff`, `useChangeRequestMergePreview`, `useCloseChangeRequest`, `useMergeChangeRequest`, `useRequestChangesOnChangeRequest`, `useSelectedVersion`, `workspaceFileSource`, `useWorkspaceSearch`, `SandboxFileExplorer`, `CodeEditor` (the CodeMirror editor, loaded as its own chunk on first render). Files storage: `DriveStorageSummary` (a project's storage against its limit, with folder-mount warnings), `StorageMeter`, `StorageNotice`, `StorageTone`, `storageTone`, `formatStorageBytes`, `useDriveUsage`, `useDriveLimitMessage` (the readable message of a storage refusal), `useFolderMountNotice`
183
183
 
184
184
  ## `@kortix/ui/file-viewer`
185
185
 
@@ -1,78 +0,0 @@
1
- /** @internal Kortix apps only. No semver: it may change in any release. */
2
- /**
3
- * The project sidebar's navigation rows, in the order and with the visibility
4
- * the person chose in "Customize sidebar". Per account, across projects: the
5
- * sidebar is the same panel in every project, so one layout serves all of
6
- * them. localStorage is the instant first paint; the account's
7
- * `user_metadata.sidebar_nav` is the source of truth
8
- * (`customize-sidebar-modal.tsx` syncs it). A row a project does not offer (a flag is off, a permission is denied)
9
- * keeps its place here and simply does not render.
10
- */
11
- export declare const SIDEBAR_NAV_ITEM_IDS: readonly ["customize", "apps", "files", "rooms", "goals", "artifacts"];
12
- export type SidebarNavItemId = (typeof SIDEBAR_NAV_ITEM_IDS)[number];
13
- /**
14
- * `never` moves the row into the More menu (`project-nav-items.tsx`), so its
15
- * page stays one click away.
16
- */
17
- export type SidebarNavVisibility = 'always' | 'never';
18
- export interface SidebarNavEntry {
19
- id: SidebarNavItemId;
20
- visibility: SidebarNavVisibility;
21
- }
22
- /** The layout before anyone customizes it. */
23
- export declare const SIDEBAR_NAV_DEFAULTS: readonly SidebarNavEntry[];
24
- /**
25
- * Saved layout → a complete, valid one. Unknown ids and duplicates drop, a bad
26
- * visibility falls back to the default, and a row added after the layout was
27
- * saved appears at its default position.
28
- */
29
- export declare function normalizeSidebarNav(saved: unknown): SidebarNavEntry[];
30
- /**
31
- * v1 → v2 (2026-10-20): Rooms moved into the More menu by default. A v1
32
- * layout persisted the old default (`always`) on first load, before anyone
33
- * chose it, so the migration moves Rooms into More. A layout the account saved
34
- * remotely is a deliberate choice and is not migrated.
35
- */
36
- export declare function migrateSidebarNav(persisted: unknown, version: number): {
37
- items: SidebarNavEntry[];
38
- };
39
- /** The `user_metadata` key that carries the layout to every device the
40
- * account signs in on (web and desktop). */
41
- export declare const SIDEBAR_NAV_METADATA_KEY = "sidebar_nav";
42
- export declare const sidebarNavKey: (items: readonly SidebarNavEntry[]) => string;
43
- /**
44
- * The account's saved layout wins. With none saved, keep the local one and
45
- * push it once if the person customized it before the layout synced.
46
- */
47
- export declare function resolveSidebarNav(remote: unknown, local: readonly SidebarNavEntry[]): {
48
- items: SidebarNavEntry[];
49
- push: boolean;
50
- };
51
- /** Moves one row up (`-1`) or down (`+1`); the keyboard path of the drag. */
52
- export declare function moveSidebarNavItem(items: readonly SidebarNavEntry[], id: SidebarNavItemId, delta: -1 | 1): SidebarNavEntry[];
53
- interface SidebarNavState {
54
- items: SidebarNavEntry[];
55
- /** The Customize sidebar modal. Not persisted. */
56
- customizeOpen: boolean;
57
- /** Replaces the whole layout; normalized, so a partial or bad list is safe. */
58
- setItems: (items: SidebarNavEntry[]) => void;
59
- move: (id: SidebarNavItemId, delta: -1 | 1) => void;
60
- setVisibility: (id: SidebarNavItemId, visibility: SidebarNavVisibility) => void;
61
- reset: () => void;
62
- setCustomizeOpen: (open: boolean) => void;
63
- }
64
- export declare const useSidebarNavStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<SidebarNavState>, "setState" | "persist"> & {
65
- setState(partial: SidebarNavState | Partial<SidebarNavState> | ((state: SidebarNavState) => SidebarNavState | Partial<SidebarNavState>), replace?: false | undefined): unknown;
66
- setState(state: SidebarNavState | ((state: SidebarNavState) => SidebarNavState), replace: true): unknown;
67
- persist: {
68
- setOptions: (options: Partial<import("zustand/middleware").PersistOptions<SidebarNavState, unknown, unknown>>) => void;
69
- clearStorage: () => void;
70
- rehydrate: () => Promise<void> | void;
71
- hasHydrated: () => boolean;
72
- onHydrate: (fn: (state: SidebarNavState) => void) => () => void;
73
- onFinishHydration: (fn: (state: SidebarNavState) => void) => () => void;
74
- getOptions: () => Partial<import("zustand/middleware").PersistOptions<SidebarNavState, unknown, unknown>>;
75
- };
76
- }>;
77
- export declare function openCustomizeSidebar(): void;
78
- export {};
@@ -1,125 +0,0 @@
1
- import { create } from 'zustand';
2
- import { persist } from 'zustand/middleware';
3
- import { createSafeJSONStorage } from '../lib/storage/managed-storage.js';
4
- import { registerPersistedStore, resetPersistedStore } from './persisted-store-registry.js';
5
- /**
6
- * The project sidebar's navigation rows, in the order and with the visibility
7
- * the person chose in "Customize sidebar". Per account, across projects: the
8
- * sidebar is the same panel in every project, so one layout serves all of
9
- * them. localStorage is the instant first paint; the account's
10
- * `user_metadata.sidebar_nav` is the source of truth
11
- * (`customize-sidebar-modal.tsx` syncs it). A row a project does not offer (a flag is off, a permission is denied)
12
- * keeps its place here and simply does not render.
13
- */
14
- // Review (2026-10-17) and Reminders (2026-10-19) left the sidebar for Customize
15
- // tabs. A saved layout that still names one drops the id in `normalizeSidebarNav`.
16
- export const SIDEBAR_NAV_ITEM_IDS = ['customize', 'apps', 'files', 'rooms', 'goals', 'artifacts'];
17
- /** The layout before anyone customizes it. */
18
- export const SIDEBAR_NAV_DEFAULTS = [
19
- { id: 'customize', visibility: 'always' },
20
- { id: 'files', visibility: 'always' },
21
- { id: 'apps', visibility: 'always' },
22
- // Rooms (2026-10-19, flag `agent_rooms`): chat rooms for sessions and people.
23
- // In the More menu until the person pins it (2026-10-20).
24
- { id: 'rooms', visibility: 'never' },
25
- // Goals (2026-10-21, flag `goals`): objectives agents own until verified done.
26
- { id: 'goals', visibility: 'always' },
27
- // Artifacts (2026-10-23, flag `artifacts`): published docs, slides, boards, dashboards.
28
- { id: 'artifacts', visibility: 'always' },
29
- ];
30
- function isItemId(value) {
31
- return SIDEBAR_NAV_ITEM_IDS.includes(value);
32
- }
33
- function isVisibility(value) {
34
- return value === 'always' || value === 'never';
35
- }
36
- /**
37
- * Saved layout → a complete, valid one. Unknown ids and duplicates drop, a bad
38
- * visibility falls back to the default, and a row added after the layout was
39
- * saved appears at its default position.
40
- */
41
- export function normalizeSidebarNav(saved) {
42
- const out = [];
43
- if (Array.isArray(saved)) {
44
- for (const entry of saved) {
45
- const id = entry?.id;
46
- if (!isItemId(id) || out.some((e) => e.id === id))
47
- continue;
48
- const fallback = SIDEBAR_NAV_DEFAULTS.find((d) => d.id === id).visibility;
49
- const visibility = entry.visibility;
50
- out.push({ id, visibility: isVisibility(visibility) ? visibility : fallback });
51
- }
52
- }
53
- SIDEBAR_NAV_DEFAULTS.forEach((entry, index) => {
54
- if (!out.some((e) => e.id === entry.id))
55
- out.splice(Math.min(index, out.length), 0, { ...entry });
56
- });
57
- return out;
58
- }
59
- /**
60
- * v1 → v2 (2026-10-20): Rooms moved into the More menu by default. A v1
61
- * layout persisted the old default (`always`) on first load, before anyone
62
- * chose it, so the migration moves Rooms into More. A layout the account saved
63
- * remotely is a deliberate choice and is not migrated.
64
- */
65
- export function migrateSidebarNav(persisted, version) {
66
- const items = normalizeSidebarNav(persisted?.items);
67
- if (version >= 2)
68
- return { items };
69
- return { items: items.map((e) => (e.id === 'rooms' ? { id: 'rooms', visibility: 'never' } : e)) };
70
- }
71
- /** The `user_metadata` key that carries the layout to every device the
72
- * account signs in on (web and desktop). */
73
- export const SIDEBAR_NAV_METADATA_KEY = 'sidebar_nav';
74
- export const sidebarNavKey = (items) => JSON.stringify(items);
75
- /**
76
- * The account's saved layout wins. With none saved, keep the local one and
77
- * push it once if the person customized it before the layout synced.
78
- */
79
- export function resolveSidebarNav(remote, local) {
80
- if (Array.isArray(remote))
81
- return { items: normalizeSidebarNav(remote), push: false };
82
- return {
83
- items: [...local],
84
- push: sidebarNavKey(local) !== sidebarNavKey(SIDEBAR_NAV_DEFAULTS),
85
- };
86
- }
87
- /** Moves one row up (`-1`) or down (`+1`); the keyboard path of the drag. */
88
- export function moveSidebarNavItem(items, id, delta) {
89
- const from = items.findIndex((e) => e.id === id);
90
- const to = from + delta;
91
- if (from === -1 || to < 0 || to >= items.length)
92
- return [...items];
93
- const next = [...items];
94
- const [moved] = next.splice(from, 1);
95
- next.splice(to, 0, moved);
96
- return next;
97
- }
98
- /** `kortix.` is an app prefix, so sign-out sweeps it
99
- * (`persisted-store-coverage.test.ts`). */
100
- const STORAGE_KEY = 'kortix.sidebarNav';
101
- export const useSidebarNavStore = create()(persist((set) => ({
102
- items: normalizeSidebarNav(null),
103
- customizeOpen: false,
104
- setItems: (items) => set({ items: normalizeSidebarNav(items) }),
105
- move: (id, delta) => set((state) => ({ items: moveSidebarNavItem(state.items, id, delta) })),
106
- setVisibility: (id, visibility) => set((state) => ({
107
- items: state.items.map((e) => e.id === id && isVisibility(visibility) ? { id, visibility } : e),
108
- })),
109
- reset: () => set({ items: normalizeSidebarNav(null) }),
110
- setCustomizeOpen: (customizeOpen) => set({ customizeOpen }),
111
- }), {
112
- name: STORAGE_KEY,
113
- storage: createSafeJSONStorage(),
114
- version: 2,
115
- migrate: (persisted, version) => migrateSidebarNav(persisted, version),
116
- partialize: (state) => ({ items: state.items }),
117
- merge: (persisted, current) => ({
118
- ...current,
119
- items: normalizeSidebarNav(persisted?.items),
120
- }),
121
- }));
122
- registerPersistedStore(STORAGE_KEY, () => resetPersistedStore(useSidebarNavStore));
123
- export function openCustomizeSidebar() {
124
- useSidebarNavStore.getState().setCustomizeOpen(true);
125
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * "Customize sidebar": the nav rows in two groups, "Sidebar" and "More". Where a row sits is the setting: drag it across the line to
3
- * move it under More, drag it back to show it. Changes apply as they are
4
- * made; there is no save step. Opened from the sidebar's More menu and
5
- * the command palette through `openCustomizeSidebar()`.
6
- */
7
- export declare function CustomizeSidebarModal({ projectId }: {
8
- projectId: string;
9
- }): import("react").JSX.Element;
@@ -1,198 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Button } from '../../primitives/button.js';
4
- import { Modal, ModalBody, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, } from '../../primitives/modal.js';
5
- import { useUiUpdateViewerMetadata, useUiViewer, } from '../../providers/ui-provider.js';
6
- import { useNavRowMeta } from './nav-row-meta.js';
7
- import { useDebounce } from '../../hooks/use-debounce.js';
8
- import { useTranslations } from '../../i18n/index.js';
9
- import { spring } from '../../lib/springs.js';
10
- import { cn } from '../../internal/lib/utils.js';
11
- import { SIDEBAR_NAV_METADATA_KEY, resolveSidebarNav, sidebarNavKey, useSidebarNavStore, } from '../../internal/stores/sidebar-nav-store.js';
12
- import { DotsSixVerticalIcon } from '@phosphor-icons/react';
13
- import { Reorder } from 'motion/react';
14
- import { memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
15
- /**
16
- * "Customize sidebar": the nav rows in two groups, "Sidebar" and "More". Where a row sits is the setting: drag it across the line to
17
- * move it under More, drag it back to show it. Changes apply as they are
18
- * made; there is no save step. Opened from the sidebar's More menu and
19
- * the command palette through `openCustomizeSidebar()`.
20
- */
21
- export function CustomizeSidebarModal({ projectId }) {
22
- const t = useTranslations('sidebar');
23
- const open = useSidebarNavStore((s) => s.customizeOpen);
24
- const setOpen = useSidebarNavStore((s) => s.setCustomizeOpen);
25
- const reset = useSidebarNavStore((s) => s.reset);
26
- const onOpenChange = useSidebarNavAccountSync(open, setOpen);
27
- return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsxs(ModalContent, { className: "sm:max-w-md", children: [_jsxs(ModalHeader, { children: [_jsx(ModalTitle, { children: t('customizeSidebar') }), _jsx(ModalDescription, { children: t('customizeSidebarDescription') })] }), _jsx(ModalBody, { children: _jsx(NavRowGroups, { projectId: projectId }) }), _jsx(ModalFooter, { children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: reset, children: t('resetSidebar') }) })] }) }));
28
- }
29
- function saveSidebarNav(save, items) {
30
- save?.({ [SIDEBAR_NAV_METADATA_KEY]: items }).catch((error) => {
31
- console.warn('[sidebar] Could not save the sidebar layout to the account:', error);
32
- });
33
- }
34
- /**
35
- * Keeps the layout on the account, so it survives sign-out (which sweeps
36
- * localStorage) and follows the person to the desktop app. The account copy
37
- * is applied when the user loads; an edit is saved once, when the modal
38
- * closes. Only an explicit close writes: the sign-out sweep also resets the
39
- * store, and a store subscription would save that reset over the layout.
40
- */
41
- function useSidebarNavAccountSync(open, setOpen) {
42
- const user = useUiViewer();
43
- const save = useUiUpdateViewerMetadata();
44
- const userId = user?.id;
45
- const remote = user?.metadata?.[SIDEBAR_NAV_METADATA_KEY];
46
- const remoteKey = JSON.stringify(remote ?? null);
47
- const openedWith = useRef(null);
48
- useEffect(() => {
49
- // An open modal holds edits the account does not have yet.
50
- if (!userId || useSidebarNavStore.getState().customizeOpen)
51
- return;
52
- const { items, push } = resolveSidebarNav(JSON.parse(remoteKey), useSidebarNavStore.getState().items);
53
- useSidebarNavStore.getState().setItems(items);
54
- if (push)
55
- saveSidebarNav(save, items);
56
- }, [userId, remoteKey, save]);
57
- // The modal opens through the store (`openCustomizeSidebar`), so the
58
- // snapshot follows `open`, not `onOpenChange`.
59
- useEffect(() => {
60
- if (open)
61
- openedWith.current = sidebarNavKey(useSidebarNavStore.getState().items);
62
- }, [open]);
63
- return useCallback((next) => {
64
- const { items } = useSidebarNavStore.getState();
65
- if (!next && openedWith.current !== null && openedWith.current !== sidebarNavKey(items)) {
66
- saveSidebarNav(save, items);
67
- }
68
- if (!next)
69
- openedWith.current = null;
70
- setOpen(next);
71
- }, [setOpen, save]);
72
- }
73
- /** The line between the two groups. A list value like any row, so a drag
74
- * across it is an ordinary reorder; it is never dragged itself. */
75
- const DIVIDER = '__more__';
76
- const { exit: _exit, ...reorderTransition } = spring.moderate;
77
- function NavRowGroups({ projectId }) {
78
- const t = useTranslations('sidebar');
79
- const meta = useNavRowMeta(projectId);
80
- const items = useSidebarNavStore((s) => s.items);
81
- const setItems = useSidebarNavStore((s) => s.setItems);
82
- const listRef = useRef(null);
83
- const [announcement, setAnnouncement] = useState('');
84
- const [dragging, setDragging] = useState(false);
85
- const hintId = useId();
86
- // Only the rows this project offers are listed; the rest keep their slot.
87
- const saved = useMemo(() => {
88
- const listed = items.filter((e) => meta[e.id]);
89
- return [
90
- ...listed.filter((e) => e.visibility !== 'never').map((e) => e.id),
91
- DIVIDER,
92
- ...listed.filter((e) => e.visibility === 'never').map((e) => e.id),
93
- ];
94
- }, [items, meta]);
95
- const savedKey = saved.join('|');
96
- // The drag edits a local draft, never the store. Every reorder frame used
97
- // to write the persisted store (a localStorage write) and re-render the
98
- // whole sidebar behind the modal; now a frame re-renders this list only.
99
- const [draft, setDraft] = useState(saved);
100
- const draftKey = draft.join('|');
101
- // Follow the store when it changes from outside (Reset). Adjusted during
102
- // render, React's pattern for state derived from a changed input.
103
- const [syncedKey, setSyncedKey] = useState(savedKey);
104
- if (syncedKey !== savedKey) {
105
- setSyncedKey(savedKey);
106
- if (draftKey !== savedKey)
107
- setDraft(saved);
108
- }
109
- const save = (list) => {
110
- const split = list.indexOf(DIVIDER);
111
- const placed = list
112
- .filter((v) => v !== DIVIDER)
113
- .map((id) => {
114
- if (list.indexOf(id) > split)
115
- return { id, visibility: 'never' };
116
- const was = items.find((e) => e.id === id).visibility;
117
- return { id, visibility: was === 'never' ? 'always' : was };
118
- });
119
- let i = 0;
120
- setItems(items.map((e) => (meta[e.id] ? placed[i++] : e)));
121
- };
122
- // Save once the order settles: after the drag ends and 200ms pass without
123
- // a change. `settledKey === draftKey` keeps a stale debounced value from
124
- // writing over a draft the store just reset.
125
- const { debouncedValue: settled } = useDebounce(draft, 200);
126
- const settledKey = settled.join('|');
127
- useEffect(() => {
128
- if (dragging || settledKey !== draftKey || draftKey === savedKey)
129
- return;
130
- save(settled);
131
- });
132
- // Closing the modal inside the 200ms window must not drop the last move.
133
- const flushRef = useRef(() => { });
134
- useLayoutEffect(() => {
135
- flushRef.current = () => {
136
- if (draftKey !== savedKey)
137
- save(draft);
138
- };
139
- });
140
- useEffect(() => () => flushRef.current(), []);
141
- // Stable across renders so memoized rows skip re-rendering on every frame.
142
- const draftRef = useRef(draft);
143
- useLayoutEffect(() => {
144
- draftRef.current = draft;
145
- });
146
- const moveByKey = useCallback((id, delta) => {
147
- const current = draftRef.current;
148
- const from = current.indexOf(id);
149
- const to = from + delta;
150
- if (to < 0 || to >= current.length)
151
- return;
152
- const next = [...current];
153
- [next[from], next[to]] = [next[to], next[from]];
154
- setDraft(next);
155
- const split = next.indexOf(DIVIDER);
156
- const at = next.indexOf(id);
157
- const inSidebar = at < split;
158
- setAnnouncement(t('reorderAnnouncement', {
159
- name: meta[id].label,
160
- group: inSidebar ? t('inSidebar') : t('underMore'),
161
- position: inSidebar ? at + 1 : at - split,
162
- total: inSidebar ? split : next.length - split - 1,
163
- }));
164
- // Reordering moves the DOM node, which can drop focus from the handle.
165
- requestAnimationFrame(() => listRef.current?.querySelector(`[data-grip="${id}"]`)?.focus());
166
- }, [t, meta]);
167
- const dividerAt = draft.indexOf(DIVIDER);
168
- const hiddenCount = draft.length - dividerAt - 1;
169
- return (_jsxs("section", { className: "space-y-2", children: [_jsx(GroupHeading, { label: t('inSidebar'), count: dividerAt }), _jsx("p", { id: hintId, className: "sr-only", children: t('reorderHint') }), _jsx(Reorder.Group, { ref: listRef, as: "ul", axis: "y", values: draft, onReorder: setDraft, className: "flex flex-col gap-px", children: draft.map((value, index) => value === DIVIDER ? (_jsxs(Reorder.Item, { as: "li", value: DIVIDER, dragListener: false, transition: reorderTransition, className: "list-none", children: [dividerAt === 0 ? _jsx(EmptyDrop, { label: t('sidebarEmpty') }) : null, _jsx("div", { className: "pt-4 pb-2", children: _jsx(GroupHeading, { label: t('underMore'), count: hiddenCount }) }), hiddenCount === 0 ? _jsx(EmptyDrop, { label: t('moreEmpty') }) : null] }, DIVIDER)) : (_jsx(NavRow, { id: value, meta: meta[value], inMore: index > dividerAt, first: index === 0 || draft[index - 1] === DIVIDER, last: index === draft.length - 1 || draft[index + 1] === DIVIDER, hintId: hintId, onMove: moveByKey, onDragChange: setDragging }, value))) }), _jsx("p", { className: "text-muted-foreground pt-1 text-xs", children: t('moreCaption') }), _jsx("p", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement })] }));
170
- }
171
- function GroupHeading({ label, count }) {
172
- return (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("h3", { className: "text-foreground flex-1 text-xs font-medium", children: label }), _jsx("span", { className: "text-muted-foreground text-xs tabular-nums", children: count })] }));
173
- }
174
- function EmptyDrop({ label }) {
175
- return (_jsx("p", { className: "text-muted-foreground border-muted-foreground/40 rounded-md border border-dashed px-3 py-2.5 text-xs", children: label }));
176
- }
177
- /**
178
- * One row. Same recipe as the sidebar's Connect MCP / Connect your computer
179
- * block: `bg-hover` rows on a 1px seam, `rounded-sm` inside, `rounded-md` on
180
- * the outer corners of each group's first and last row.
181
- */
182
- const NavRow = memo(function NavRow({ id, meta, inMore, first, last, hintId, onMove, onDragChange, }) {
183
- const t = useTranslations('sidebar');
184
- const [dragging, setDragging] = useState(false);
185
- const onHandleKeyDown = (event) => {
186
- if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
187
- event.preventDefault();
188
- onMove(id, event.key === 'ArrowUp' ? -1 : 1);
189
- }
190
- };
191
- return (_jsxs(Reorder.Item, { as: "li", value: id, transition: reorderTransition, onDragStart: () => {
192
- setDragging(true);
193
- onDragChange(true);
194
- }, onDragEnd: () => {
195
- setDragging(false);
196
- onDragChange(false);
197
- }, className: cn('bg-hover relative flex h-9 cursor-move touch-none list-none items-center gap-2.5 rounded-sm px-1.5 select-none', first && 'rounded-t-md', last && 'rounded-b-md', dragging && 'bg-popover ring-border z-10 rounded-md shadow-sm ring-1'), children: [_jsx("button", { "data-grip": id, type: "button", "aria-label": t('reorderItem', { name: meta.label }), "aria-describedby": hintId, onKeyDown: onHandleKeyDown, className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring/15 flex size-6 shrink-0 cursor-move items-center justify-center rounded-sm transition-colors focus-visible:ring-3 focus-visible:outline-none", children: _jsx(DotsSixVerticalIcon, { className: "size-4", weight: "bold" }) }), _jsx("span", { className: cn('flex size-4 shrink-0 items-center justify-center [&>svg]:size-4', inMore ? 'text-muted-foreground' : 'text-foreground'), children: meta.icon }), _jsx("span", { className: cn('min-w-0 flex-1 truncate text-sm', inMore ? 'text-muted-foreground' : 'text-foreground'), children: meta.label })] }));
198
- });