mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
fix(web): portal plugin details modal (#3065)
Signed-off-by: jaehanbyun <awbrg789@naver.com>
This commit is contained in:
parent
39ae2cbc57
commit
62972f14a3
2 changed files with 104 additions and 17 deletions
|
|
@ -19,6 +19,7 @@
|
|||
// same callback wiring.
|
||||
|
||||
import type { InstalledPluginRecord } from '@open-design/contracts';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { inferPluginPreview } from './plugins-home/preview';
|
||||
import { PluginScenarioDetail } from './plugin-details/PluginScenarioDetail';
|
||||
import { PluginExampleDetail } from './plugin-details/PluginExampleDetail';
|
||||
|
|
@ -39,9 +40,10 @@ export function PluginDetailsModal({
|
|||
isApplying,
|
||||
}: Props) {
|
||||
const preview = inferPluginPreview(record);
|
||||
let detail: JSX.Element;
|
||||
|
||||
if (preview.kind === 'media') {
|
||||
return (
|
||||
detail = (
|
||||
<PluginMediaDetail
|
||||
record={record}
|
||||
onClose={onClose}
|
||||
|
|
@ -49,10 +51,8 @@ export function PluginDetailsModal({
|
|||
isApplying={isApplying}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (preview.kind === 'html') {
|
||||
return (
|
||||
} else if (preview.kind === 'html') {
|
||||
detail = (
|
||||
<PluginExampleDetail
|
||||
record={record}
|
||||
exampleStem={
|
||||
|
|
@ -63,10 +63,8 @@ export function PluginDetailsModal({
|
|||
isApplying={isApplying}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (preview.kind === 'design') {
|
||||
return (
|
||||
} else if (preview.kind === 'design') {
|
||||
detail = (
|
||||
<PluginDesignSystemDetail
|
||||
record={record}
|
||||
onClose={onClose}
|
||||
|
|
@ -74,14 +72,17 @@ export function PluginDetailsModal({
|
|||
isApplying={isApplying}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
detail = (
|
||||
<PluginScenarioDetail
|
||||
record={record}
|
||||
onClose={onClose}
|
||||
onUse={onUse}
|
||||
isApplying={isApplying}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PluginScenarioDetail
|
||||
record={record}
|
||||
onClose={onClose}
|
||||
onUse={onUse}
|
||||
isApplying={isApplying}
|
||||
/>
|
||||
);
|
||||
if (typeof document === 'undefined') return detail;
|
||||
return createPortal(detail, document.body);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import type { InstalledPluginRecord } from '@open-design/contracts';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { PluginDetailsModal } from '../../src/components/PluginDetailsModal';
|
||||
import { I18nProvider } from '../../src/i18n';
|
||||
|
||||
function makePlugin(
|
||||
id: string,
|
||||
preview?: Record<string, unknown>,
|
||||
): InstalledPluginRecord {
|
||||
return {
|
||||
id,
|
||||
title: id,
|
||||
version: '0.1.0',
|
||||
sourceKind: 'bundled',
|
||||
source: '/tmp',
|
||||
trust: 'bundled',
|
||||
capabilitiesGranted: [],
|
||||
manifest: {
|
||||
name: id,
|
||||
version: '0.1.0',
|
||||
title: id,
|
||||
od: {
|
||||
kind: 'scenario',
|
||||
...(preview ? { preview } : {}),
|
||||
useCase: {
|
||||
query: 'Generate a preview.',
|
||||
},
|
||||
},
|
||||
},
|
||||
fsPath: '/tmp',
|
||||
installedAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function renderInsideStackingContext(record: InstalledPluginRecord) {
|
||||
const host = document.createElement('div');
|
||||
host.className = 'composer';
|
||||
document.body.appendChild(host);
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<div className="composer-shell">
|
||||
<PluginDetailsModal record={record} onClose={() => {}} onUse={() => {}} />
|
||||
</div>
|
||||
</I18nProvider>,
|
||||
{ container: host },
|
||||
);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
describe('PluginDetailsModal layering', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('portals rich preview details to the document body so sticky chat and workspace headers cannot cover them', () => {
|
||||
const host = renderInsideStackingContext(
|
||||
makePlugin('video-plugin', {
|
||||
type: 'video',
|
||||
poster: 'https://cdn.example/poster.jpg',
|
||||
video: 'https://cdn.example/clip.mp4',
|
||||
}),
|
||||
);
|
||||
|
||||
const backdrop = document.body.querySelector('.ds-modal-backdrop');
|
||||
expect(backdrop).toBeTruthy();
|
||||
expect(backdrop?.parentElement).toBe(document.body);
|
||||
expect(host.querySelector('.ds-modal-backdrop')).toBeNull();
|
||||
});
|
||||
|
||||
it('portals fallback scenario details through the same top-level layer', () => {
|
||||
const host = renderInsideStackingContext(makePlugin('scenario-plugin'));
|
||||
|
||||
const backdrop = document.body.querySelector('.plugin-details-modal-backdrop');
|
||||
expect(backdrop).toBeTruthy();
|
||||
expect(backdrop?.parentElement).toBe(document.body);
|
||||
expect(host.querySelector('.plugin-details-modal-backdrop')).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue