@nextclaw/ui 0.12.35-beta.6 → 0.12.35
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/CHANGELOG.md +482 -0
- package/dist/assets/api-DruAcori.js +15 -0
- package/dist/assets/{book-open-qc0UVn6m.js → book-open-cPX0Za7Z.js} +1 -1
- package/dist/assets/channels-list-page-DXH1qYLs.js +8 -0
- package/dist/assets/chat-page-Cpo0AFHb.js +105 -0
- package/dist/assets/{config-split-page-DW8tP9Bc.js → config-split-page-BFh_Qixx.js} +1 -1
- package/dist/assets/confirm-dialog-B0TP-vPd.js +5 -0
- package/dist/assets/{createLucideIcon-CA0sgoMJ.js → createLucideIcon-Br1EFMEr.js} +1 -1
- package/dist/assets/{desktop-update-config-DARSwQG8.js → desktop-update-config-Be8x1jNy.js} +1 -1
- package/dist/assets/{dist-D5zsy7pX.js → dist-CpwDZDtv.js} +1 -1
- package/dist/assets/{dist-DNxIa3pe.js → dist-rzakR_bu.js} +1 -1
- package/dist/assets/doc-browser-BI7HSaMK.js +3 -0
- package/dist/assets/doc-browser-context-DB6TTHeE.js +1 -0
- package/dist/assets/doc-browser-route-registry.utils-x5ijvk0u.js +1 -0
- package/dist/assets/doc-browser-yTfN7UBH.js +1 -0
- package/dist/assets/{external-link-C9fbyi6k.js → external-link-C3usm3se.js} +1 -1
- package/dist/assets/index-GVTiCf1s.js +101 -0
- package/dist/assets/index-v0fAGXQW.css +1 -0
- package/dist/assets/{mcp-marketplace-page-BiExW5wq.js → mcp-marketplace-page-DDjeHd1I.js} +2 -2
- package/dist/assets/mcp-marketplace-page-WUkc7n94.js +1 -0
- package/dist/assets/model-config-BjXMpU9U.js +1 -0
- package/dist/assets/play-CqNi0u02.js +1 -0
- package/dist/assets/plus-BcIC8dG1.js +1 -0
- package/dist/assets/{provider-scoped-model-input-Bk8jlpRm.js → provider-scoped-model-input-t1tCP5y5.js} +1 -1
- package/dist/assets/providers-list-C1D5p9kK.js +1 -0
- package/dist/assets/react-B2X_ph0a.js +1 -0
- package/dist/assets/remote-BHeENKxL.js +1 -0
- package/dist/assets/runtime-config-page--O0Wq01S.js +1 -0
- package/dist/assets/{save-CG4Hb1u8.js → save-D10M5vQg.js} +1 -1
- package/dist/assets/{search-lWpOIHEn.js → search-CdhgFNwf.js} +1 -1
- package/dist/assets/search-config-Dzpizo-_.js +1 -0
- package/dist/assets/{secrets-config-ZbLzjIRx.js → secrets-config-D1AUSEXX.js} +1 -1
- package/dist/assets/{setting-row-CNfTKDjc.js → setting-row-8VkukJoH.js} +1 -1
- package/dist/assets/{tabs-custom-BbZoysAl.js → tabs-custom-BBXu1OGs.js} +1 -1
- package/dist/assets/{tag-chip-Dl8JbhC7.js → tag-chip-ix15P-r4.js} +1 -1
- package/dist/assets/wrench-qVIwShTF.js +1 -0
- package/dist/assets/x-Chb07O-Y.js +1 -0
- package/dist/index.html +17 -15
- package/package.json +8 -8
- package/src/features/panel-apps/components/panel-app-list-item.test.tsx +60 -0
- package/src/features/panel-apps/components/panel-app-list-item.tsx +120 -30
- package/src/features/panel-apps/components/panel-apps-list.tsx +8 -1
- package/src/features/panel-apps/hooks/use-panel-apps.ts +10 -0
- package/src/features/panel-apps/managers/panel-app-bridge.manager.test.ts +153 -1
- package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +126 -9
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +7 -3
- package/src/features/panel-apps/utils/panel-app-view.utils.test.ts +66 -0
- package/src/features/panel-apps/utils/panel-app-view.utils.ts +13 -2
- package/src/index.css +23 -0
- package/src/shared/components/doc-browser/doc-browser-context.test.tsx +170 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +3 -0
- package/src/shared/components/doc-browser/doc-browser-home-page.tsx +95 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +3 -97
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -0
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +117 -44
- package/src/shared/components/doc-browser/doc-browser.test.tsx +159 -1
- package/src/shared/components/doc-browser/doc-browser.tsx +46 -27
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +183 -71
- package/src/shared/components/doc-browser/stores/doc-browser.store.ts +52 -1
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +9 -0
- package/src/shared/components/doc-browser/utils/doc-browser-route-registry.utils.ts +232 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +2 -0
- package/src/shared/lib/api/types.ts +1 -0
- package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +10 -0
- package/dist/assets/api-BTZui7nj.js +0 -15
- package/dist/assets/channels-list-page-DBtU_M1Y.js +0 -8
- package/dist/assets/chat-page-BcoJYo91.js +0 -105
- package/dist/assets/doc-browser-B9skxzVo.js +0 -1
- package/dist/assets/doc-browser-CwyITRXx.js +0 -1
- package/dist/assets/doc-browser-context-Do2qSrB8.js +0 -1
- package/dist/assets/index-BYRP_BMj.css +0 -1
- package/dist/assets/index-DckPGTdM.js +0 -103
- package/dist/assets/mcp-marketplace-page-Cj6MDSkE.js +0 -1
- package/dist/assets/model-config-DbdY7fF1.js +0 -1
- package/dist/assets/notice-card-CyiVcu_Z.js +0 -5
- package/dist/assets/play-D-s0C4MN.js +0 -1
- package/dist/assets/plus-DOwjM_kF.js +0 -1
- package/dist/assets/providers-list-CJjNStDV.js +0 -1
- package/dist/assets/react-BaRotFd6.js +0 -1
- package/dist/assets/remote-gl8QckNc.js +0 -1
- package/dist/assets/runtime-config-page-BMFc1yzf.js +0 -1
- package/dist/assets/search-config-DUOR26if.js +0 -1
- package/dist/assets/x-D6GwT_8k.js +0 -1
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-CY1lg9IE.js} +0 -0
|
@@ -1,111 +1,34 @@
|
|
|
1
1
|
import type { ReactNode, Ref } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
ArrowLeft,
|
|
4
|
-
ArrowRight,
|
|
5
|
-
BookOpen,
|
|
6
3
|
ExternalLink,
|
|
7
|
-
Maximize2,
|
|
8
|
-
PanelRightOpen,
|
|
9
4
|
Search,
|
|
10
|
-
X,
|
|
11
5
|
} from 'lucide-react';
|
|
12
6
|
import {
|
|
13
7
|
isDocsUrl,
|
|
14
8
|
type DocBrowserTab,
|
|
15
9
|
} from './doc-browser-context';
|
|
16
|
-
import type { DocBrowserCustomTabRenderer } from './doc-browser-renderer.types';
|
|
17
|
-
import { cn } from '@/shared/lib/utils';
|
|
18
10
|
import { t } from '@/shared/lib/i18n';
|
|
19
11
|
|
|
20
|
-
type DocBrowserPanelHeaderProps = {
|
|
21
|
-
currentTab?: DocBrowserTab;
|
|
22
|
-
customRenderer?: DocBrowserCustomTabRenderer;
|
|
23
|
-
isDocked: boolean;
|
|
24
|
-
isFullscreen: boolean;
|
|
25
|
-
onClose: () => void;
|
|
26
|
-
onDragStart: (e: React.PointerEvent<HTMLElement>) => void;
|
|
27
|
-
onToggleMode: () => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
12
|
type DocBrowserDocsToolbarProps = {
|
|
31
|
-
currentTab?: DocBrowserTab;
|
|
32
13
|
isDocsTab: boolean;
|
|
33
|
-
onBack: () => void;
|
|
34
|
-
onForward: () => void;
|
|
35
14
|
onSubmit: (e: React.FormEvent) => void;
|
|
36
15
|
onUrlInputChange: (value: string) => void;
|
|
37
16
|
urlInput: string;
|
|
38
17
|
};
|
|
39
18
|
|
|
40
19
|
type DocBrowserFrameContentProps = {
|
|
41
|
-
activeTabId: string;
|
|
42
20
|
currentTab?: DocBrowserTab;
|
|
43
21
|
currentUrl: string;
|
|
44
22
|
customContent: ReactNode;
|
|
45
23
|
iframeRef: Ref<HTMLIFrameElement>;
|
|
46
|
-
|
|
24
|
+
iframeInstanceId: string;
|
|
47
25
|
iframeSandbox: string;
|
|
48
26
|
isDragging: boolean;
|
|
49
27
|
isResizing: boolean;
|
|
50
|
-
navVersion: number;
|
|
51
28
|
};
|
|
52
29
|
|
|
53
|
-
export function DocBrowserPanelHeader({
|
|
54
|
-
currentTab,
|
|
55
|
-
customRenderer,
|
|
56
|
-
isDocked,
|
|
57
|
-
isFullscreen,
|
|
58
|
-
onClose,
|
|
59
|
-
onDragStart,
|
|
60
|
-
onToggleMode,
|
|
61
|
-
}: DocBrowserPanelHeaderProps) {
|
|
62
|
-
const title = currentTab && customRenderer?.getTitle
|
|
63
|
-
? customRenderer.getTitle(currentTab)
|
|
64
|
-
: t('docBrowserTitle');
|
|
65
|
-
const icon = currentTab && customRenderer?.renderIcon
|
|
66
|
-
? customRenderer.renderIcon(currentTab)
|
|
67
|
-
: <BookOpen className="w-4 h-4 text-primary shrink-0" />;
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div
|
|
71
|
-
className={cn(
|
|
72
|
-
'flex items-center justify-between px-4 py-2.5 bg-gray-50 border-b border-gray-200 shrink-0 select-none',
|
|
73
|
-
!isDocked && !isFullscreen && 'cursor-grab active:cursor-grabbing',
|
|
74
|
-
isFullscreen && 'pt-[calc(env(safe-area-inset-top,0px)+0.625rem)]',
|
|
75
|
-
)}
|
|
76
|
-
onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
|
|
77
|
-
>
|
|
78
|
-
<div className="flex items-center gap-2.5 min-w-0">
|
|
79
|
-
{icon}
|
|
80
|
-
<span className="text-sm font-semibold text-gray-900 truncate">{title}</span>
|
|
81
|
-
</div>
|
|
82
|
-
<div className="flex items-center gap-1">
|
|
83
|
-
{!isFullscreen ? (
|
|
84
|
-
<button
|
|
85
|
-
onClick={onToggleMode}
|
|
86
|
-
className="hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
87
|
-
title={isDocked ? t('docBrowserFloatMode') : t('docBrowserDockMode')}
|
|
88
|
-
>
|
|
89
|
-
{isDocked ? <Maximize2 className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
|
90
|
-
</button>
|
|
91
|
-
) : null}
|
|
92
|
-
<button
|
|
93
|
-
onClick={onClose}
|
|
94
|
-
className="hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
95
|
-
title={t('docBrowserClose')}
|
|
96
|
-
>
|
|
97
|
-
<X className="w-3.5 h-3.5" />
|
|
98
|
-
</button>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
30
|
export function DocBrowserDocsToolbar({
|
|
105
|
-
currentTab,
|
|
106
31
|
isDocsTab,
|
|
107
|
-
onBack,
|
|
108
|
-
onForward,
|
|
109
32
|
onSubmit,
|
|
110
33
|
onUrlInputChange,
|
|
111
34
|
urlInput,
|
|
@@ -116,21 +39,6 @@ export function DocBrowserDocsToolbar({
|
|
|
116
39
|
|
|
117
40
|
return (
|
|
118
41
|
<div className="flex items-center gap-2 px-3.5 py-2 bg-white border-b border-gray-100 shrink-0">
|
|
119
|
-
<button
|
|
120
|
-
onClick={onBack}
|
|
121
|
-
disabled={!isDocsTab || (currentTab?.historyIndex ?? 0) <= 0}
|
|
122
|
-
className="p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors"
|
|
123
|
-
>
|
|
124
|
-
<ArrowLeft className="w-4 h-4" />
|
|
125
|
-
</button>
|
|
126
|
-
<button
|
|
127
|
-
onClick={onForward}
|
|
128
|
-
disabled={!isDocsTab || (currentTab?.historyIndex ?? 0) >= (currentTab?.history.length ?? 0) - 1}
|
|
129
|
-
className="p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors"
|
|
130
|
-
>
|
|
131
|
-
<ArrowRight className="w-4 h-4" />
|
|
132
|
-
</button>
|
|
133
|
-
|
|
134
42
|
<form onSubmit={onSubmit} className="flex-1 relative">
|
|
135
43
|
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
|
136
44
|
<input
|
|
@@ -146,16 +54,14 @@ export function DocBrowserDocsToolbar({
|
|
|
146
54
|
}
|
|
147
55
|
|
|
148
56
|
export function DocBrowserFrameContent({
|
|
149
|
-
activeTabId,
|
|
150
57
|
currentTab,
|
|
151
58
|
currentUrl,
|
|
152
59
|
customContent,
|
|
153
60
|
iframeRef,
|
|
154
|
-
|
|
61
|
+
iframeInstanceId,
|
|
155
62
|
iframeSandbox,
|
|
156
63
|
isDragging,
|
|
157
64
|
isResizing,
|
|
158
|
-
navVersion,
|
|
159
65
|
}: DocBrowserFrameContentProps) {
|
|
160
66
|
return (
|
|
161
67
|
<div className="flex-1 relative overflow-hidden">
|
|
@@ -164,7 +70,7 @@ export function DocBrowserFrameContent({
|
|
|
164
70
|
) : (
|
|
165
71
|
<iframe
|
|
166
72
|
ref={iframeRef}
|
|
167
|
-
key={
|
|
73
|
+
key={iframeInstanceId}
|
|
168
74
|
src={currentUrl}
|
|
169
75
|
className="absolute inset-0 w-full h-full border-0"
|
|
170
76
|
title={currentTab?.title || 'NextClaw Docs'}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PointerEvent } from 'react';
|
|
2
|
+
import { ArrowLeft, ArrowRight, Maximize2, PanelRightOpen, Plus, X } from 'lucide-react';
|
|
2
3
|
import type { DocBrowserTab } from './doc-browser-context';
|
|
3
4
|
import { cn } from '@/shared/lib/utils';
|
|
4
5
|
import { t } from '@/shared/lib/i18n';
|
|
@@ -6,62 +7,134 @@ import { t } from '@/shared/lib/i18n';
|
|
|
6
7
|
type DocBrowserTabStripProps = {
|
|
7
8
|
tabs: DocBrowserTab[];
|
|
8
9
|
activeTabId: string;
|
|
9
|
-
|
|
10
|
+
canGoBack: boolean;
|
|
11
|
+
canGoForward: boolean;
|
|
12
|
+
isDocked: boolean;
|
|
13
|
+
isFullscreen: boolean;
|
|
14
|
+
onGoBack: () => void;
|
|
15
|
+
onGoForward: () => void;
|
|
16
|
+
onOpenNewTab: () => void;
|
|
10
17
|
onSetActiveTab: (tabId: string) => void;
|
|
11
18
|
onCloseTab: (tabId: string) => void;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
onDragStart: (event: PointerEvent<HTMLElement>) => void;
|
|
21
|
+
onToggleMode: () => void;
|
|
12
22
|
};
|
|
13
23
|
|
|
14
24
|
export function DocBrowserTabStrip({
|
|
15
25
|
tabs,
|
|
16
26
|
activeTabId,
|
|
17
|
-
|
|
27
|
+
canGoBack,
|
|
28
|
+
canGoForward,
|
|
29
|
+
isDocked,
|
|
30
|
+
isFullscreen,
|
|
31
|
+
onGoBack,
|
|
32
|
+
onGoForward,
|
|
33
|
+
onOpenNewTab,
|
|
18
34
|
onSetActiveTab,
|
|
19
35
|
onCloseTab,
|
|
36
|
+
onClose,
|
|
37
|
+
onDragStart,
|
|
38
|
+
onToggleMode,
|
|
20
39
|
}: DocBrowserTabStripProps) {
|
|
21
40
|
return (
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
onClick={(event) => {
|
|
46
|
-
event.stopPropagation();
|
|
47
|
-
onCloseTab(tab.id);
|
|
48
|
-
}}
|
|
49
|
-
className="rounded p-0.5 hover:bg-black/10"
|
|
50
|
-
aria-label={t('docBrowserCloseTab')}
|
|
41
|
+
<div
|
|
42
|
+
data-testid="doc-browser-tab-strip"
|
|
43
|
+
className={cn(
|
|
44
|
+
'flex h-11 items-stretch gap-2 px-2.5 bg-background border-b border-[#f1e7d4] shrink-0 select-none',
|
|
45
|
+
isFullscreen && 'h-[calc(env(safe-area-inset-top,0px)+2.75rem)] pt-[env(safe-area-inset-top,0px)]',
|
|
46
|
+
)}
|
|
47
|
+
onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
className="doc-browser-tab-scrollbar flex h-full min-w-0 flex-1 items-center gap-1.5 overflow-x-auto"
|
|
51
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
52
|
+
>
|
|
53
|
+
{tabs.map((tab) => {
|
|
54
|
+
const isActive = tab.id === activeTabId;
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
key={tab.id}
|
|
58
|
+
className={cn(
|
|
59
|
+
'inline-flex items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors',
|
|
60
|
+
isActive
|
|
61
|
+
? 'bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]'
|
|
62
|
+
: 'bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]',
|
|
63
|
+
)}
|
|
51
64
|
>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={() => onSetActiveTab(tab.id)}
|
|
68
|
+
className="truncate text-left px-1"
|
|
69
|
+
title={tab.title}
|
|
70
|
+
>
|
|
71
|
+
{tab.title || t('docBrowserTabUntitled')}
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
onClick={(event) => {
|
|
76
|
+
event.stopPropagation();
|
|
77
|
+
onCloseTab(tab.id);
|
|
78
|
+
}}
|
|
79
|
+
className="rounded p-0.5 hover:bg-black/10"
|
|
80
|
+
aria-label={t('docBrowserCloseTab')}
|
|
81
|
+
>
|
|
82
|
+
<X className="w-3 h-3" />
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</div>
|
|
88
|
+
<div
|
|
89
|
+
className="flex h-full items-center gap-1 shrink-0"
|
|
90
|
+
data-testid="doc-browser-tab-actions"
|
|
91
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
62
92
|
>
|
|
63
|
-
<
|
|
64
|
-
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={onGoBack}
|
|
96
|
+
disabled={!canGoBack}
|
|
97
|
+
className="rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
|
|
98
|
+
title={t('docBrowserBack')}
|
|
99
|
+
>
|
|
100
|
+
<ArrowLeft className="w-3.5 h-3.5" />
|
|
101
|
+
</button>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
onClick={onGoForward}
|
|
105
|
+
disabled={!canGoForward}
|
|
106
|
+
className="rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
|
|
107
|
+
title={t('docBrowserForward')}
|
|
108
|
+
>
|
|
109
|
+
<ArrowRight className="w-3.5 h-3.5" />
|
|
110
|
+
</button>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
onClick={onOpenNewTab}
|
|
114
|
+
className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
115
|
+
title={t('docBrowserNewTab')}
|
|
116
|
+
>
|
|
117
|
+
<Plus className="w-3.5 h-3.5" />
|
|
118
|
+
</button>
|
|
119
|
+
{!isFullscreen ? (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
onClick={onToggleMode}
|
|
123
|
+
className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
124
|
+
title={isDocked ? t('docBrowserFloatMode') : t('docBrowserDockMode')}
|
|
125
|
+
>
|
|
126
|
+
{isDocked ? <Maximize2 className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
|
127
|
+
</button>
|
|
128
|
+
) : null}
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={onClose}
|
|
132
|
+
className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
133
|
+
title={t('docBrowserClose')}
|
|
134
|
+
>
|
|
135
|
+
<X className="w-3.5 h-3.5" />
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
65
138
|
</div>
|
|
66
139
|
);
|
|
67
140
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { DocBrowser } from "@/shared/components/doc-browser/doc-browser";
|
|
4
|
+
import type { DocBrowserContextValue, DocBrowserTab } from "@/shared/components/doc-browser/doc-browser-context";
|
|
4
5
|
|
|
5
|
-
const {
|
|
6
|
+
const { navigateMock } = vi.hoisted(() => ({
|
|
7
|
+
navigateMock: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("react-router-dom", async () => ({
|
|
11
|
+
...(await vi.importActual("react-router-dom") as object),
|
|
12
|
+
useNavigate: () => navigateMock,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const { docBrowserState } = vi.hoisted<{ docBrowserState: DocBrowserContextValue }>(() => ({
|
|
6
16
|
docBrowserState: {
|
|
7
17
|
isOpen: true,
|
|
8
18
|
mode: "docked" as "docked" | "floating",
|
|
@@ -18,6 +28,12 @@ const { docBrowserState } = vi.hoisted(() => ({
|
|
|
18
28
|
},
|
|
19
29
|
],
|
|
20
30
|
activeTabId: "docs",
|
|
31
|
+
activeHistory: [{
|
|
32
|
+
kind: "docs" as const,
|
|
33
|
+
tabId: "docs",
|
|
34
|
+
url: "https://docs.nextclaw.io/en/guide/getting-started",
|
|
35
|
+
}],
|
|
36
|
+
activeHistoryIndex: 0,
|
|
21
37
|
currentTab: {
|
|
22
38
|
id: "docs",
|
|
23
39
|
kind: "docs" as const,
|
|
@@ -27,6 +43,8 @@ const { docBrowserState } = vi.hoisted(() => ({
|
|
|
27
43
|
historyIndex: 0,
|
|
28
44
|
navVersion: 0,
|
|
29
45
|
},
|
|
46
|
+
open: vi.fn(),
|
|
47
|
+
openNewTab: vi.fn(),
|
|
30
48
|
close: vi.fn(),
|
|
31
49
|
toggleMode: vi.fn(),
|
|
32
50
|
goBack: vi.fn(),
|
|
@@ -65,7 +83,27 @@ function firePointerEvent(
|
|
|
65
83
|
|
|
66
84
|
describe("DocBrowser", () => {
|
|
67
85
|
beforeEach(() => {
|
|
86
|
+
vi.clearAllMocks();
|
|
68
87
|
docBrowserState.mode = "docked";
|
|
88
|
+
docBrowserState.tabs = [
|
|
89
|
+
{
|
|
90
|
+
id: "docs",
|
|
91
|
+
kind: "docs" as const,
|
|
92
|
+
title: "Docs",
|
|
93
|
+
currentUrl: "https://docs.nextclaw.io/en/guide/getting-started",
|
|
94
|
+
history: ["https://docs.nextclaw.io/en/guide/getting-started"],
|
|
95
|
+
historyIndex: 0,
|
|
96
|
+
navVersion: 0,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
docBrowserState.activeTabId = "docs";
|
|
100
|
+
docBrowserState.activeHistory = [{
|
|
101
|
+
kind: "docs" as const,
|
|
102
|
+
tabId: "docs",
|
|
103
|
+
url: "https://docs.nextclaw.io/en/guide/getting-started",
|
|
104
|
+
}];
|
|
105
|
+
docBrowserState.activeHistoryIndex = 0;
|
|
106
|
+
docBrowserState.currentTab = docBrowserState.tabs[0];
|
|
69
107
|
Object.defineProperty(window, "innerWidth", {
|
|
70
108
|
configurable: true,
|
|
71
109
|
value: 1200,
|
|
@@ -99,6 +137,126 @@ describe("DocBrowser", () => {
|
|
|
99
137
|
expect(panel.querySelector(".cursor-se-resize")).toBeNull();
|
|
100
138
|
});
|
|
101
139
|
|
|
140
|
+
it("keeps browser window controls on the tab strip", () => {
|
|
141
|
+
render(<DocBrowser />);
|
|
142
|
+
|
|
143
|
+
const tabStrip = screen.getByTestId("doc-browser-tab-strip");
|
|
144
|
+
const tabActions = screen.getByTestId("doc-browser-tab-actions");
|
|
145
|
+
|
|
146
|
+
expect(tabActions.contains(screen.getByTitle("Back"))).toBe(true);
|
|
147
|
+
expect(tabActions.contains(screen.getByTitle("Forward"))).toBe(true);
|
|
148
|
+
expect(tabStrip.contains(screen.getByTitle("Float Window"))).toBe(true);
|
|
149
|
+
expect(tabStrip.contains(screen.getByTitle("Close"))).toBe(true);
|
|
150
|
+
expect(tabActions.contains(screen.getByTitle("New Tab"))).toBe(true);
|
|
151
|
+
expect(screen.queryByText("Embedded Browser")).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("uses active history to enable browser back and forward actions", () => {
|
|
155
|
+
const history = [
|
|
156
|
+
"https://docs.nextclaw.io/en/guide/getting-started",
|
|
157
|
+
"https://docs.nextclaw.io/en/guide/channels",
|
|
158
|
+
"https://docs.nextclaw.io/en/guide/apps",
|
|
159
|
+
];
|
|
160
|
+
const historyTab: DocBrowserTab = {
|
|
161
|
+
id: "docs",
|
|
162
|
+
kind: "docs",
|
|
163
|
+
title: "Docs",
|
|
164
|
+
currentUrl: history[1],
|
|
165
|
+
history,
|
|
166
|
+
historyIndex: 1,
|
|
167
|
+
navVersion: 0,
|
|
168
|
+
};
|
|
169
|
+
docBrowserState.tabs = [historyTab];
|
|
170
|
+
docBrowserState.activeTabId = historyTab.id;
|
|
171
|
+
docBrowserState.activeHistory = [
|
|
172
|
+
{ kind: "home" as const, tabId: "home", url: "nextclaw://new-tab" },
|
|
173
|
+
{ kind: "docs" as const, tabId: historyTab.id, url: history[1] },
|
|
174
|
+
{ kind: "apps" as const, tabId: "apps", url: "nextclaw://apps" },
|
|
175
|
+
];
|
|
176
|
+
docBrowserState.activeHistoryIndex = 1;
|
|
177
|
+
docBrowserState.currentTab = historyTab;
|
|
178
|
+
|
|
179
|
+
render(<DocBrowser />);
|
|
180
|
+
|
|
181
|
+
fireEvent.click(screen.getByTitle("Back"));
|
|
182
|
+
fireEvent.click(screen.getByTitle("Forward"));
|
|
183
|
+
|
|
184
|
+
expect(docBrowserState.goBack).toHaveBeenCalled();
|
|
185
|
+
expect(docBrowserState.goForward).toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("disables browser history actions at the active history edges", () => {
|
|
189
|
+
const history = [
|
|
190
|
+
"https://docs.nextclaw.io/en/guide/getting-started",
|
|
191
|
+
"https://docs.nextclaw.io/en/guide/apps",
|
|
192
|
+
];
|
|
193
|
+
const latestTab: DocBrowserTab = {
|
|
194
|
+
id: "docs",
|
|
195
|
+
kind: "docs",
|
|
196
|
+
title: "Docs",
|
|
197
|
+
currentUrl: history[1],
|
|
198
|
+
history,
|
|
199
|
+
historyIndex: 1,
|
|
200
|
+
navVersion: 0,
|
|
201
|
+
};
|
|
202
|
+
docBrowserState.tabs = [latestTab];
|
|
203
|
+
docBrowserState.activeTabId = latestTab.id;
|
|
204
|
+
docBrowserState.activeHistory = [
|
|
205
|
+
{ kind: "docs" as const, tabId: latestTab.id, url: history[0] },
|
|
206
|
+
{ kind: "docs" as const, tabId: latestTab.id, url: history[1] },
|
|
207
|
+
];
|
|
208
|
+
docBrowserState.activeHistoryIndex = 1;
|
|
209
|
+
docBrowserState.currentTab = latestTab;
|
|
210
|
+
|
|
211
|
+
const { rerender } = render(<DocBrowser />);
|
|
212
|
+
|
|
213
|
+
expect(screen.getByTitle("Back")).toHaveProperty("disabled", false);
|
|
214
|
+
expect(screen.getByTitle("Forward")).toHaveProperty("disabled", true);
|
|
215
|
+
|
|
216
|
+
const oldestTab: DocBrowserTab = {
|
|
217
|
+
...latestTab,
|
|
218
|
+
currentUrl: history[0],
|
|
219
|
+
historyIndex: 0,
|
|
220
|
+
};
|
|
221
|
+
docBrowserState.tabs = [oldestTab];
|
|
222
|
+
docBrowserState.currentTab = oldestTab;
|
|
223
|
+
docBrowserState.activeHistoryIndex = 0;
|
|
224
|
+
rerender(<DocBrowser />);
|
|
225
|
+
|
|
226
|
+
expect(screen.getByTitle("Back")).toHaveProperty("disabled", true);
|
|
227
|
+
expect(screen.getByTitle("Forward")).toHaveProperty("disabled", false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("opens the start page from the fixed new tab action", () => {
|
|
231
|
+
render(<DocBrowser />);
|
|
232
|
+
|
|
233
|
+
fireEvent.click(screen.getByTitle("New Tab"));
|
|
234
|
+
|
|
235
|
+
expect(docBrowserState.openNewTab).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("renders a start page for home tabs", () => {
|
|
239
|
+
const homeTab: DocBrowserTab = {
|
|
240
|
+
id: "home",
|
|
241
|
+
kind: "home" as const,
|
|
242
|
+
title: "Start Page",
|
|
243
|
+
currentUrl: "nextclaw://new-tab",
|
|
244
|
+
history: ["nextclaw://new-tab"],
|
|
245
|
+
historyIndex: 0,
|
|
246
|
+
navVersion: 0,
|
|
247
|
+
};
|
|
248
|
+
docBrowserState.tabs = [homeTab];
|
|
249
|
+
docBrowserState.activeTabId = homeTab.id;
|
|
250
|
+
docBrowserState.currentTab = homeTab;
|
|
251
|
+
|
|
252
|
+
render(<DocBrowser />);
|
|
253
|
+
|
|
254
|
+
expect(screen.getAllByText("Start Page").length).toBeGreaterThan(0);
|
|
255
|
+
expect(screen.getByText("Apps")).toBeTruthy();
|
|
256
|
+
expect(screen.getByText("Service Apps")).toBeTruthy();
|
|
257
|
+
expect(screen.getByText("Help Docs")).toBeTruthy();
|
|
258
|
+
});
|
|
259
|
+
|
|
102
260
|
it("keeps the floating panel left edge stable when resizing from the right", () => {
|
|
103
261
|
docBrowserState.mode = "floating";
|
|
104
262
|
|