Skip to content

Commit

Permalink
Merge pull request #9 from penpot/contrast-plugin
Browse files Browse the repository at this point in the history
feat: contrast plugin
  • Loading branch information
cocotime authored Mar 8, 2024
2 parents 6e32d21 + 9be5235 commit 06db3af
Show file tree
Hide file tree
Showing 28 changed files with 552 additions and 13 deletions.
3 changes: 3 additions & 0 deletions apps/contrast-plugin/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@nx/js/babel"]
}
18 changes: 18 additions & 0 deletions apps/contrast-plugin/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
8 changes: 8 additions & 0 deletions apps/contrast-plugin/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"jsc": {
"parser": {
"syntax": "typescript"
},
"target": "es2016"
}
}
16 changes: 16 additions & 0 deletions apps/contrast-plugin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ContrastPlugin</title>
<base href="/" />

<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<app-root></app-root>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
8 changes: 8 additions & 0 deletions apps/contrast-plugin/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "contrast-plugin",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/contrast-plugin/src",
"tags": ["type:plugin"],
"targets": {}
}
Binary file added apps/contrast-plugin/public/favicon.ico
Binary file not shown.
10 changes: 10 additions & 0 deletions apps/contrast-plugin/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "Contrast plugin",
"code": "http://localhost:4201/plugin.js",
"permissions": [
"page:read",
"file:read",
"selection:read"
]
}

95 changes: 95 additions & 0 deletions apps/contrast-plugin/src/app/app.element.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
.wrapper {
color: var(--app-white);
}

.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;
}
21 changes: 21 additions & 0 deletions apps/contrast-plugin/src/app/app.element.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AppElement } from './app.element';

describe('AppElement', () => {
let app: AppElement;

beforeEach(() => {
app = new AppElement();
});

it('should create successfully', () => {
expect(app).toBeTruthy();
});

it('should have a greeting', () => {
app.connectedCallback();

expect(app.querySelector('h1').innerHTML).toContain(
'Welcome contrast-plugin'
);
});
});
184 changes: 184 additions & 0 deletions apps/contrast-plugin/src/app/app.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import 'plugins-styles/lib/styles.css';
import './app.element.css';

export class AppElement extends HTMLElement {
public static observedAttributes = [];

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);
return 0.2126 * (rgb[0]/255) + 0.7152 * (rgb[1]/255) + 0.0722 * (rgb[2]/255);
}

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);
}
}

connectedCallback() {
window.addEventListener('message', (event) => {
if (event.data.type === 'selection') {
if (event.data.content.length === 2) {
this.calculateContrast('#d5d1d1', '#000410');
} else {
this.setColors(null, null);
this.setResult('0');
this.setA11yTags(0);
}
} else if (event.data.type === 'page') {
console.log('refrespage', event.data);
} else if (event.data.type === 'init') {
if (event.data.content.selection.length === 2) {
//TODO get real colors from selection
this.calculateContrast('#d5d1d1', '#000410');
}
}
});

this.innerHTML = `
<div class="wrapper">
<div id="contrast-preview" class="contrast-preview">
<p id="empty-preview" class="empty-preview">Select two colors to calculate contrast</p>
<p id="small-text" data-color="text" data-second class="text small">SMALL sample text</p>
<p id="large-text" data-color="text" data-second class="text large">LARGE sample text</p>
<ul class="icons-list">
<span id="circle" class="shape circle"></span>
<span id="square" class="shape square"></span>
<span id="triangle" class="triangle"></span>
</ul>
</div>
<p class="title body-l">Selected colors:</p>
<ul class="list">
<li class="color">
<span id="first-color" data-first class="color-preview"></span>
<code id="first-color-code"></code>
</li>
<li class="color">
<span id="second-color" data-second class="color-preview"></span>
<code id="second-color-code"></code>
</li>
</ul>
<p class="title body-l">Contrast ratio: <span id="result">0 : 1</span></p>
<p class="title body-l">Normal text:</p>
<ul class="list">
<li id="aa" class="tag fail">AA</li>
<li id="aaa" class="tag fail">AAA</li>
</ul>
<p class="title body-l">Large text (24px or 19px + bold):</p>
<ul class="list">
<li id="aa-lg" class="tag fail">AA</li>
<li id="aaa-lg" class="tag fail">AAA</li>
</ul>
<p class="title body-l">Graphics (such as form input borders):</p>
<ul class="list">
<li id="graphics" class="tag fail">AA</li>
</ul>
</div>
`;

parent.postMessage({ content: 'ready' }, '*');
}
}
customElements.define('app-root', AppElement);
Empty file.
1 change: 1 addition & 0 deletions apps/contrast-plugin/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './app/app.element';
Loading

0 comments on commit 06db3af

Please sign in to comment.