Test area!
@@ -17,20 +17,48 @@ import type { PenpotShape } from '@penpot/plugin-types';
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -83,6 +111,10 @@ export class AppComponent {
});
this.#sendMessage({ content: 'ready' });
+
+ effect(() => {
+ document.body.setAttribute('data-theme', this.theme());
+ });
}
close() {
diff --git a/libs/plugins-runtime/src/lib/api/index.ts b/libs/plugins-runtime/src/lib/api/index.ts
index 6625149..0b55b62 100644
--- a/libs/plugins-runtime/src/lib/api/index.ts
+++ b/libs/plugins-runtime/src/lib/api/index.ts
@@ -15,9 +15,9 @@ import type {
import { Manifest, Permissions } from '../models/manifest.model.js';
import { OpenUIOptions } from '../models/open-ui-options.model.js';
-import { setModalTheme } from '../create-modal.js';
import openUIApi from './openUI.api.js';
import z from 'zod';
+import type { PluginModalElement } from '../plugin-modal.js';
type Callback = (message: T) => void;
@@ -30,7 +30,7 @@ export const validEvents = [
export let uiMessagesCallbacks: Callback[] = [];
-let modal: HTMLElement | null = null;
+let modal: PluginModalElement | null = null;
const eventListeners: Map[]> = new Map();
@@ -45,7 +45,7 @@ export function triggerEvent(
message: EventsMap[keyof EventsMap]
) {
if (type === 'themechange' && modal) {
- setModalTheme(modal, message);
+ modal.setTheme(message);
}
const listeners = eventListeners.get(type) || [];
listeners.forEach((listener) => listener(message));
@@ -72,7 +72,7 @@ export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
open: (name: string, url: string, options: OpenUIOptions) => {
const theme = context.getTheme() as 'light' | 'dark';
modal = openUIApi(name, url, theme, options);
- setModalTheme(modal, theme);
+ modal.setTheme(theme);
modal.addEventListener('close', closePlugin, {
once: true,
diff --git a/libs/plugins-runtime/src/lib/api/openUI.api.ts b/libs/plugins-runtime/src/lib/api/openUI.api.ts
index d170acb..35a28f1 100644
--- a/libs/plugins-runtime/src/lib/api/openUI.api.ts
+++ b/libs/plugins-runtime/src/lib/api/openUI.api.ts
@@ -1,6 +1,6 @@
import z from 'zod';
-import { openUISchema } from '../models/open-ui-options.schema';
-import { createModal } from '../create-modal';
+import { openUISchema } from '../models/open-ui-options.schema.js';
+import { createModal } from '../create-modal.js';
export default z
.function()
diff --git a/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts b/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts
index b01a3e9..39fa139 100644
--- a/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts
+++ b/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts
@@ -1,9 +1,5 @@
import { expect, describe, vi } from 'vitest';
-import {
- createApi,
- triggerEvent,
- uiMessagesCallbacks,
-} from './index.js';
+import { createApi, triggerEvent, uiMessagesCallbacks } from './index.js';
import openUIApi from './openUI.api.js';
import { FileState } from '@penpot/plugin-types';
@@ -15,6 +11,7 @@ vi.mock('./openUI.api', () => {
removeEventListener: vi.fn(),
remove: vi.fn(),
setAttribute: vi.fn(),
+ setTheme: vi.fn(),
})),
};
});
@@ -256,9 +253,11 @@ describe('Plugin api', () => {
api.ui.open(name, url, options);
const modalMock = openUIApiMock.mock.results[0].value;
- expect(modalMock.setAttribute).toHaveBeenCalledWith('data-theme', 'light');
- expect(modalMock.setAttribute).toHaveBeenCalledTimes(1);
+ expect(modalMock.setTheme).toHaveBeenCalledWith('light');
expect(api.getTheme()).toBe('light');
+
+ triggerEvent('themechange', 'dark' as any);
+ expect(modalMock.setTheme).toHaveBeenCalledWith('dark');
});
it('close puglin', () => {
diff --git a/libs/plugins-runtime/src/lib/create-modal.ts b/libs/plugins-runtime/src/lib/create-modal.ts
index a4396e0..84eac04 100644
--- a/libs/plugins-runtime/src/lib/create-modal.ts
+++ b/libs/plugins-runtime/src/lib/create-modal.ts
@@ -1,10 +1,6 @@
-import { OpenUIOptions } from './models/open-ui-options.model';
-
+import type { OpenUIOptions } from './models/open-ui-options.model.js';
import type { PenpotTheme } from '@penpot/plugin-types';
-
-export function setModalTheme(modal: HTMLElement, theme: PenpotTheme) {
- modal.setAttribute('data-theme', theme);
-}
+import type { PluginModalElement } from './plugin-modal.js';
export function createModal(
name: string,
@@ -12,14 +8,14 @@ export function createModal(
theme: PenpotTheme,
options: OpenUIOptions
) {
- const modal = document.createElement('plugin-modal');
+ const modal = document.createElement('plugin-modal') as PluginModalElement;
- setModalTheme(modal, theme);
+ modal.setTheme(theme);
modal.setAttribute('title', name);
modal.setAttribute('iframe-src', url);
- modal.setAttribute('width', String(options.width || 300));
- modal.setAttribute('height', String(options.height || 400));
+ modal.setAttribute('width', String(options.width || 285));
+ modal.setAttribute('height', String(options.height || 540));
document.body.appendChild(modal);
diff --git a/libs/plugins-runtime/src/lib/plugin-modal.ts b/libs/plugins-runtime/src/lib/plugin-modal.ts
index b764bc8..17e4a3f 100644
--- a/libs/plugins-runtime/src/lib/plugin-modal.ts
+++ b/libs/plugins-runtime/src/lib/plugin-modal.ts
@@ -1,12 +1,22 @@
const closeSvg = `
`;
+import type { PenpotTheme } from '@penpot/plugin-types';
+
export class PluginModalElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
+ #wrapper: HTMLElement | null = null;
+
+ setTheme(theme: PenpotTheme) {
+ if (this.#wrapper) {
+ this.#wrapper.setAttribute('data-theme', theme);
+ }
+ }
+
connectedCallback() {
const title = this.getAttribute('title');
const iframeSrc = this.getAttribute('iframe-src');
@@ -21,6 +31,9 @@ export class PluginModalElement extends HTMLElement {
throw new Error('Error creating shadow root');
}
+ this.#wrapper = document.createElement('div');
+ this.#wrapper.classList.add('wrapper');
+
const header = document.createElement('div');
header.classList.add('header');
@@ -67,52 +80,75 @@ export class PluginModalElement extends HTMLElement {
iframe.contentWindow.postMessage((e as CustomEvent).detail, '*');
});
- this.shadowRoot.appendChild(header);
- this.shadowRoot.appendChild(iframe);
+ this.shadowRoot.appendChild(this.#wrapper);
+
+ this.#wrapper.appendChild(header);
+ this.#wrapper.appendChild(iframe);
const style = document.createElement('style');
style.textContent = `
:host {
+ --spacing-4: 0.25rem;
+ --spacing-8: calc(var(--spacing-4) * 2);
+ --spacing-12: calc(var(--spacing-4) * 3);
+ --spacing-16: calc(var(--spacing-4) * 4);
+ --spacing-20: calc(var(--spacing-4) * 5);
+ --spacing-24: calc(var(--spacing-4) * 6);
+ --spacing-28: calc(var(--spacing-4) * 7);
+ --spacing-32: calc(var(--spacing-4) * 8);
+ --spacing-36: calc(var(--spacing-4) * 9);
+ --spacing-40: calc(var(--spacing-4) * 10);
+
+ --font-weight-regular: 400;
+ --font-weight-bold: 500;
+ --font-line-height-s: 1.2;
+ --font-line-height-m: 1.4;
+ --font-line-height-l: 1.5;
+ --font-size-s: 12px;
+ --font-size-m: 14px;
+ --font-size-l: 16px;
+ }
+
+ [data-theme] {
+ background-color: var(--color-background-primary);
+ color: var(--color-foreground-secondary);
+ }
+
+ .wrapper {
display: flex;
flex-direction: column;
position: fixed;
inset-block-end: 10px;
inset-inline-start: 10px;
z-index: 1000;
- padding: 20px;
- border-radius: 20px;
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+ padding: 25px;
+ border-radius: 15px;
+ box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
inline-size: ${width}px;
block-size: ${height}px;
}
- :host([data-theme="dark"]) {
- background: #2e3434;
- border: 1px solid #2e3434;
- color: #ffffff;
- }
-
- :host([data-theme="light"]) {
- background: #ffffff;
- border: 1px solid #eef0f2;
- color: #18181a;
- }
-
.header {
+ align-items: center;
display: flex;
justify-content: space-between;
+ border-block-end: 2px solid var(--color-background-quaternary);
+ padding-block-end: var(--spacing-4);
+ margin-block-end: var(--spacing-20);
}
button {
background: transparent;
border: 0;
cursor: pointer;
+ padding: 0;
}
h1 {
- font-family: Arial, sans-serif;
+ font-size: var(--font-size-s);
+ font-weight: var(--font-weight-bold);
margin: 0;
- margin-block-end: 10px;
+ margin-inline-end: var(--spacing-4);
}
iframe {
diff --git a/libs/plugins-styles/src/lib/core/generic.css b/libs/plugins-styles/src/lib/core/generic.css
index 9d885ee..5aca3e4 100644
--- a/libs/plugins-styles/src/lib/core/generic.css
+++ b/libs/plugins-styles/src/lib/core/generic.css
@@ -33,11 +33,42 @@ ul {
}
[data-theme='dark'] {
- background-color: var(--db-quaternary);
- color: var(--lb-primary);
+ color-scheme: dark;
+
+ --background-primary: var(--db-primary);
+ --background-secondary: var(--db-secondary);
+ --background-tertiary: var(--db-tertiary);
+ --background-quaternary: var(--db-quaternary);
+
+ --foreground-primary: var(--df-primary);
+ --foreground-secondary: var(--df-secondary);
+
+ --accent-primary: var(--da-primary);
+ --accent-primary-muted: var(--da-primary-muted);
+ --accent-secondary: var(--da-secondary);
+ --accent-tertiary: var(--da-tertiary);
+ --accent-quaternary: var(--da-quaternary);
}
[data-theme='light'] {
- background-color: var(--lb-primary);
- color: var(--db-primary);
+ color-scheme: light;
+
+ --background-primary: var(--lb-primary);
+ --background-secondary: var(--lb-secondary);
+ --background-tertiary: var(--lb-tertiary);
+ --background-quaternary: var(--lb-quaternary);
+
+ --foreground-primary: var(--lf-primary);
+ --foreground-secondary: var(--lf-secondary);
+
+ --accent-primary: var(--la-primary);
+ --accent-primary-muted: var(--la-primary-muted);
+ --accent-secondary: var(--la-secondary);
+ --accent-tertiary: var(--la-tertiary);
+ --accent-quaternary: var(--la-quaternary);
+}
+
+[data-theme] {
+ background-color: var(--background-primary);
+ color: var(--foreground-secondary);
}