diff --git a/apps/contrast-plugin/.babelrc b/apps/contrast-plugin/.babelrc
deleted file mode 100644
index f2f3806..0000000
--- a/apps/contrast-plugin/.babelrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "presets": ["@nx/js/babel"]
-}
diff --git a/apps/contrast-plugin/.swcrc b/apps/contrast-plugin/.swcrc
deleted file mode 100644
index a2d5b04..0000000
--- a/apps/contrast-plugin/.swcrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "jsc": {
- "parser": {
- "syntax": "typescript"
- },
- "target": "es2016"
- }
-}
diff --git a/apps/contrast-plugin/eslint.config.js b/apps/contrast-plugin/eslint.config.js
index ad49d00..29c2176 100644
--- a/apps/contrast-plugin/eslint.config.js
+++ b/apps/contrast-plugin/eslint.config.js
@@ -1,25 +1,43 @@
import baseConfig from '../../eslint.config.js';
-import typescriptEslintParser from '@typescript-eslint/parser';
+import { compat } from '../../eslint.base.config.js';
export default [
...baseConfig,
- {
- languageOptions: {
- parser: typescriptEslintParser,
- parserOptions: { project: './apps/contrast-plugin/tsconfig.app.json' },
- },
- },
- {
- files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
- rules: {},
- },
- {
- files: ['**/*.ts', '**/*.tsx'],
- rules: {},
- },
- {
- files: ['**/*.js', '**/*.jsx'],
- rules: {},
- },
- { ignores: ['vite.config.ts'] },
+ ...compat
+ .config({
+ extends: [
+ 'plugin:@nx/angular',
+ 'plugin:@angular-eslint/template/process-inline-templates',
+ ],
+ })
+ .map((config) => ({
+ ...config,
+ files: ['**/*.ts'],
+ rules: {
+ '@angular-eslint/directive-selector': [
+ 'error',
+ {
+ type: 'attribute',
+ prefix: 'app',
+ style: 'camelCase',
+ },
+ ],
+ '@angular-eslint/component-selector': [
+ 'error',
+ {
+ type: 'element',
+ prefix: 'app',
+ style: 'kebab-case',
+ },
+ ],
+ },
+ })),
+ ...compat
+ .config({ extends: ['plugin:@nx/angular-template'] })
+ .map((config) => ({
+ ...config,
+ files: ['**/*.html'],
+ rules: {},
+ })),
+ { ignores: ['**/assets/*.js'] },
];
diff --git a/apps/contrast-plugin/project.json b/apps/contrast-plugin/project.json
index 497af03..20bd11f 100644
--- a/apps/contrast-plugin/project.json
+++ b/apps/contrast-plugin/project.json
@@ -2,7 +2,92 @@
"name": "contrast-plugin",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
+ "prefix": "app",
"sourceRoot": "apps/contrast-plugin/src",
"tags": ["type:plugin"],
- "targets": {}
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:application",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/contrast-plugin",
+ "index": "apps/contrast-plugin/src/index.html",
+ "browser": "apps/contrast-plugin/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/contrast-plugin/tsconfig.app.json",
+ "assets": [
+ "apps/contrast-plugin/src/favicon.ico",
+ "apps/contrast-plugin/src/assets"
+ ],
+ "styles": [
+ "libs/plugins-styles/src/lib/styles.css",
+ "apps/contrast-plugin/src/styles.css"
+ ],
+ "scripts": [],
+ "optimization": {
+ "scripts": true,
+ "styles": true,
+ "fonts": false
+ }
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "contrast-plugin:build:production"
+ },
+ "development": {
+ "buildTarget": "contrast-plugin:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "contrast-plugin:build"
+ }
+ },
+ "buildPlugin": {
+ "executor": "@nx/esbuild:esbuild",
+ "outputs": [
+ "{options.outputPath}"
+ ],
+ "options": {
+ "minify": true,
+ "outputPath": "apps/contrast-plugin/src/assets/",
+ "main": "apps/contrast-plugin/src/plugin.ts",
+ "tsConfig": "apps/contrast-plugin/tsconfig.plugin.json",
+ "generatePackageJson": false,
+ "format": [
+ "esm"
+ ],
+ "deleteOutputPath": false
+ }
+ },
+ }
}
diff --git a/apps/contrast-plugin/public/manifest.json b/apps/contrast-plugin/public/manifest.json
deleted file mode 100644
index f504ea4..0000000
--- a/apps/contrast-plugin/public/manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "Contrast plugin",
- "host": "http://localhost:4302",
- "code": "/plugin.js",
- "icon": "/icon.png",
- "permissions": [
- "page:read",
- "file:read",
- "selection:read"
- ]
-}
diff --git a/apps/contrast-plugin/src/app/app.component.css b/apps/contrast-plugin/src/app/app.component.css
new file mode 100644
index 0000000..f911b90
--- /dev/null
+++ b/apps/contrast-plugin/src/app/app.component.css
@@ -0,0 +1,77 @@
+.wrapper {
+ padding-block-start: var(--spacing-24);
+}
+
+.bold {
+ font-weight: 600;
+}
+
+.contrast-preview {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-8);
+ padding-block-end: var(--spacing-20);
+ border-block-end: 2px solid var(--background-quaternary);
+}
+
+.color-box {
+ block-size: 66px;
+ border-radius: var(--spacing-8);
+ background: linear-gradient(
+ to right,
+ var(--color1) 0%,
+ var(--color1) 50%,
+ var(--color2) 50%,
+ var(--color2) 100%
+ );
+}
+
+.select-colors {
+ display: flex;
+ justify-content: space-between;
+}
+
+.contrast-ratio {
+ padding-block: var(--spacing-24);
+
+ span {
+ color: var(--foreground-primary);
+ }
+}
+
+.contrast-results {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-16);
+}
+
+.contrast-result {
+ .title {
+ margin-block-end: var(--spacing-4);
+ }
+ .list {
+ display: flex;
+ gap: var(--spacing-8);
+ }
+}
+
+.tag {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ inline-size: 42px;
+ block-size: 32px;
+ color: var(--app-white);
+ border: 1px solid transparent;
+ border-radius: var(--spacing-8);
+
+ &.good {
+ background-color: var(--success-950);
+ border-color: var(--success-500);
+ }
+
+ &.fail {
+ background-color: var(--error-950);
+ border-color: var(--error-700);
+ }
+}
diff --git a/apps/contrast-plugin/src/app/app.component.ts b/apps/contrast-plugin/src/app/app.component.ts
new file mode 100644
index 0000000..1748ff3
--- /dev/null
+++ b/apps/contrast-plugin/src/app/app.component.ts
@@ -0,0 +1,231 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ inject,
+} from '@angular/core';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { ActivatedRoute } from '@angular/router';
+import type {
+ PluginMessageEvent,
+ PluginUIEvent,
+ ThemePluginEvent,
+} from '../model';
+import { filter, fromEvent, map, merge, take } from 'rxjs';
+import { CommonModule } from '@angular/common';
+import { PenpotShape } from '@penpot/plugin-types';
+
+@Component({
+ standalone: true,
+ imports: [CommonModule],
+ selector: 'app-root',
+ template: `
+
+ @if (selection().length === 0) {
+
+ Select two filled shapes to calculate the color contrast between them.
+
+ } @else if (selection().length === 1) {
+
+ Select one more filled shape to calculate the
+ color contrast between the selected colors.
+
+ } @else if (selection().length >= 2) {
+
+
Selected colors:
+
+
+ -
+ {{ color1() }}
+
+ - {{ color2() }}
+
+
+
+ Contrast ratio: {{ result() }} : 1
+
+
+
+
Normal text:
+
+ - = contrastStandards.AA.normal ? 'good' : 'fail'
+ "
+ >
+ AA
+
+ - = contrastStandards.AAA.normal ? 'good' : 'fail'
+ "
+ >
+ AAA
+
+
+
+
+
+ Large text
+ (starting from 19px bold or 24px):
+
+
+ - = contrastStandards.AA.large ? 'good' : 'fail'
+ "
+ >
+ AA
+
+ - = contrastStandards.AAA.large ? 'good' : 'fail'
+ "
+ >
+ AAA
+
+
+
+
+
+ Graphics (such as form input borders):
+
+
+ - = contrastStandards.graphics ? 'good' : 'fail'
+ "
+ >
+ AA
+
+
+
+
+ }
+
+ `,
+ styleUrl: './app.component.css',
+ host: {
+ '[attr.data-theme]': 'theme()',
+ '[style.--color1]': 'color1()',
+ '[style.--color2]': 'color2()',
+ },
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AppComponent {
+ #route = inject(ActivatedRoute);
+ #messages$ = fromEvent>(window, 'message');
+
+ #initialTheme$ = this.#route.queryParamMap.pipe(
+ map((params) => params.get('theme')),
+ filter((theme) => !!theme),
+ take(1)
+ );
+
+ selection = toSignal(
+ this.#messages$.pipe(
+ filter(
+ (event) => event.data.type === 'init' || event.data.type === 'selection'
+ ),
+ map((event) => {
+ if (event.data.type === 'init') {
+ return event.data.content.selection;
+ } else if (event.data.type === 'selection') {
+ return event.data.content;
+ }
+
+ return [];
+ }),
+ map((shapes) => {
+ return shapes
+ .map((shape) => this.#getShapeColor(shape))
+ .filter((color): color is string => !!color);
+ })
+ ),
+ {
+ initialValue: [],
+ }
+ );
+
+ theme = toSignal(
+ merge(
+ this.#initialTheme$,
+ this.#messages$.pipe(
+ map((event) => event.data),
+ filter((data): data is ThemePluginEvent => data.type === 'theme'),
+ map((data) => {
+ return data.content;
+ })
+ )
+ )
+ );
+
+ color1 = computed(() => {
+ return this.selection().at(-2);
+ });
+
+ color2 = computed(() => {
+ return this.selection().at(-1);
+ });
+
+ result = computed(() => {
+ const color1 = this.color1();
+ const color2 = this.color2();
+
+ if (!color1 || !color2) {
+ return 0;
+ }
+
+ const lum1 = this.#getLuminosity(color1) + 0.05;
+ const lum2 = this.#getLuminosity(color2) + 0.05;
+
+ const result = lum1 > lum2 ? lum1 / lum2 : lum2 / lum1;
+
+ return Number(result.toFixed(2));
+ });
+
+ contrastStandards = {
+ AA: {
+ normal: 4.5,
+ large: 3,
+ },
+ AAA: {
+ normal: 7,
+ large: 4.5,
+ },
+ graphics: 3,
+ } as const;
+
+ constructor() {
+ this.#sendMessage({ type: 'ready' });
+ }
+
+ #getLuminosity(color: string) {
+ const rgb = this.#hexToRgb(color);
+ const a = rgb.map((v) => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
+ }
+
+ #hexToRgb(hex: string) {
+ const r = parseInt(hex.slice(1, 3), 16);
+ const g = parseInt(hex.slice(3, 5), 16);
+ const b = parseInt(hex.slice(5, 7), 16);
+ return [r, g, b];
+ }
+
+ #getShapeColor(shape?: PenpotShape): string | undefined {
+ return shape?.fills?.[0]?.fillColor ?? shape?.strokes?.[0]?.strokeColor;
+ }
+
+ #sendMessage(message: PluginUIEvent) {
+ parent.postMessage(message, '*');
+ }
+}
diff --git a/apps/contrast-plugin/src/app/app.config.ts b/apps/contrast-plugin/src/app/app.config.ts
new file mode 100644
index 0000000..1b3e4af
--- /dev/null
+++ b/apps/contrast-plugin/src/app/app.config.ts
@@ -0,0 +1,6 @@
+import { ApplicationConfig } from '@angular/core';
+import { provideRouter } from '@angular/router';
+
+export const appConfig: ApplicationConfig = {
+ providers: [provideRouter([])],
+};
diff --git a/apps/contrast-plugin/src/app/app.element.css b/apps/contrast-plugin/src/app/app.element.css
deleted file mode 100644
index 542fb3e..0000000
--- a/apps/contrast-plugin/src/app/app.element.css
+++ /dev/null
@@ -1,101 +0,0 @@
-.wrapper {
- &[data-theme='dark'] {
- color: var(--app-white);
- }
-
- &[data-theme='light'] {
- color: var(--app-black);
- }
-}
-
-.color {
- align-items: center;
- display: flex;
- margin-inline-end: var(--spacing-16);
-}
-
-.color-preview {
- block-size: var(--spacing-36);
- border: 1px solid var(--df-secondary);
- border-radius: var(--spacing-4);
- display: block;
- inline-size: var(--spacing-36);
- margin-inline-end: var(--spacing-16);
-}
-
-.fail {
- background-color: var(--error-500);
-}
-
-.good {
- background-color: var(--success-500);
-}
-
-.title {
- margin-block-end: var(--spacing-8);
-}
-
-.list {
- display: flex;
- margin-block-end: var(--spacing-16);
-}
-
-.tag {
- border-radius: var(--spacing-4);
- color: var(--db-primary);
- margin-inline-end: var(--spacing-16);
- padding: var(--spacing-4) var(--spacing-8);
- text-transform: uppercase;
-}
-
-.contrast-preview {
- align-items: center;
- border: 1px solid var(--df-secondary);
- border-radius: var(--spacing-4);
- box-sizing: content-box;
- block-size: calc(2 * var(--spacing-40));
- display: flex;
- flex-direction: column;
- justify-content: center;
- inline-size: calc(100% - var(--spacing-16));
- margin-block-end: var(--spacing-16);
- padding-block: var(--spacing-24);
-}
-
-.empty-preview {
- position: absolute;
-}
-
-.text {
- color: transparent;
- margin-block-end: var(--spacing-8);
-
- &.small {
- font-size: 18px;
- }
-
- &.large {
- font-size: 24px;
- }
-}
-
-.icons-list {
- display: flex;
- gap: var(--spacing-8);
- margin-block-start: var(--spacing-8);
-}
-
-.shape {
- block-size: var(--spacing-24);
- inline-size: var(--spacing-24);
-}
-
-.circle {
- border-radius: 50%;
-}
-
-.triangle {
- border-left: var(--spacing-12) solid transparent;
- border-right: var(--spacing-12) solid transparent;
- border-bottom: var(--spacing-24) solid transparent;
-}
diff --git a/apps/contrast-plugin/src/app/app.element.ts b/apps/contrast-plugin/src/app/app.element.ts
deleted file mode 100644
index 1528db1..0000000
--- a/apps/contrast-plugin/src/app/app.element.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-/* eslint-disable */
-import 'plugins-styles/lib/styles.css';
-import './app.element.css';
-
-export class AppElement extends HTMLElement {
- public static observedAttributes = [];
- public shapes: any;
-
- calculateContrast(firstColor: string, secondColor: string) {
- const luminosityFirstColor = this.getLuminosity(firstColor);
- const luminositySecondColor = this.getLuminosity(secondColor);
-
- const result =
- (luminosityFirstColor + 0.05) / (luminositySecondColor + 0.05);
-
- this.setColors(firstColor, secondColor);
- this.setResult(result.toFixed(2).toString());
- this.setA11yTags(result);
- }
-
- getLuminosity(color: string) {
- const rgb = this.hexToRgb(color);
- const a = rgb.map((v) => {
- v /= 255;
- return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
- });
- return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
- }
-
- hexToRgb(hex: string) {
- const r = parseInt(hex.slice(1, 3), 16);
- const g = parseInt(hex.slice(3, 5), 16);
- const b = parseInt(hex.slice(5, 7), 16);
- return [r, g, b];
- }
-
- setResult(text: string) {
- const selector = document.getElementById('result');
-
- if (selector) {
- selector.innerText = `${text} : 1`;
- }
- }
-
- setColors(firstColor: string | null, secondColor: string | null) {
- const color1 = document.getElementById('first-color');
- const color2 = document.getElementById('second-color');
- const code1 = document.getElementById('first-color-code');
- const code2 = document.getElementById('second-color-code');
- const contrastPreview = document.getElementById('contrast-preview');
- const smallText = document.getElementById('small-text');
- const largeText = document.getElementById('large-text');
- const circle = document.getElementById('circle');
- const square = document.getElementById('square');
- const triangle = document.getElementById('triangle');
-
- if (color1 && code1) {
- color1.style.background = firstColor ? firstColor : 'transparent';
- code1.innerText = firstColor ? firstColor : '';
- }
-
- if (color2 && code2) {
- color2.style.background = secondColor ? secondColor : 'transparent';
- code2.innerText = secondColor ? secondColor : '';
- }
-
- if (
- contrastPreview &&
- smallText &&
- largeText &&
- circle &&
- square &&
- triangle
- ) {
- contrastPreview.style.background = secondColor
- ? secondColor
- : 'transparent';
- smallText.style.color = firstColor ? firstColor : 'transparent';
- largeText.style.color = firstColor ? firstColor : 'transparent';
- circle.style.background = firstColor ? firstColor : 'transparent';
- square.style.background = firstColor ? firstColor : 'transparent';
- triangle.style.borderBottom = firstColor
- ? `var(--spacing-24) solid ${firstColor}`
- : 'var(--spacing-24) solid transparent';
- }
-
- const emptyPreview = document.getElementById('empty-preview');
- if (!firstColor && !secondColor && emptyPreview) {
- emptyPreview.style.display = 'block';
- } else if (emptyPreview) {
- emptyPreview.style.display = 'none';
- }
- }
-
- setA11yTags(result: number) {
- const selectors = {
- aa: document.getElementById('aa'),
- aaa: document.getElementById('aaa'),
- aaLg: document.getElementById('aa-lg'),
- aaaLg: document.getElementById('aaa-lg'),
- graphics: document.getElementById('graphics'),
- };
- const fail = 'tag fail';
- const good = 'tag good';
-
- function setClass(selector: HTMLElement | null, className: string) {
- if (selector) {
- selector.className = className;
- }
- }
-
- if (result > 7) {
- setClass(selectors.aa, good);
- setClass(selectors.aaa, good);
- setClass(selectors.aaLg, good);
- setClass(selectors.aaaLg, good);
- setClass(selectors.graphics, good);
- } else if (result > 4.5) {
- setClass(selectors.aa, good);
- setClass(selectors.aaa, fail);
- setClass(selectors.aaLg, good);
- setClass(selectors.aaaLg, good);
- setClass(selectors.graphics, good);
- } else if (result > 3) {
- setClass(selectors.aa, fail);
- setClass(selectors.aaa, fail);
- setClass(selectors.aaLg, good);
- setClass(selectors.aaaLg, fail);
- setClass(selectors.graphics, good);
- } else {
- setClass(selectors.aa, fail);
- setClass(selectors.aaa, fail);
- setClass(selectors.aaLg, fail);
- setClass(selectors.aaaLg, fail);
- setClass(selectors.graphics, fail);
- }
- }
-
- initCalculate(shapes: any) {
- const obj0 = shapes[0]?.fills?.[0]?.fillColor;
- const obj1 = shapes[1]?.fills?.[0]?.fillColor;
-
- if (obj0 && obj1) {
- this.calculateContrast(obj0, obj1);
- }
- }
-
- connectedCallback() {
- window.addEventListener('message', (event) => {
- if (event.data.type === 'init') {
- this.setAttribute('data-theme', event.data.content.theme);
- if (event.data.content.selection.length >= 2) {
- this.initCalculate(event.data.content.shapes);
- }
- } else if (event.data.type === 'selection') {
- if (event.data.content.shapes.length >= 2) {
- this.initCalculate(event.data.content.shapes);
- } else {
- this.setColors(null, null);
- this.setResult('0');
- this.setA11yTags(0);
- }
- } else if (event.data.type === 'theme') {
- this.setAttribute('data-theme', event.data.content.theme);
- }
- });
-
- this.innerHTML = `
-
-
-
Select two colors to calculate contrast
-
SMALL sample text
-
LARGE sample text
-
-
-
Selected colors:
-
-
Contrast ratio: 0 : 1
-
Normal text:
-
-
Large text (24px or 19px + bold):
-
-
Graphics (such as form input borders):
-
-
- `;
-
- parent.postMessage({ content: 'ready' }, '*');
- }
-}
-customElements.define('app-root', AppElement);
diff --git a/apps/contrast-plugin/src/assets/.gitkeep b/apps/contrast-plugin/src/assets/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/contrast-plugin/src/assets/manifest.json b/apps/contrast-plugin/src/assets/manifest.json
new file mode 100644
index 0000000..0870e1e
--- /dev/null
+++ b/apps/contrast-plugin/src/assets/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "Contrast",
+ "host": "http://localhost:4302",
+ "description": "Measure contrast plugin",
+ "code": "/assets/plugin.js",
+ "permissions": ["page:read", "file:read", "selection:read"]
+}
diff --git a/apps/contrast-plugin/index.html b/apps/contrast-plugin/src/index.html
similarity index 52%
rename from apps/contrast-plugin/index.html
rename to apps/contrast-plugin/src/index.html
index 88aef45..5326791 100644
--- a/apps/contrast-plugin/index.html
+++ b/apps/contrast-plugin/src/index.html
@@ -2,15 +2,11 @@
- ContrastPlugin
+ contrast-plugin
-
-
-
-
diff --git a/apps/contrast-plugin/src/main.ts b/apps/contrast-plugin/src/main.ts
index fdb879d..514c89a 100644
--- a/apps/contrast-plugin/src/main.ts
+++ b/apps/contrast-plugin/src/main.ts
@@ -1 +1,7 @@
-import './app/app.element';
+import { bootstrapApplication } from '@angular/platform-browser';
+import { appConfig } from './app/app.config';
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err)
+);
diff --git a/apps/contrast-plugin/src/model.ts b/apps/contrast-plugin/src/model.ts
new file mode 100644
index 0000000..214a79e
--- /dev/null
+++ b/apps/contrast-plugin/src/model.ts
@@ -0,0 +1,29 @@
+import { PenpotShape } from '@penpot/plugin-types';
+
+export interface InitPluginUIEvent {
+ type: 'ready';
+}
+
+export type PluginUIEvent = InitPluginUIEvent;
+
+export interface InitPluginEvent {
+ type: 'init';
+ content: {
+ theme: string;
+ selection: PenpotShape[];
+ };
+}
+export interface SelectionPluginEvent {
+ type: 'selection';
+ content: PenpotShape[];
+}
+
+export interface ThemePluginEvent {
+ type: 'theme';
+ content: string;
+}
+
+export type PluginMessageEvent =
+ | InitPluginEvent
+ | SelectionPluginEvent
+ | ThemePluginEvent;
diff --git a/apps/contrast-plugin/src/plugin.ts b/apps/contrast-plugin/src/plugin.ts
index 1c5e866..07386c5 100644
--- a/apps/contrast-plugin/src/plugin.ts
+++ b/apps/contrast-plugin/src/plugin.ts
@@ -1,15 +1,17 @@
-penpot.ui.open('Contrast plugin', '', {
- width: 450,
- height: 625,
+import type { PluginMessageEvent, PluginUIEvent } from './model.js';
+
+penpot.ui.open('CONTRAST PLUGIN', `?theme=${penpot.getTheme()}`, {
+ width: 235,
+ height: 445,
});
-penpot.ui.onMessage<{ content: string }>((message) => {
- if (message.content === 'ready') {
- penpot.ui.sendMessage({
+penpot.ui.onMessage((message) => {
+ if (message.type === 'ready') {
+ sendMessage({
type: 'init',
content: {
theme: penpot.getTheme(),
- shapes: penpot.getSelectedShapes(),
+ selection: penpot.getSelectedShapes(),
},
});
}
@@ -17,10 +19,14 @@ penpot.ui.onMessage<{ content: string }>((message) => {
penpot.on('selectionchange', () => {
const shapes = penpot.getSelectedShapes();
- penpot.ui.sendMessage({ type: 'selection', content: { shapes } });
+ sendMessage({ type: 'selection', content: shapes });
});
penpot.on('themechange', () => {
const theme = penpot.getTheme();
- penpot.ui.sendMessage({ type: 'theme', content: { theme } });
+ sendMessage({ type: 'theme', content: theme });
});
+
+function sendMessage(message: PluginMessageEvent) {
+ penpot.ui.sendMessage(message);
+}
diff --git a/apps/contrast-plugin/src/styles.css b/apps/contrast-plugin/src/styles.css
index 90d4ee0..e69de29 100644
--- a/apps/contrast-plugin/src/styles.css
+++ b/apps/contrast-plugin/src/styles.css
@@ -1 +0,0 @@
-/* You can add global styles to this file, and also import other style files */
diff --git a/apps/contrast-plugin/tsconfig.app.json b/apps/contrast-plugin/tsconfig.app.json
index 559b7d6..936913d 100644
--- a/apps/contrast-plugin/tsconfig.app.json
+++ b/apps/contrast-plugin/tsconfig.app.json
@@ -2,8 +2,9 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
- "types": ["node"]
+ "types": []
},
- "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"],
- "include": ["src/**/*.ts", "../../libs/plugin-types/index.d.ts"]
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"],
+ "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}
diff --git a/apps/contrast-plugin/tsconfig.editor.json b/apps/contrast-plugin/tsconfig.editor.json
new file mode 100644
index 0000000..4ee6393
--- /dev/null
+++ b/apps/contrast-plugin/tsconfig.editor.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "compilerOptions": {
+ "types": []
+ }
+}
diff --git a/apps/contrast-plugin/tsconfig.json b/apps/contrast-plugin/tsconfig.json
index cee4bba..4c48587 100644
--- a/apps/contrast-plugin/tsconfig.json
+++ b/apps/contrast-plugin/tsconfig.json
@@ -1,30 +1,33 @@
{
- "extends": "../../tsconfig.base.json",
- "files": [],
"compilerOptions": {
- "target": "ESNext",
- "useDefineForClassFields": true,
- "module": "ESNext",
- "lib": ["ESNext", "DOM"],
- "moduleResolution": "Node",
- "strict": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
+ "target": "es2022",
+ "useDefineForClassFields": false,
"esModuleInterop": true,
- "noEmit": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
- "skipLibCheck": true,
- "types": ["vite/client"]
+ "noFallthroughCasesInSwitch": true
},
- "include": ["src"],
+ "files": [],
+ "include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
- "path": "./tsconfig.spec.json"
+ "path": "./tsconfig.editor.json"
+ },
+ {
+ "path": "./tsconfig.plugin.json"
}
- ]
+ ],
+ "extends": "../../tsconfig.base.json",
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
}
diff --git a/apps/contrast-plugin/tsconfig.plugin.json b/apps/contrast-plugin/tsconfig.plugin.json
new file mode 100644
index 0000000..961987f
--- /dev/null
+++ b/apps/contrast-plugin/tsconfig.plugin.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "types": []
+ },
+ "files": ["src/plugin.ts"],
+ "include": ["../../libs/plugin-types/index.d.ts"]
+}
diff --git a/apps/contrast-plugin/tsconfig.spec.json b/apps/contrast-plugin/tsconfig.spec.json
deleted file mode 100644
index 3c002c2..0000000
--- a/apps/contrast-plugin/tsconfig.spec.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "types": [
- "vitest/globals",
- "vitest/importMeta",
- "vite/client",
- "node",
- "vitest"
- ]
- },
- "include": [
- "vite.config.ts",
- "vitest.config.ts",
- "src/**/*.test.ts",
- "src/**/*.spec.ts",
- "src/**/*.test.tsx",
- "src/**/*.spec.tsx",
- "src/**/*.test.js",
- "src/**/*.spec.js",
- "src/**/*.test.jsx",
- "src/**/*.spec.jsx",
- "src/**/*.d.ts"
- ]
-}
diff --git a/apps/contrast-plugin/vite.config.ts b/apps/contrast-plugin/vite.config.ts
index 95c516e..c5ff14b 100644
--- a/apps/contrast-plugin/vite.config.ts
+++ b/apps/contrast-plugin/vite.config.ts
@@ -1,57 +1,19 @@
///
import { defineConfig } from 'vite';
-import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
-
export default defineConfig({
root: __dirname,
- cacheDir: '../../node_modules/.vite/apps/contrast-plugin',
-
- server: {
- port: 4302,
- host: '0.0.0.0',
- },
-
- preview: {
- port: 4302,
- host: '0.0.0.0',
- },
-
- plugins: [nxViteTsPaths()],
-
- // Uncomment this if you are using workers.
- // worker: {
- // plugins: [ nxViteTsPaths() ],
- // },
-
- build: {
- outDir: '../../dist/apps/contrast-plugin',
- reportCompressedSize: true,
- commonjsOptions: {
- transformMixedEsModules: true,
- },
- rollupOptions: {
- input: {
- plugin: 'src/plugin.ts',
- index: './index.html',
- },
- output: {
- entryFileNames: '[name].js',
- },
- },
- },
-
+ cacheDir: '../node_modules/.vite/contrast-plugin',
test: {
globals: true,
cache: {
- dir: '../../node_modules/.vitest',
+ dir: '../node_modules/.vitest',
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
-
reporters: ['default'],
coverage: {
- reportsDirectory: '../../coverage/apps/contrast-plugin',
+ reportsDirectory: '../coverage/contrast-plugin',
provider: 'v8',
},
},
diff --git a/libs/plugins-styles/src/lib/core/fonts.css b/libs/plugins-styles/src/lib/core/fonts.css
index cbb5a88..005dc1a 100644
--- a/libs/plugins-styles/src/lib/core/fonts.css
+++ b/libs/plugins-styles/src/lib/core/fonts.css
@@ -7,6 +7,7 @@
--font-line-height-s: 1.2;
--font-line-height-m: 1.4;
--font-line-height-l: 1.5;
+ --font-size-xs: 10px;
--font-size-s: 12px;
--font-size-m: 14px;
--font-size-l: 16px;
@@ -68,6 +69,12 @@ code {
text-transform: uppercase;
}
+.body-xs {
+ font-weight: var(--font-weight-regular);
+ font-size: var(--font-size-xs);
+ line-height: var(--font-line-height-s);
+}
+
.body-s {
font-weight: var(--font-weight-regular);
font-size: var(--font-size-s);
diff --git a/package.json b/package.json
index d72e0e3..bf72043 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"start:plugin:all": "concurrently --kill-others \"npm:start:plugin:*(!all)\"",
"start:plugin:poc-state": "npx nx run-many --targets=buildPlugin,serve --projects=poc-state-plugin --watch --host 0.0.0.0 --port 4301",
- "start:plugin:contrast": "npx nx run contrast-plugin:build --watch & npx nx run contrast-plugin:preview",
+ "start:contrast-plugin": "npx nx run-many --targets=buildPlugin,serve --projects=contrast-plugin --watch --host 0.0.0.0 --port 4302",
"start:plugin:icons": "npx nx run-many --targets=buildPlugin,serve --projects=icons-plugin --watch --host 0.0.0.0 --port 4303",
"start:plugin:loremipsum": "npx nx run-many --targets=buildPlugin,serve --projects=lorem-ipsum-plugin --watch --port 4304",
"start:plugin:palette": "npx nx run create-palette-plugin:build --watch & npx nx run create-palette-plugin:preview",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 316b398..cdc4da8 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -10,7 +10,7 @@
"importHelpers": true,
"target": "es2015",
"module": "esnext",
- "lib": ["es2020", "dom"],
+ "lib": ["es2022", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",