@nextclaw/ui 0.14.4-beta.0 → 0.15.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/CHANGELOG.md +119 -0
- package/dist/assets/api-DLtsiBKm.js +13 -0
- package/dist/assets/appearance-settings-page-CtkeYXux.js +1 -0
- package/dist/assets/book-open-BQwWBDKb.js +1 -0
- package/dist/assets/channels-list-page-Cv8o9Hxg.js +8 -0
- package/dist/assets/chat-page-BtA7H1Ea.js +1 -0
- package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-EidRBpP2.js} +1 -1
- package/dist/assets/confirm-dialog-iIJFFp2T.js +5 -0
- package/dist/assets/desktop-update-config-C6n_TB1r.js +1 -0
- package/dist/assets/{dist-CikkhOlq.js → dist-CBxWOazz.js} +1 -1
- package/dist/assets/{dist-PVA_xL1D.js → dist-D2eZC3OR.js} +1 -1
- package/dist/assets/doc-browser-B76XOXmW.js +1 -0
- package/dist/assets/doc-browser-DBnj9UMB.js +1 -0
- package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-Sv-044Cl.js} +1 -1
- package/dist/assets/{ellipsis-y6vDtq4M.js → ellipsis-2uzhypw5.js} +1 -1
- package/dist/assets/es2015-DhQo_oS6.js +41 -0
- package/dist/assets/{external-link-DVJIQf_G.js → external-link-CM5dnUI6.js} +1 -1
- package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
- package/dist/assets/i18n-provider-di1waTGX.js +1 -0
- package/dist/assets/index-CBRP-7-a.css +1 -0
- package/dist/assets/index-Z1fYpbNd.js +104 -0
- package/dist/assets/loader-circle-BjmN5DeJ.js +1 -0
- package/dist/assets/mcp-marketplace-page-C_MVQUbT.js +9 -0
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +1 -0
- package/dist/assets/model-config-page-Cdr9P6TQ.js +1 -0
- package/dist/assets/plus-3ztRZD_l.js +1 -0
- package/dist/assets/provider-scoped-model-input-g1WkGiJ8.js +1 -0
- package/dist/assets/providers-config-page-CWlO7J4A.js +1 -0
- package/dist/assets/{react-VNqT5Zp7.js → react-DjpzoZXB.js} +1 -1
- package/dist/assets/refresh-cw-DaTv3zxE.js +1 -0
- package/dist/assets/remote-CKWZfVVB.js +1 -0
- package/dist/assets/rotate-cw-CHcaRUDz.js +1 -0
- package/dist/assets/runtime-config-page-CfTqh1nv.js +1 -0
- package/dist/assets/{save-BOJ477H-.js → save-BZof1uie.js} +1 -1
- package/dist/assets/{search-BEti5beA.js → search-MxAarS_g.js} +1 -1
- package/dist/assets/search-config-page-Dm8daYny.js +1 -0
- package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-D8AbcR05.js} +2 -2
- package/dist/assets/security-config-Ds0LsJmo.js +1 -0
- package/dist/assets/select-D7GcNP0z.js +1 -0
- package/dist/assets/setting-row-CuozlnJd.js +1 -0
- package/dist/assets/side-dock-BcLau5S_.js +3 -0
- package/dist/assets/skeleton-shfmXuYp.js +1 -0
- package/dist/assets/switch-C1BWMU5c.js +1 -0
- package/dist/assets/tag-chip-DCUrQUCP.js +1 -0
- package/dist/assets/tooltip-DfAHHA76.js +1 -0
- package/dist/assets/use-config-DAdbT5QJ.js +1 -0
- package/dist/assets/use-confirm-dialog-eWcJ6V-s.js +1 -0
- package/dist/assets/x-DqL1mYLC.js +1 -0
- package/dist/index.html +53 -17
- package/index.html +25 -2
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
- package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
- package/src/app/components/layout/app-layout.tsx +10 -3
- package/src/app/components/layout/sidebar-items.tsx +8 -8
- package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
- package/src/app/components/layout/sidebar.tsx +21 -28
- package/src/app/configs/app-navigation.config.ts +11 -5
- package/src/app/index.tsx +8 -0
- package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
- package/src/app/managers/viewport-layout.manager.ts +39 -0
- package/src/app/presenters/app.presenter.ts +25 -2
- package/src/app/styles/design-system.css +91 -4
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +51 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +166 -0
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
- package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +39 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +270 -115
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +70 -5
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +79 -1
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
- package/src/features/chat/managers/chat-thread.manager.ts +9 -3
- package/src/features/chat/managers/chat-ui.manager.ts +5 -1
- package/src/features/chat/presenters/chat.presenter.ts +2 -1
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -64
- package/src/features/chat/types/chat-message.types.ts +4 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
- package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
- package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
- package/src/features/marketplace/index.ts +3 -0
- package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
- package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
- package/src/features/side-dock/components/side-dock.tsx +100 -13
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
- package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
- package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
- package/src/features/side-dock/stores/side-dock.store.ts +9 -0
- package/src/features/side-dock/types/side-dock.types.ts +10 -3
- package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
- package/src/index.css +501 -27
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
- package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
- package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
- package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
- package/src/shared/components/ui/dialog.tsx +2 -2
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +4 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
- package/src/shared/components/ui/tooltip.tsx +1 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +27 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +15 -6
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +15 -6
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
- package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
- package/src/shared/lib/session-project/index.ts +1 -1
- package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
- package/src/shared/lib/theme/index.ts +51 -13
- package/dist/assets/api-BP_8IaxB.js +0 -13
- package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
- package/dist/assets/chat-page-BRB0N5WW.js +0 -105
- package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
- package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
- package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
- package/dist/assets/index-BiE-K8-q.css +0 -1
- package/dist/assets/index-D9BAE_L_.js +0 -105
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
- package/dist/assets/model-config-page-DotaTUHb.js +0 -1
- package/dist/assets/plus-BbZqquJe.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
- package/dist/assets/remote-B8TiUfwW.js +0 -1
- package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
- package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
- package/dist/assets/search-config-page-DGj5am03.js +0 -1
- package/dist/assets/select-hoj8dK4K.js +0 -41
- package/dist/assets/tag-chip-T8-h-die.js +0 -1
- package/dist/assets/use-config-CjRV2hwL.js +0 -1
- package/dist/assets/x-D9PXs_xP.js +0 -1
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
- package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
- /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
|
@@ -11,13 +11,21 @@ import { normalizeDocUrl } from './utils/doc-browser-url.utils';
|
|
|
11
11
|
import { DocBrowserHomePage } from './doc-browser-home-page';
|
|
12
12
|
import type { DocBrowserCustomTabRenderers } from './doc-browser-renderer.types';
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
DocBrowserAddressToolbar,
|
|
15
15
|
DocBrowserExternalLink,
|
|
16
16
|
DocBrowserFrameContent,
|
|
17
17
|
} from './doc-browser-panel-parts';
|
|
18
18
|
import { DocBrowserTabStrip } from './doc-browser-tab-strip';
|
|
19
|
+
import {
|
|
20
|
+
createInitialFloatingPanelRect,
|
|
21
|
+
moveFloatingPanelRect,
|
|
22
|
+
resizeFloatingPanelRect,
|
|
23
|
+
type FloatingPanelRect,
|
|
24
|
+
type FloatingPanelResizeEdge,
|
|
25
|
+
} from './utils/doc-browser-floating-panel.utils';
|
|
19
26
|
import { ResizableRightPanel } from '@/shared/components/resizable-right-panel/resizable-right-panel';
|
|
20
27
|
import { cn } from '@/shared/lib/utils';
|
|
28
|
+
import { t } from '@/shared/lib/i18n';
|
|
21
29
|
import { GripVertical } from 'lucide-react';
|
|
22
30
|
|
|
23
31
|
type DocBrowserProps = {
|
|
@@ -26,26 +34,46 @@ type DocBrowserProps = {
|
|
|
26
34
|
dockControls?: DocBrowserDockControls;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
|
-
type FloatingPanelRect = { x: number; y: number; w: number; h: number };
|
|
30
|
-
type FloatingPanelResizeEdge = 'left' | 'right' | 'top' | 'bottom' | 'bottom-right';
|
|
31
37
|
type FloatingPanelInteraction = {
|
|
32
|
-
kind: 'drag' | 'resize';
|
|
33
|
-
edge?: FloatingPanelResizeEdge;
|
|
34
38
|
startX: number;
|
|
35
39
|
startY: number;
|
|
36
40
|
startRect: FloatingPanelRect;
|
|
37
|
-
}
|
|
41
|
+
} & (
|
|
42
|
+
| { kind: 'drag' }
|
|
43
|
+
| { kind: 'resize'; edge: FloatingPanelResizeEdge }
|
|
44
|
+
);
|
|
38
45
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const DEFAULT_DOCS_IFRAME_SANDBOX = 'allow-same-origin allow-scripts allow-popups allow-forms';
|
|
47
|
+
|
|
48
|
+
function resolveContentUrlInput(input: string, currentUrl: string): string {
|
|
49
|
+
if (input.startsWith('/')) {
|
|
50
|
+
try {
|
|
51
|
+
return new URL(input, currentUrl).toString();
|
|
52
|
+
} catch {
|
|
53
|
+
return input;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(input) || input === 'about:blank') {
|
|
57
|
+
return input;
|
|
58
|
+
}
|
|
59
|
+
if (/^(localhost|127\.|0\.0\.0\.0|\[::1\])(?::|\/|$)/i.test(input)) {
|
|
60
|
+
return `http://${input}`;
|
|
61
|
+
}
|
|
62
|
+
return `https://${input}`;
|
|
63
|
+
}
|
|
43
64
|
|
|
44
65
|
function resolveIframeSandbox(
|
|
45
66
|
customRenderer: DocBrowserCustomTabRenderers[string] | undefined,
|
|
46
67
|
currentTab: DocBrowserTab | undefined,
|
|
47
|
-
): string {
|
|
48
|
-
|
|
68
|
+
): string | undefined {
|
|
69
|
+
if (!currentTab) {
|
|
70
|
+
return DEFAULT_DOCS_IFRAME_SANDBOX;
|
|
71
|
+
}
|
|
72
|
+
const customSandbox = customRenderer?.getIframeSandbox?.(currentTab);
|
|
73
|
+
if (customSandbox !== undefined) {
|
|
74
|
+
return customSandbox;
|
|
75
|
+
}
|
|
76
|
+
return currentTab.kind === 'content' ? undefined : DEFAULT_DOCS_IFRAME_SANDBOX;
|
|
49
77
|
}
|
|
50
78
|
|
|
51
79
|
function useDocBrowserDockAction(
|
|
@@ -67,16 +95,62 @@ function useDocBrowserDockAction(
|
|
|
67
95
|
}, [currentTab, dockControls, dockState]);
|
|
68
96
|
}
|
|
69
97
|
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
function useDocBrowserAddressBar({
|
|
99
|
+
activeTabId,
|
|
100
|
+
currentUrl,
|
|
101
|
+
isAddressToolbarTab,
|
|
102
|
+
isContentTab,
|
|
103
|
+
isDocsTab,
|
|
104
|
+
navigate,
|
|
105
|
+
}: {
|
|
106
|
+
activeTabId: string;
|
|
107
|
+
currentUrl: string;
|
|
108
|
+
isAddressToolbarTab: boolean;
|
|
109
|
+
isContentTab: boolean;
|
|
110
|
+
isDocsTab: boolean;
|
|
111
|
+
navigate: (url: string) => void;
|
|
112
|
+
}) {
|
|
113
|
+
const addressInputKey = `${activeTabId}:${currentUrl}:${isContentTab ? 'content' : 'docs'}`;
|
|
114
|
+
const [draftInput, setDraftInput] = useState<{ key: string; value: string } | null>(null);
|
|
115
|
+
const currentInput = (() => {
|
|
116
|
+
if (!isAddressToolbarTab) {
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
if (isContentTab) {
|
|
120
|
+
return currentUrl;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
return new URL(currentUrl).pathname;
|
|
124
|
+
} catch {
|
|
125
|
+
return currentUrl;
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
const urlInput = draftInput?.key === addressInputKey ? draftInput.value : currentInput;
|
|
129
|
+
const setUrlInput = useCallback(
|
|
130
|
+
(value: string) => setDraftInput({ key: addressInputKey, value }),
|
|
131
|
+
[addressInputKey],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const handleUrlSubmit = useCallback((e: React.FormEvent) => {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
if (!isAddressToolbarTab) return;
|
|
137
|
+
const input = urlInput.trim();
|
|
138
|
+
if (!input) return;
|
|
139
|
+
if (isDocsTab && input.startsWith('/')) {
|
|
140
|
+
navigate(`${DOCS_DEFAULT_BASE_URL}${input}`);
|
|
141
|
+
} else if (isDocsTab && input.startsWith('http')) {
|
|
142
|
+
navigate(input);
|
|
143
|
+
} else if (isDocsTab) {
|
|
144
|
+
navigate(`${DOCS_DEFAULT_BASE_URL}/${input}`);
|
|
145
|
+
} else {
|
|
146
|
+
navigate(resolveContentUrlInput(input, currentUrl));
|
|
147
|
+
}
|
|
148
|
+
}, [currentUrl, isAddressToolbarTab, isDocsTab, navigate, urlInput]);
|
|
73
149
|
|
|
74
|
-
function createInitialFloatingPanelRect(): FloatingPanelRect {
|
|
75
150
|
return {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
h: 600,
|
|
151
|
+
handleUrlSubmit,
|
|
152
|
+
setUrlInput,
|
|
153
|
+
urlInput,
|
|
80
154
|
};
|
|
81
155
|
}
|
|
82
156
|
|
|
@@ -102,7 +176,6 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
102
176
|
closeTab,
|
|
103
177
|
} = useDocBrowser();
|
|
104
178
|
|
|
105
|
-
const [urlInput, setUrlInput] = useState('');
|
|
106
179
|
const [iframeReloadVersion, setIframeReloadVersion] = useState(0);
|
|
107
180
|
const [floatRect, setFloatRect] = useState<FloatingPanelRect>(createInitialFloatingPanelRect);
|
|
108
181
|
const [floatInteraction, setFloatInteraction] = useState<FloatingPanelInteraction | null>(null);
|
|
@@ -118,19 +191,16 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
118
191
|
const canGoForward = activeHistoryIndex < activeHistory.length - 1;
|
|
119
192
|
const customRenderer = currentTab ? customTabRenderers[currentTab.kind] : undefined;
|
|
120
193
|
const dockState = dockControls?.getDockState(currentTab);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
setUrlInput(currentUrl);
|
|
132
|
-
}
|
|
133
|
-
}, [currentUrl, activeTabId, isDocsTab]);
|
|
194
|
+
const isContentTab = currentTab?.kind === 'content';
|
|
195
|
+
const isAddressToolbarTab = isDocsTab || isContentTab;
|
|
196
|
+
const { handleUrlSubmit, setUrlInput, urlInput } = useDocBrowserAddressBar({
|
|
197
|
+
activeTabId,
|
|
198
|
+
currentUrl,
|
|
199
|
+
isAddressToolbarTab,
|
|
200
|
+
isContentTab,
|
|
201
|
+
isDocsTab,
|
|
202
|
+
navigate,
|
|
203
|
+
});
|
|
134
204
|
|
|
135
205
|
// When currentUrl changes without navVersion bump (goBack/goForward),
|
|
136
206
|
// use postMessage to SPA-navigate inside the iframe instead of remounting.
|
|
@@ -221,31 +291,11 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
221
291
|
const dy = event.clientY - startY;
|
|
222
292
|
|
|
223
293
|
if (floatInteraction.kind === 'drag') {
|
|
224
|
-
setFloatRect(
|
|
225
|
-
...startRect,
|
|
226
|
-
x: clamp(startRect.x + dx, FLOATING_PANEL_MARGIN, window.innerWidth - FLOATING_PANEL_MARGIN - startRect.w),
|
|
227
|
-
y: clamp(startRect.y + dy, FLOATING_PANEL_MARGIN, window.innerHeight - FLOATING_PANEL_MARGIN - startRect.h),
|
|
228
|
-
});
|
|
294
|
+
setFloatRect(moveFloatingPanelRect(startRect, dx, dy));
|
|
229
295
|
return;
|
|
230
296
|
}
|
|
231
297
|
|
|
232
|
-
|
|
233
|
-
const isLeftEdge = floatInteraction.edge === 'left';
|
|
234
|
-
const fixedEdge = isLeftEdge ? startRect.x + startRect.w : startRect.y + startRect.h;
|
|
235
|
-
const movingEdge = isLeftEdge ? startRect.x + dx : startRect.y + dy;
|
|
236
|
-
const minSize = isLeftEdge ? FLOATING_PANEL_MIN_WIDTH : FLOATING_PANEL_MIN_HEIGHT;
|
|
237
|
-
const nextEdge = clamp(movingEdge, FLOATING_PANEL_MARGIN, fixedEdge - minSize);
|
|
238
|
-
setFloatRect(isLeftEdge ? { ...startRect, x: nextEdge, w: fixedEdge - nextEdge } : { ...startRect, y: nextEdge, h: fixedEdge - nextEdge });
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const right = clamp(startRect.x + startRect.w + dx, startRect.x + FLOATING_PANEL_MIN_WIDTH, window.innerWidth - FLOATING_PANEL_MARGIN);
|
|
243
|
-
const bottom = clamp(startRect.y + startRect.h + dy, startRect.y + FLOATING_PANEL_MIN_HEIGHT, window.innerHeight - FLOATING_PANEL_MARGIN);
|
|
244
|
-
setFloatRect({
|
|
245
|
-
...startRect,
|
|
246
|
-
w: floatInteraction.edge === 'bottom' ? startRect.w : right - startRect.x,
|
|
247
|
-
h: floatInteraction.edge === 'right' ? startRect.h : bottom - startRect.y,
|
|
248
|
-
});
|
|
298
|
+
setFloatRect(resizeFloatingPanelRect(floatInteraction.edge, startRect, dx, dy));
|
|
249
299
|
};
|
|
250
300
|
|
|
251
301
|
const onEnd = () => setFloatInteraction(null);
|
|
@@ -260,20 +310,6 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
260
310
|
};
|
|
261
311
|
}, [floatInteraction]);
|
|
262
312
|
|
|
263
|
-
const handleUrlSubmit = useCallback((e: React.FormEvent) => {
|
|
264
|
-
e.preventDefault();
|
|
265
|
-
if (!isDocsTab) return;
|
|
266
|
-
const input = urlInput.trim();
|
|
267
|
-
if (!input) return;
|
|
268
|
-
if (input.startsWith('/')) {
|
|
269
|
-
navigate(`${DOCS_DEFAULT_BASE_URL}${input}`);
|
|
270
|
-
} else if (input.startsWith('http')) {
|
|
271
|
-
navigate(input);
|
|
272
|
-
} else {
|
|
273
|
-
navigate(`${DOCS_DEFAULT_BASE_URL}/${input}`);
|
|
274
|
-
}
|
|
275
|
-
}, [urlInput, navigate, isDocsTab]);
|
|
276
|
-
|
|
277
313
|
const refreshIframe = useCallback(() => {
|
|
278
314
|
setIframeReloadVersion((version) => version + 1);
|
|
279
315
|
}, []);
|
|
@@ -318,10 +354,12 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
318
354
|
onToggleMode={toggleMode}
|
|
319
355
|
/>
|
|
320
356
|
|
|
321
|
-
<
|
|
322
|
-
|
|
357
|
+
<DocBrowserAddressToolbar
|
|
358
|
+
isVisible={isAddressToolbarTab}
|
|
359
|
+
onRefresh={refreshIframe}
|
|
323
360
|
onSubmit={handleUrlSubmit}
|
|
324
361
|
onUrlInputChange={setUrlInput}
|
|
362
|
+
placeholder={isDocsTab ? t('docBrowserSearchPlaceholder') : t('docBrowserAddressPlaceholder')}
|
|
325
363
|
urlInput={urlInput}
|
|
326
364
|
/>
|
|
327
365
|
|
|
@@ -338,7 +376,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
338
376
|
isResizing={floatInteraction?.kind === 'resize'}
|
|
339
377
|
/>
|
|
340
378
|
|
|
341
|
-
<DocBrowserExternalLink currentUrl={currentUrl}
|
|
379
|
+
<DocBrowserExternalLink currentUrl={currentUrl} isVisible={isDocsTab || isContentTab} />
|
|
342
380
|
</>
|
|
343
381
|
);
|
|
344
382
|
|
|
@@ -361,7 +399,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
361
399
|
className={cn(
|
|
362
400
|
'relative flex flex-col overflow-hidden bg-card text-card-foreground',
|
|
363
401
|
isFullscreen
|
|
364
|
-
? 'fixed inset-0 z-[
|
|
402
|
+
? 'fixed inset-0 z-[var(--z-floating-panel)] h-[100dvh] w-screen rounded-none border-0 shadow-2xl'
|
|
365
403
|
: 'rounded-2xl border border-border shadow-2xl',
|
|
366
404
|
)}
|
|
367
405
|
style={
|
|
@@ -373,7 +411,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
373
411
|
top: floatRect.y,
|
|
374
412
|
width: floatRect.w,
|
|
375
413
|
height: floatRect.h,
|
|
376
|
-
zIndex:
|
|
414
|
+
zIndex: 'var(--z-floating-panel)',
|
|
377
415
|
}
|
|
378
416
|
}
|
|
379
417
|
>
|
|
@@ -397,6 +435,21 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
397
435
|
data-testid="doc-browser-resize-bottom"
|
|
398
436
|
onPointerDown={startFloatResize('bottom')}
|
|
399
437
|
/>
|
|
438
|
+
<div
|
|
439
|
+
className="absolute top-0 left-0 z-30 h-4 w-4 cursor-nw-resize hover:bg-primary/10 transition-colors"
|
|
440
|
+
data-testid="doc-browser-resize-top-left"
|
|
441
|
+
onPointerDown={startFloatResize('top-left')}
|
|
442
|
+
/>
|
|
443
|
+
<div
|
|
444
|
+
className="absolute top-0 right-0 z-30 h-4 w-4 cursor-ne-resize hover:bg-primary/10 transition-colors"
|
|
445
|
+
data-testid="doc-browser-resize-top-right"
|
|
446
|
+
onPointerDown={startFloatResize('top-right')}
|
|
447
|
+
/>
|
|
448
|
+
<div
|
|
449
|
+
className="absolute bottom-0 left-0 z-30 h-4 w-4 cursor-sw-resize hover:bg-primary/10 transition-colors"
|
|
450
|
+
data-testid="doc-browser-resize-bottom-left"
|
|
451
|
+
onPointerDown={startFloatResize('bottom-left')}
|
|
452
|
+
/>
|
|
400
453
|
<div
|
|
401
454
|
className="absolute bottom-0 right-0 z-30 flex h-4 w-4 cursor-se-resize items-center justify-center text-muted-foreground/45 transition-colors hover:text-muted-foreground"
|
|
402
455
|
data-testid="doc-browser-resize-bottom-right"
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
|
|
28
28
|
const defaultDocBrowserRouteResolver = new DefaultDocBrowserRouteResolver();
|
|
29
29
|
|
|
30
|
+
type RightPanelOpenedHandler = () => void;
|
|
31
|
+
|
|
30
32
|
function updateTab(
|
|
31
33
|
state: DocBrowserState,
|
|
32
34
|
tabId: string,
|
|
@@ -258,7 +260,10 @@ function openDocBrowserState(
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
export class DocBrowserManager {
|
|
261
|
-
constructor(
|
|
263
|
+
constructor(
|
|
264
|
+
private readonly routeResolver: DocBrowserRouteResolver = defaultDocBrowserRouteResolver,
|
|
265
|
+
private readonly onRightPanelOpened?: RightPanelOpenedHandler,
|
|
266
|
+
) {}
|
|
262
267
|
|
|
263
268
|
private readonly setSnapshot = (next: DocBrowserStateUpdate): void => {
|
|
264
269
|
useDocBrowserStore.getState().setSnapshot(next);
|
|
@@ -266,6 +271,7 @@ export class DocBrowserManager {
|
|
|
266
271
|
|
|
267
272
|
readonly open = (url?: string, options?: DocBrowserOpenOptions): void => {
|
|
268
273
|
this.setSnapshot((prev) => openDocBrowserState(this.routeResolver, prev, url, options));
|
|
274
|
+
this.onRightPanelOpened?.();
|
|
269
275
|
};
|
|
270
276
|
|
|
271
277
|
readonly openTarget = (target: DocBrowserRouteTarget, options?: DocBrowserOpenOptions): void => {
|
|
@@ -275,6 +281,7 @@ export class DocBrowserManager {
|
|
|
275
281
|
kind: options?.kind ?? target.kind,
|
|
276
282
|
title: options?.title ?? target.title,
|
|
277
283
|
}));
|
|
284
|
+
this.onRightPanelOpened?.();
|
|
278
285
|
};
|
|
279
286
|
|
|
280
287
|
readonly openNewTab = (): void => {
|
|
@@ -287,6 +294,7 @@ export class DocBrowserManager {
|
|
|
287
294
|
|
|
288
295
|
readonly toggleMode = (): void => {
|
|
289
296
|
this.setSnapshot((prev) => ({ ...prev, mode: prev.mode === 'floating' ? 'docked' : 'floating' }));
|
|
297
|
+
this.onRightPanelOpened?.();
|
|
290
298
|
};
|
|
291
299
|
|
|
292
300
|
readonly navigate = (url: string): void => {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type FloatingPanelRect = { x: number; y: number; w: number; h: number };
|
|
2
|
+
export type FloatingPanelResizeEdge =
|
|
3
|
+
| 'left'
|
|
4
|
+
| 'right'
|
|
5
|
+
| 'top'
|
|
6
|
+
| 'bottom'
|
|
7
|
+
| 'top-left'
|
|
8
|
+
| 'top-right'
|
|
9
|
+
| 'bottom-left'
|
|
10
|
+
| 'bottom-right';
|
|
11
|
+
|
|
12
|
+
const FLOATING_PANEL_MARGIN = 40;
|
|
13
|
+
const FLOATING_PANEL_MIN_WIDTH = 360;
|
|
14
|
+
const FLOATING_PANEL_MIN_HEIGHT = 400;
|
|
15
|
+
|
|
16
|
+
function clamp(value: number, min: number, max: number): number {
|
|
17
|
+
return Math.min(max, Math.max(min, value));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resizeEdgeIncludes(
|
|
21
|
+
edge: FloatingPanelResizeEdge,
|
|
22
|
+
side: 'left' | 'right' | 'top' | 'bottom',
|
|
23
|
+
): boolean {
|
|
24
|
+
return edge.split('-').includes(side);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createInitialFloatingPanelRect(): FloatingPanelRect {
|
|
28
|
+
return {
|
|
29
|
+
x: Math.max(FLOATING_PANEL_MARGIN, window.innerWidth - 520),
|
|
30
|
+
y: 80,
|
|
31
|
+
w: 480,
|
|
32
|
+
h: 600,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function moveFloatingPanelRect(
|
|
37
|
+
startRect: FloatingPanelRect,
|
|
38
|
+
dx: number,
|
|
39
|
+
dy: number,
|
|
40
|
+
): FloatingPanelRect {
|
|
41
|
+
return {
|
|
42
|
+
...startRect,
|
|
43
|
+
x: clamp(
|
|
44
|
+
startRect.x + dx,
|
|
45
|
+
FLOATING_PANEL_MARGIN,
|
|
46
|
+
window.innerWidth - FLOATING_PANEL_MARGIN - startRect.w,
|
|
47
|
+
),
|
|
48
|
+
y: clamp(
|
|
49
|
+
startRect.y + dy,
|
|
50
|
+
FLOATING_PANEL_MARGIN,
|
|
51
|
+
window.innerHeight - FLOATING_PANEL_MARGIN - startRect.h,
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resizeFloatingPanelRect(
|
|
57
|
+
edge: FloatingPanelResizeEdge,
|
|
58
|
+
startRect: FloatingPanelRect,
|
|
59
|
+
dx: number,
|
|
60
|
+
dy: number,
|
|
61
|
+
): FloatingPanelRect {
|
|
62
|
+
const startRight = startRect.x + startRect.w;
|
|
63
|
+
const startBottom = startRect.y + startRect.h;
|
|
64
|
+
const nextLeft = resizeEdgeIncludes(edge, 'left')
|
|
65
|
+
? clamp(startRect.x + dx, FLOATING_PANEL_MARGIN, startRight - FLOATING_PANEL_MIN_WIDTH)
|
|
66
|
+
: startRect.x;
|
|
67
|
+
const nextRight = resizeEdgeIncludes(edge, 'right')
|
|
68
|
+
? clamp(startRight + dx, nextLeft + FLOATING_PANEL_MIN_WIDTH, window.innerWidth - FLOATING_PANEL_MARGIN)
|
|
69
|
+
: startRight;
|
|
70
|
+
const nextTop = resizeEdgeIncludes(edge, 'top')
|
|
71
|
+
? clamp(startRect.y + dy, FLOATING_PANEL_MARGIN, startBottom - FLOATING_PANEL_MIN_HEIGHT)
|
|
72
|
+
: startRect.y;
|
|
73
|
+
const nextBottom = resizeEdgeIncludes(edge, 'bottom')
|
|
74
|
+
? clamp(startBottom + dy, nextTop + FLOATING_PANEL_MIN_HEIGHT, window.innerHeight - FLOATING_PANEL_MARGIN)
|
|
75
|
+
: startBottom;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
x: nextLeft,
|
|
79
|
+
y: nextTop,
|
|
80
|
+
w: nextRight - nextLeft,
|
|
81
|
+
h: nextBottom - nextTop,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
} from '@/shared/components/ui/dialog';
|
|
8
|
+
|
|
9
|
+
describe('Dialog', () => {
|
|
10
|
+
it('renders modal layers above floating panels', () => {
|
|
11
|
+
render(
|
|
12
|
+
<Dialog open>
|
|
13
|
+
<DialogContent>
|
|
14
|
+
<DialogTitle>Authorize action</DialogTitle>
|
|
15
|
+
</DialogContent>
|
|
16
|
+
</Dialog>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const dialog = screen.getByRole('dialog');
|
|
20
|
+
const overlay = Array.from(document.body.querySelectorAll('[data-state="open"]'))
|
|
21
|
+
.find((element) => element instanceof HTMLElement && element.className.includes('--z-modal-backdrop'));
|
|
22
|
+
|
|
23
|
+
expect(dialog.className).toContain('z-[var(--z-modal,10050)]');
|
|
24
|
+
expect(overlay).toBeInstanceOf(HTMLElement);
|
|
25
|
+
expect((overlay as HTMLElement).className).toContain('z-[var(--z-modal-backdrop,10000)]');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -14,10 +14,22 @@ type IconActionButtonProps = Omit<
|
|
|
14
14
|
icon: React.ReactNode;
|
|
15
15
|
label: string;
|
|
16
16
|
tooltip?: string | false | null;
|
|
17
|
+
tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonProps>(
|
|
20
|
-
(
|
|
21
|
+
(
|
|
22
|
+
{
|
|
23
|
+
className,
|
|
24
|
+
disabled = false,
|
|
25
|
+
icon,
|
|
26
|
+
label,
|
|
27
|
+
tooltip,
|
|
28
|
+
tooltipSide = 'bottom',
|
|
29
|
+
...buttonProps
|
|
30
|
+
},
|
|
31
|
+
ref
|
|
32
|
+
) => {
|
|
21
33
|
const button = (
|
|
22
34
|
<button
|
|
23
35
|
{...buttonProps}
|
|
@@ -42,7 +54,7 @@ const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonPro
|
|
|
42
54
|
<TooltipTrigger asChild>
|
|
43
55
|
{disabled ? <span className="inline-flex">{button}</span> : button}
|
|
44
56
|
</TooltipTrigger>
|
|
45
|
-
<TooltipContent side=
|
|
57
|
+
<TooltipContent side={tooltipSide} className="text-xs">{content}</TooltipContent>
|
|
46
58
|
</Tooltip>
|
|
47
59
|
</TooltipProvider>
|
|
48
60
|
);
|
|
@@ -19,7 +19,7 @@ const DialogOverlay = React.forwardRef<
|
|
|
19
19
|
<DialogPrimitive.Overlay
|
|
20
20
|
ref={ref}
|
|
21
21
|
className={cn(
|
|
22
|
-
"fixed inset-0 z-
|
|
22
|
+
"fixed inset-0 z-[var(--z-modal-backdrop,10000)] bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
23
23
|
className
|
|
24
24
|
)}
|
|
25
25
|
{...props}
|
|
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
|
|
|
36
36
|
<DialogPrimitive.Content
|
|
37
37
|
ref={ref}
|
|
38
38
|
className={cn(
|
|
39
|
-
"fixed left-[50%] top-[50%] z-
|
|
39
|
+
"fixed left-[50%] top-[50%] z-[var(--z-modal,10050)] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl duration-base data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
|
|
40
40
|
className
|
|
41
41
|
)}
|
|
42
42
|
{...props}
|
|
@@ -28,7 +28,7 @@ const PopoverContent = React.forwardRef<
|
|
|
28
28
|
align={align}
|
|
29
29
|
collisionPadding={collisionPadding}
|
|
30
30
|
className={cn(
|
|
31
|
-
'z-[var(--z-popover,
|
|
31
|
+
'z-[var(--z-popover,10100)] w-72 overflow-x-hidden overflow-y-auto rounded-2xl border border-border bg-popover p-4 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
32
32
|
className
|
|
33
33
|
)}
|
|
34
34
|
style={{ maxHeight: POPOVER_CONTENT_MAX_HEIGHT, ...style }}
|
|
@@ -78,7 +78,7 @@ const SelectContent = React.forwardRef<
|
|
|
78
78
|
<SelectPrimitive.Content
|
|
79
79
|
ref={ref}
|
|
80
80
|
className={cn(
|
|
81
|
-
"relative z-[var(--z-popover,
|
|
81
|
+
"relative z-[var(--z-popover,10100)] flex max-h-96 min-w-[8rem] flex-col overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
82
82
|
position === "popper" &&
|
|
83
83
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
84
84
|
className
|
|
@@ -14,10 +14,11 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
|
14
14
|
type="button"
|
|
15
15
|
role="switch"
|
|
16
16
|
aria-checked={checked}
|
|
17
|
+
data-state={checked ? 'checked' : 'unchecked'}
|
|
17
18
|
ref={ref}
|
|
18
19
|
className={cn(
|
|
19
|
-
'peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2
|
|
20
|
-
checked ? 'bg-primary' : 'bg-muted hover:bg-accent',
|
|
20
|
+
'switch-track peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2 transition-colors duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',
|
|
21
|
+
checked ? 'border-primary/70 bg-primary' : 'border-border/60 bg-muted hover:bg-accent',
|
|
21
22
|
className
|
|
22
23
|
)}
|
|
23
24
|
onClick={() => onCheckedChange?.(!checked)}
|
|
@@ -26,7 +27,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
|
26
27
|
<span
|
|
27
28
|
data-state={checked ? 'checked' : 'unchecked'}
|
|
28
29
|
className={cn(
|
|
29
|
-
'pointer-events-none block h-5 w-5 rounded-full bg-card shadow-md ring-
|
|
30
|
+
'switch-thumb pointer-events-none block h-5 w-5 rounded-full bg-card shadow-md ring-1 ring-border/50 transition-transform duration-fast',
|
|
30
31
|
checked ? 'translate-x-5' : 'translate-x-0',
|
|
31
32
|
thumbClassName
|
|
32
33
|
)}
|
|
@@ -72,4 +72,58 @@ describe("CompactTabStrip", () => {
|
|
|
72
72
|
|
|
73
73
|
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
74
74
|
});
|
|
75
|
+
|
|
76
|
+
it("selects a tab when clicking the tab item outside the label button", () => {
|
|
77
|
+
const onSelect = vi.fn();
|
|
78
|
+
|
|
79
|
+
render(
|
|
80
|
+
<CompactTabStrip
|
|
81
|
+
tabs={[
|
|
82
|
+
{
|
|
83
|
+
key: "preview-tab",
|
|
84
|
+
label: "Preview",
|
|
85
|
+
active: false,
|
|
86
|
+
onSelect,
|
|
87
|
+
},
|
|
88
|
+
]}
|
|
89
|
+
actions={[]}
|
|
90
|
+
/>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const labelButton = screen.getByRole("button", { name: "Preview" });
|
|
94
|
+
const tabItem = labelButton.parentElement;
|
|
95
|
+
|
|
96
|
+
expect(tabItem).not.toBeNull();
|
|
97
|
+
fireEvent.click(tabItem!);
|
|
98
|
+
|
|
99
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("does not select a tab when clicking its close button", () => {
|
|
103
|
+
const onClose = vi.fn();
|
|
104
|
+
const onSelect = vi.fn();
|
|
105
|
+
|
|
106
|
+
render(
|
|
107
|
+
<CompactTabStrip
|
|
108
|
+
tabs={[
|
|
109
|
+
{
|
|
110
|
+
key: "preview-tab",
|
|
111
|
+
label: "Preview",
|
|
112
|
+
active: false,
|
|
113
|
+
closeLabel: "Close preview",
|
|
114
|
+
closePlacement: "leading-hover",
|
|
115
|
+
leadingIcon: <span />,
|
|
116
|
+
onClose,
|
|
117
|
+
onSelect,
|
|
118
|
+
},
|
|
119
|
+
]}
|
|
120
|
+
actions={[]}
|
|
121
|
+
/>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
fireEvent.click(screen.getByRole("button", { name: "Close preview" }));
|
|
125
|
+
|
|
126
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
127
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
75
129
|
});
|
|
@@ -50,10 +50,7 @@ type CompactTabStripProps = {
|
|
|
50
50
|
onScrollPointerDown?: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
function closeCompactTab(
|
|
54
|
-
event: React.MouseEvent<HTMLButtonElement>,
|
|
55
|
-
onClose: () => void,
|
|
56
|
-
) {
|
|
53
|
+
function closeCompactTab(event: React.MouseEvent<HTMLButtonElement>, onClose: () => void) {
|
|
57
54
|
event.stopPropagation();
|
|
58
55
|
onClose();
|
|
59
56
|
}
|
|
@@ -123,9 +120,11 @@ function CompactTabItem({
|
|
|
123
120
|
return (
|
|
124
121
|
<div
|
|
125
122
|
ref={itemRef}
|
|
123
|
+
data-compact-tab-item=""
|
|
124
|
+
onClick={(event) => event.target === event.currentTarget && tab.onSelect()}
|
|
126
125
|
className={cn(
|
|
127
126
|
tabBaseClassName ??
|
|
128
|
-
"group flex max-w-[180px] min-w-0 items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
|
|
127
|
+
"group flex max-w-[180px] min-w-0 cursor-pointer items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
|
|
129
128
|
tab.active
|
|
130
129
|
? (activeTabClassName ?? "border-b-primary bg-white text-gray-900")
|
|
131
130
|
: (inactiveTabClassName ??
|
|
@@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
|
|
|
18
18
|
ref={ref}
|
|
19
19
|
sideOffset={sideOffset}
|
|
20
20
|
className={cn(
|
|
21
|
-
'z-[var(--z-tooltip)] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
21
|
+
'z-[var(--z-tooltip,10150)] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
22
22
|
className
|
|
23
23
|
)}
|
|
24
24
|
{...props}
|