Skip to content

Commit c90a111

Browse files
authored
feat: popover plugin support (#475)
1 parent 5729c25 commit c90a111

File tree

9 files changed

+146
-2
lines changed

9 files changed

+146
-2
lines changed

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@spectrum-web-components/menu": "0.43.0",
5454
"@spectrum-web-components/overlay": "0.43.0",
5555
"@spectrum-web-components/picker": "0.43.0",
56+
"@spectrum-web-components/popover": "0.43.0",
5657
"@spectrum-web-components/progress-circle": "0.43.0",
5758
"@spectrum-web-components/search": "0.43.0",
5859
"@spectrum-web-components/status-light": "0.43.0",

src/extension/app/components/plugin/plugin-action-bar.css.js

+12
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ export const style = css`
143143
opacity: 0.5;
144144
}
145145
146+
action-bar overlay-trigger sp-popover .content {
147+
padding: 10px;
148+
height: 100%;
149+
}
150+
151+
action-bar overlay-trigger sp-popover .content iframe {
152+
width: 100%;
153+
height: 100%;
154+
border: 0;
155+
color-scheme: auto;
156+
}
157+
146158
#plugin-menu sp-menu-group [slot="header"] {
147159
text-transform: uppercase;
148160
font-size: var(--spectrum-global-dimension-font-size-75);

src/extension/app/components/plugin/plugin-action-bar.js

+7
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ export class PluginActionBar extends ConnectedElement {
147147
},
148148
);
149149

150+
reaction(
151+
() => this.appStore.theme,
152+
async () => {
153+
this.setupPlugins();
154+
},
155+
);
156+
150157
reaction(
151158
() => this.appStore.bulkStore.selection,
152159
() => {

src/extension/app/components/plugin/plugin.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/* eslint-disable max-len */
1414

1515
import { html } from 'lit';
16+
import { ifDefined } from 'lit/directives/if-defined.js';
1617
import { EXTERNAL_EVENTS } from '../../constants.js';
1718

1819
/**
@@ -150,6 +151,14 @@ export class Plugin {
150151
return this.config.isBadge;
151152
}
152153

154+
/**
155+
* Is this plugin a popover?
156+
* @returns {boolean} True if the plugin is a popover
157+
*/
158+
isPopover() {
159+
return this.config.isPopover;
160+
}
161+
153162
/**
154163
* Adds a plugin to this plugin's children.
155164
* @param {Plugin} plugin The plugin to add
@@ -257,13 +266,44 @@ export class Plugin {
257266
`;
258267
}
259268

269+
if (this.isPopover() && this.config.url) {
270+
const {
271+
url, popoverRect, title, titleI18n,
272+
} = this.config;
273+
274+
const popoverTitle = titleI18n?.[this.appStore.siteStore.lang] || title;
275+
const src = new URL(url);
276+
src.searchParams.set('theme', this.appStore.theme);
277+
278+
let filteredPopoverRect = popoverRect;
279+
if (popoverRect) {
280+
filteredPopoverRect = `${popoverRect
281+
.split(';')
282+
.map((s) => s.trim())
283+
.filter((s) => s.startsWith('width:') || s.startsWith('height:'))
284+
.join('; ')};`;
285+
}
286+
287+
return html`
288+
<overlay-trigger receivesFocus="true" offset="-3">
289+
<sk-action-button quiet slot="trigger">${this.getButtonText()}</sk-action-button>
290+
<sp-popover slot="click-content" placement="top" tip style=${ifDefined(filteredPopoverRect)}>
291+
<div class="content">
292+
<iframe title=${popoverTitle || 'Popover content'} src=${src}></iframe>
293+
</div>
294+
</sp-popover>
295+
</overlay-trigger>`;
296+
}
297+
260298
return html`
261299
<sk-action-button
262300
class=${this.getId()}
263301
.disabled=${!this.isEnabled()}
264302
quiet
265303
@click=${(evt) => this.onButtonClick(evt)}
266-
>${this.getButtonText()}</sk-action-button>
304+
>
305+
${this.getButtonText()}
306+
</sk-action-button>
267307
`;
268308
}
269309

src/extension/app/store/app.js

+7
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ export class AppStore {
337337
passConfig,
338338
passReferrer,
339339
isPalette,
340+
isPopover,
341+
popoverRect,
340342
event: eventName,
341343
environments,
342344
excludePaths,
@@ -378,6 +380,8 @@ export class AppStore {
378380
const plugin = {
379381
custom: true,
380382
id,
383+
title,
384+
titleI18n,
381385
condition,
382386
button: {
383387
text: (titleI18n && titleI18n[lang]) || title,
@@ -417,7 +421,10 @@ export class AppStore {
417421
pinned,
418422
confirm,
419423
container: containerId,
424+
url,
420425
isBadge,
426+
isPopover,
427+
popoverRect,
421428
badgeVariant,
422429
};
423430

src/extension/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import '@spectrum-web-components/divider/sp-divider.js';
1818
import '@spectrum-web-components/dialog/sp-dialog-base.js';
1919
import '@spectrum-web-components/menu/sp-menu-divider.js';
2020
import '@spectrum-web-components/overlay/sp-overlay.js';
21+
import '@spectrum-web-components/overlay/overlay-trigger.js';
22+
import '@spectrum-web-components/popover/sp-popover.js';
2123
import '@spectrum-web-components/picker/sp-picker.js';
2224
import '@spectrum-web-components/status-light/sp-status-light.js';
2325
import '@spectrum-web-components/switch/sp-switch.js';

src/extension/types/typedefs.js

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
* @prop {boolean} [isContainer] Determines whether to turn this plugin into a dropdown
110110
* @prop {boolean} [isPalette] Determines whether a URL is opened in a palette instead of a new tab
111111
* @prop {string} [paletteRect] The dimensions and position of a palette (optional)
112+
* @prop {boolean} [isPopover] Determines whether a URL is opened in a popover instead of a new tab
113+
* @prop {string} [popoverRect] The dimensions of a popover (optional)
112114
* @prop {boolean} [isBadge] Determines whether the plugin is a badge (optional)
113115
* @prop {string} [badgeVariant] The color variant of the badge (optional)
114116
* @prop {string[]} [environments] Specifies when to show this plugin (admin, edit, dev, preview, live, prod)

test/app/components/plugin/plugin.test.js

+73
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/* eslint-disable no-unused-expressions, import/no-extraneous-dependencies */
1313

1414
import { expect } from '@open-wc/testing';
15+
import { render } from 'lit';
1516
import chromeMock from '../../../mocks/chrome.js';
1617
import { Plugin } from '../../../../src/extension/app/components/plugin/plugin.js';
1718
import { AppStore } from '../../../../src/extension/app/store/app.js';
@@ -48,6 +49,18 @@ const TEST_BADGE_CONFIG = {
4849
},
4950
};
5051

52+
const TEST_POPOVER_CONFIG = {
53+
id: 'test',
54+
title: 'Test Popover',
55+
isPopover: true,
56+
popoverRect: 'width: 100px; height: 100px;',
57+
url: 'https://labs.aem.live/tools/snapshot-admin/palette.html?foo=bar',
58+
button: {
59+
text: 'Test Child',
60+
action: () => {},
61+
},
62+
};
63+
5164
describe('Plugin', () => {
5265
const appStore = new AppStore();
5366

@@ -124,6 +137,66 @@ describe('Plugin', () => {
124137
parent.append(child);
125138
});
126139

140+
it('renders a popover plugin', async () => {
141+
appStore.theme = 'dark';
142+
const plugin = new Plugin({ ...TEST_POPOVER_CONFIG }, appStore);
143+
144+
const container = document.createElement('div');
145+
render(plugin.render(), container);
146+
147+
// Wait for next time to let lit process the update
148+
await Promise.resolve();
149+
150+
const overlayTrigger = container.querySelector('overlay-trigger');
151+
expect(overlayTrigger).to.exist;
152+
expect(overlayTrigger.getAttribute('offset')).to.equal('-3');
153+
154+
const popover = container.querySelector('sp-popover');
155+
expect(popover).to.exist;
156+
expect(popover.getAttribute('placement')).to.equal('top');
157+
158+
const popoverStyle = popover.getAttribute('style');
159+
expect(popoverStyle).to.include('width: 100px; height: 100px;');
160+
161+
const iframe = container.querySelector('iframe');
162+
expect(iframe).to.exist;
163+
expect(iframe.getAttribute('title')).to.equal(TEST_POPOVER_CONFIG.title);
164+
expect(iframe.getAttribute('src')).to.include(TEST_POPOVER_CONFIG.url);
165+
expect(iframe.getAttribute('src')).to.include('?foo=bar&theme=dark');
166+
});
167+
168+
it('renders a popover plugin with filtered popoverRect', async () => {
169+
const config = { ...TEST_POPOVER_CONFIG };
170+
config.popoverRect = 'width: 100px; height: 100px; background-color: red;';
171+
const plugin = new Plugin({ ...config }, appStore);
172+
173+
const container = document.createElement('div');
174+
render(plugin.render(), container);
175+
176+
// Wait for next time to let lit process the update
177+
await Promise.resolve();
178+
179+
const popover = container.querySelector('sp-popover');
180+
const popoverStyle = popover.getAttribute('style');
181+
expect(popoverStyle).to.include('width: 100px; height: 100px;');
182+
});
183+
184+
it('renders a popover without a popoverRect', async () => {
185+
const config = { ...TEST_POPOVER_CONFIG };
186+
config.popoverRect = undefined;
187+
const plugin = new Plugin({ ...config }, appStore);
188+
189+
const container = document.createElement('div');
190+
render(plugin.render(), container);
191+
192+
// Wait for next time to let lit process the update
193+
await Promise.resolve();
194+
195+
const popover = container.querySelector('sp-popover');
196+
const popoverStyle = popover.getAttribute('style');
197+
expect(popoverStyle).to.be.null;
198+
});
199+
127200
it('does not render if not pinned', async () => {
128201
const plugin = new Plugin({
129202
id: 'test',

0 commit comments

Comments
 (0)