Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5dba463
feat(hct-color-picker): add alpha field to UiPaintStyle, HctObject, a…
Mordech Mar 28, 2026
f0d8850
feat(hct-color-picker): read alpha from paint opacity and variable RG…
Mordech Mar 28, 2026
e9e2b0a
feat(hct-color-picker): include alpha in selection message from paint…
Mordech Mar 28, 2026
9a900d9
feat(hct-color-picker): apply alpha to SolidPaint opacity and variabl…
Mordech Mar 28, 2026
9076cee
feat(hct-color-picker): include alpha in paint style opacity and name…
Mordech Mar 28, 2026
b03778a
feat(hct-color-picker): add alpha state, encode alpha in argb, restor…
Mordech Mar 28, 2026
e10a753
feat(hct-color-picker): add alpha gradient getter and 4th hct-control…
Mordech Mar 28, 2026
c8f562a
fix(hct-color-picker): pass alpha through swatch click and use argb i…
Mordech Mar 28, 2026
59a94b4
feat(hct-color-picker): show alpha hex digits in color display when a…
Mordech Mar 28, 2026
b4db6f6
feat(hct-color-picker): show transparency via checkerboard in preview…
Mordech Mar 28, 2026
f77f175
fix(web-components): use CSS custom property for swatch color to fix …
Mordech Mar 28, 2026
6c8552e
fix(hct-color-picker): set swatch color via host CSS var for shadow D…
Mordech Mar 28, 2026
e65677b
feat(web-components): add RGBA support with alpha to paint-swatch
Mordech Apr 1, 2026
66fe43d
style(hct-color-picker): refactor UI dimensions and styling
Mordech Apr 1, 2026
fad3ec9
feat: add mobile and desktop paint swatch reference images
Mordech Apr 1, 2026
1eacdf0
chore: update reference screenshots for atoms paint swatch chrome views
Mordech Apr 1, 2026
0e1f362
fix: update alpha opacity in mrd paint swatch gradient from 1 to 100%
Mordech Apr 1, 2026
c740ca5
chore: add .continue directory to .gitignore
Mordech Apr 1, 2026
adbd314
fix(hct-color-picker): fix ARGB overflow, alpha gradient, and type sa…
Mordech Apr 2, 2026
5f27780
feat(hct-color-picker): update backgrounds with alpha variable
Mordech Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ coverage
.worktrees
# CocoIndex Code (ccc)
/.cocoindex_code/
/.continue/

.superpowers
.playwright-mcp
Expand Down
2 changes: 2 additions & 0 deletions apps/hct-color-picker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"build:plugin": "esbuild --bundle --minify --sourcemap --target=es2015 --format=esm --legal-comments=none --outfile=dist/code.js plugin-src/code.ts",
"build": "concurrently \"vite build\" \"yarn build:plugin\"",
"watch": "concurrently \"vite build --watch --mode development\" \"yarn build:plugin --watch\"",
"lint": "eslint ui-src plugin-src",
"lint:css": "stylelint './**/*.{css,scss,ts}'"
},
"devDependencies": {
Expand All @@ -23,6 +24,7 @@
"@mordech/web-components": "*",
"concurrently": "^8.2.2",
"esbuild": "0.19.11",
"eslint": "8.57.0",
"lit-analyzer": "^2.0.2",
"stylelint": "^16.1.0",
"vite": "^5.0.10"
Expand Down
2 changes: 1 addition & 1 deletion apps/hct-color-picker/plugin-src/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"extends": ["../.eslintrc.json"],
"parserOptions": {
"project": "./tsconfig.json",
"tsconfigRootDir": "./apps/hct-color-picker/plugin-src"
"tsconfigRootDir": "./plugin-src"
}
}
6 changes: 3 additions & 3 deletions apps/hct-color-picker/plugin-src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import {
updateSelection,
} from './utils';

figma.showUI(__html__, { width: 340, height: 560 });
figma.showUI(__html__, { width: 300, height: 564 });
figma.currentPage.selection = [];

const uiSizes: Record<string, [number, number]> = {
small: [340, 568],
medium: [340, 618],
small: [300, 564],
medium: [300, 600],
};

initPaints();
Expand Down
10 changes: 9 additions & 1 deletion apps/hct-color-picker/plugin-src/utils/createPaintStyle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
alphaFromArgb,
blueFromArgb,
greenFromArgb,
Hct,
Expand All @@ -8,22 +9,29 @@ import {
import { applyPaint } from './applyPaint';

export function createPaintStyle(argb: number) {
const opacity = alphaFromArgb(argb) / 255;

const color: RGB = {
r: redFromArgb(argb) / 255,
g: greenFromArgb(argb) / 255,
b: blueFromArgb(argb) / 255,
};

const hct = Hct.fromInt(argb);
const alphaPercent = Math.round(opacity * 100);

const name = `hct(${hct.hue.toFixed()}, ${hct.chroma.toFixed()}, ${hct.tone.toFixed()})`;
const name =
alphaPercent < 100
? `hct(${hct.hue.toFixed()}, ${hct.chroma.toFixed()}, ${hct.tone.toFixed()}, ${alphaPercent}%)`
: `hct(${hct.hue.toFixed()}, ${hct.chroma.toFixed()}, ${hct.tone.toFixed()})`;

const paintStyle = figma.createPaintStyle();
paintStyle.name = name;
paintStyle.paints = [
{
type: 'SOLID',
color,
opacity,
},
];

Expand Down
50 changes: 17 additions & 33 deletions apps/hct-color-picker/plugin-src/utils/extractValidStyles.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,5 @@
import type { UiPaintStyle } from '../../types';

function isPaintStyle(paintStyle: PaintStyle | Variable) {
if ('paints' in paintStyle) {
const { paints } = paintStyle;

if (!paints.length) return false;
if (paints.length > 1) return false;
if (paints[0].type !== 'SOLID') return false;

return true;
}

return false;
}

function isColorVariable(paintStyle: PaintStyle | Variable) {
if ('resolvedType' in paintStyle) {
return paintStyle.resolvedType === 'COLOR' ? true : false;
}
return false;
}

function extractColor(
paintStyle: PaintStyle | Variable,
): Variable | RGB | undefined {
Expand All @@ -42,10 +21,6 @@ function extractColor(
export function extractValidStyles(
paintStyles: (PaintStyle | Variable)[],
): UiPaintStyle[] | undefined {
paintStyles.filter((paintStyle) => {
isPaintStyle(paintStyle) || isColorVariable(paintStyle);
});

if (!paintStyles.length) return;
return paintStyles
.map((paintStyle) => {
Expand All @@ -58,30 +33,38 @@ export function extractValidStyles(
const { id, name } = paintStyle;

return Object.entries(valuesByMode)
.map(([modeId, paint]) => {
if (!(typeof paint === 'object')) return;
if ('type' in paint) return;
if (!('r' in paint)) return;
.map(([modeId, value]) => {
if (!(typeof value === 'object')) return;
if ('type' in value) return;
if (!('r' in value)) return;

const { r, g, b } = paint as RGB;
const { r, g, b } = value as RGB;
const rawAlpha =
'a' in value ? Math.round((value as RGBA).a * 100) : 100;
const alpha = Number.isFinite(rawAlpha) ? rawAlpha : 100;

return {
id: id,
id,
modeId,
name: name,
name,
color: {
r: r * 255,
g: g * 255,
b: b * 255,
},
alpha,
} satisfies UiPaintStyle;
})
.filter((paintStyle) => paintStyle !== undefined) as UiPaintStyle[];
}

const { id, name } = paintStyle;

const { r, g, b } = paint;
const paintOpacity =
'paints' in paintStyle
? (paintStyle.paints[0] as SolidPaint).opacity ?? 1
: 1;
const alpha = Math.round(paintOpacity * 100);

return {
id,
Expand All @@ -91,6 +74,7 @@ export function extractValidStyles(
g: g * 255,
b: b * 255,
},
alpha,
} satisfies UiPaintStyle;
})
.flat()
Expand Down
9 changes: 8 additions & 1 deletion apps/hct-color-picker/plugin-src/utils/updatePaint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
alphaFromArgb,
blueFromArgb,
greenFromArgb,
redFromArgb,
Expand All @@ -12,13 +13,16 @@ export async function updatePaint(
paintStyle: UiPaintStyle | undefined,
argb: number,
) {
const opacity = alphaFromArgb(argb) / 255;

const solidPaint: SolidPaint = {
type: 'SOLID',
color: {
r: redFromArgb(argb) / 255,
g: greenFromArgb(argb) / 255,
b: blueFromArgb(argb) / 255,
},
opacity,
};

if (!paintStyle) return applyPaint(solidPaint);
Expand All @@ -40,7 +44,10 @@ export async function updatePaint(
const mode = paintStyle.modeId;
if (!mode) return;

paint.setValueForMode(mode, solidPaint.color);
paint.setValueForMode(mode, {
...solidPaint.color,
a: opacity,
});
}

if (paintStyle.variableAlias) return;
Expand Down
9 changes: 8 additions & 1 deletion apps/hct-color-picker/plugin-src/utils/updateSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ export async function updateSelection() {
const selectionFillsId = selection.fillStyleId.toString();

const { id } = variableObject || { id: selectionFillsId };
const { modeId, variableAlias, variableValue } = variableObject || {};

const { modeId, variableAlias } = variableObject || {};
// For variable fills: read alpha from the variable's RGBA value
// For paint style / direct fills: read alpha from the paint's opacity field
const alpha =
variableValue && typeof variableValue === 'object' && 'a' in variableValue
? Math.round((variableValue as RGBA).a * 100)
: Math.round((fills[0].opacity ?? 1) * 100);

return postMessage({
type: 'selection',
Expand All @@ -88,6 +94,7 @@ export async function updateSelection() {
modeId,
color,
variableAlias,
alpha,
},
});
}
Expand Down
3 changes: 3 additions & 0 deletions apps/hct-color-picker/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type HctObject = {
hue: number;
chroma: number;
tone: number;
alpha: number;
};

export interface UiPaintStyle {
Expand All @@ -18,6 +19,7 @@ export interface UiPaintStyle {
variableAlias?: boolean;
name: string;
color: RGB;
alpha: number;
}

export type SaveColor = {
Expand Down Expand Up @@ -56,6 +58,7 @@ export type Selection = {
color?: RGB;
modeId?: string;
variableAlias?: boolean;
alpha: number;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
.color-input-container {
--mrd-swatch-size: 5rem;

height: var(--mrd-swatch-size);
z-index: 1;
margin: -2px;
align-items: center;
justify-content: center;
overflow: hidden;
display: flex;
position: relative;
flex-direction: column;
border-radius: var(--mrd-border-radius-100);
border: 1px solid var(--mrd-color-outline);
background: var(--mrd-alpha-background);

& mrd-button {
position: absolute;
inset-block-end: 0.7rem;
inset-inline-end: 0.6rem;
inset-block-end: calc(var(--mrd-space-200) + var(--mrd-space-50));
inset-inline-end: var(--mrd-space-200);
opacity: 0.9;
z-index: 2;
}
}

Expand All @@ -19,9 +28,11 @@ mrd-button.save-detach-button {
}

&::part(button) {
padding: 0.5rem;
padding: 0.35rem;
}

--mrd-icon-size: 1rem;

&::part(slot) {
visibility: hidden;
width: 0;
Expand All @@ -36,7 +47,7 @@ mrd-button.save-detach-button {
&::part(slot) {
visibility: visible;
width: auto;
padding-inline: 0.25rem;
padding-inline: var(--mrd-space-100);
overflow: hidden;
white-space: nowrap;
transform: translate(0);
Expand All @@ -52,17 +63,16 @@ mrd-button.save-detach-button {
}

.color-input {
--mrd-swatch-size: 7rem;
appearance: none;
width: 102%;

@mixin color-swatch {
border-color: var(--mrd-color-outline);
border-width: 1px;
border-radius: 0.5rem;
border-radius: var(--mrd-border-radius-200);
height: var(--mrd-swatch-size);
}

appearance: none;

&::-webkit-color-swatch {
@include color-swatch;
}
Expand All @@ -81,16 +91,16 @@ mrd-button.save-detach-button {

.info-container {
display: inline-flex;
gap: 2rem;
gap: var(--mrd-space-400);
justify-content: space-between;
align-items: center;
padding-block-end: 0.5rem;
padding-block-end: var(--mrd-space-200);
}

.color-container {
display: flex;
flex-direction: column;
gap: 0.25rem;
gap: var(--mrd-space-100);
}

.selected-style-span {
Expand Down
Loading
Loading