Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Add support for updated tokens conventions (dimension)
- Removes assumptions around "light" as a default mode, since current theming algorithm does not have explicit dark or light modes. Instead uses "default" as default mode
- Updates to remove handling of aliases to primitive values, as primitive values are no longer included in Figma JSON output
- Adds handling for imported dimension values (padding, gap) as numeric values
- Creates separate collections based on top-level grouping (i.e. "Color" and "Dimension"). This is because each grouping may have their own grouping-specific modes. For example, density should only affect dimension values.
  • Loading branch information
aduth committed Nov 18, 2025
commit 322f7e3f7aa997a0cee51eb72eb13e69c409d12f
112 changes: 52 additions & 60 deletions plugin-src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ figma.showUI(__html__, { themeColors: true, height: 300 });
// alias variables correctly.
const allImportedVariables: Record<string, Variable> = {};

const DEFAULT_COLOR_MODE = "light";
const LEGACY_DEFAULT_MODE = "light";
const DEFAULT_MODE = "default";

function isAliasValue(
value: ParsedTokenValue[keyof ParsedTokenValue]
Expand All @@ -22,6 +23,26 @@ function isValidColorValue(value: any): value is ParsedTokenValue {
);
}

function getResolvedType(value: any): VariableResolvedDataType | undefined {
if (isValidColorValue(value)) {
return "COLOR";
} else if (typeof value === "number") {
return "FLOAT";
}
}

function groupByCollection(tokens: ParsedTokens): Record<string, ParsedTokens> {
return Object.entries(tokens).reduce<Record<string, ParsedTokens>>(
(result, [tokenName, tokenData]) => {
const collection = tokenName.split("/")[0];
result[collection] = result[collection] || {};
result[collection][tokenName] = tokenData;
return result;
},
{}
);
}

async function updateCollection(args: {
tokens: ParsedTokens;
collectionName: string;
Expand All @@ -48,6 +69,10 @@ async function updateCollection(args: {

// Get existing modes in the collection
for (const mode of collection.modes) {
if (mode.name === LEGACY_DEFAULT_MODE) {
collection.renameMode(mode.modeId, DEFAULT_MODE);
}

modesInCollectionBeforeImporting[mode.name] = mode.modeId;
}

Expand All @@ -64,9 +89,19 @@ async function updateCollection(args: {

let variable = variablesInCollectionBeforeImporting[tokenName];

// Don't save variables where we can't determine the resolved type
const resolvedType = getResolvedType(value["."]);
if (!resolvedType) {
continue;
}

if (!variable) {
// Create new variable
variable = figma.variables.createVariable(tokenName, collection, "COLOR");
variable = figma.variables.createVariable(
tokenName,
collection,
resolvedType
);
}

if (description) {
Expand All @@ -83,12 +118,16 @@ async function updateCollection(args: {
variable.scopes = ["STROKE_COLOR", "EFFECT_COLOR"];
} else if (/color\/semantic\/background/gi.test(tokenName)) {
variable.scopes = ["FRAME_FILL", "SHAPE_FILL"];
} else if (/dimension\/semantic\/(padding|gap)/gi.test(tokenName)) {
variable.scopes = ["GAP"];
} else if (/dimension\/semantic/gi.test(tokenName)) {
variable.scopes = ["WIDTH_HEIGHT"];
} else {
variable.scopes = [];
}

for (const [modeName, modeValue] of Object.entries(value)) {
const computedModeName = modeName === "." ? DEFAULT_COLOR_MODE : modeName;
const computedModeName = modeName === "." ? DEFAULT_MODE : modeName;

if (!(computedModeName in modesInCollectionBeforeImporting)) {
modesInCollectionBeforeImporting[computedModeName] =
Expand All @@ -101,10 +140,6 @@ async function updateCollection(args: {
continue;
}

if (!isValidColorValue(modeValue)) {
continue;
}

variable.setValueForMode(
modesInCollectionBeforeImporting[computedModeName],
modeValue
Expand All @@ -115,51 +150,7 @@ async function updateCollection(args: {
variablesUpdatedDuringImport[tokenName] = true;
}

// Pass 2: create or update aliases
for (const [tokenName, tokenData] of Object.entries(tokens)) {
const { value } = tokenData;

const variable = allImportedVariables[tokenName];
if (!variable) {
console.log(
"Something is off — this variable should have already been created"
);
continue;
}

for (const [modeName, modeValue] of Object.entries(value)) {
const computedModeName = modeName === "." ? DEFAULT_COLOR_MODE : modeName;

if (!(computedModeName in modesInCollectionBeforeImporting)) {
console.log(
"Something is off — this mode should have already been created"
);
continue;
}

// Second pass: only save aliased values
if (!isAliasValue(modeValue)) {
continue;
}

const matchAliasTokenName = /^\{(.*)\}$/.exec(modeValue);
const aliasTokenName = matchAliasTokenName && matchAliasTokenName[1];

if (!aliasTokenName || !allImportedVariables[aliasTokenName]) {
continue;
}

variable.setValueForMode(
modesInCollectionBeforeImporting[computedModeName],
{
type: "VARIABLE_ALIAS",
id: allImportedVariables[aliasTokenName].id,
}
);
}
}

// Pass 3: fallback missing mode values to the default mode value
// Pass 2: fallback missing mode values to the default mode value
for (const [tokenName, tokenData] of Object.entries(tokens)) {
const { value } = tokenData;

Expand All @@ -174,7 +165,7 @@ async function updateCollection(args: {
const modesMissingValues = new Set(collection.modes.map((m) => m.modeId));

for (const [modeName] of Object.entries(value)) {
const computedModeName = modeName === "." ? DEFAULT_COLOR_MODE : modeName;
const computedModeName = modeName === "." ? DEFAULT_MODE : modeName;

if (!(computedModeName in modesInCollectionBeforeImporting)) {
console.log(
Expand All @@ -191,9 +182,7 @@ async function updateCollection(args: {
for (const modeId of modesMissingValues) {
variable.setValueForMode(
modeId,
variable.valuesByMode[
modesInCollectionBeforeImporting[DEFAULT_COLOR_MODE]
]
variable.valuesByMode[modesInCollectionBeforeImporting[DEFAULT_MODE]]
);
}
}
Expand All @@ -220,11 +209,14 @@ async function updateCollection(args: {
figma.ui.onmessage = async (msg) => {
if (msg.type === "import-tokens") {
const parsedTokens: ParsedTokens = msg.parsedTokens;
const groupedTokens = groupByCollection(parsedTokens);

await updateCollection({
tokens: parsedTokens,
collectionName: "WPDS Tokens",
});
for (const [collectionName, tokens] of Object.entries(groupedTokens)) {
await updateCollection({
tokens: tokens,
collectionName: `WPDS Tokens/${collectionName}`,
});
}
}

figma.closePlugin();
Expand Down
6 changes: 3 additions & 3 deletions types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export interface RGBA {
readonly a: number;
}

type ColorModes = "light" | "dark" | ".";
type TokenMode = ".";

export type ImportedTokenValue = {
[key in ColorModes]?: string;
[key in TokenMode]?: string;
};

export interface ImportedTokens {
Expand All @@ -25,7 +25,7 @@ export interface ImportedTokens {
}

export type ParsedTokenValue = {
[key in ColorModes]?: RGB | RGBA | string;
[key in TokenMode]?: RGB | RGBA | string | number;
};
export interface ParsedTokens {
[key: string]: {
Expand Down
11 changes: 9 additions & 2 deletions ui-src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ function App() {
const parsedTokens: ParsedTokens = {};

for (const [tokenName, tokenObject] of Object.entries(tokens)) {
// For now, only import colors.
if (!/color/gi.test(tokenName)) {
// Limit to supported token types
if (!/^(color|dimension)/gi.test(tokenName)) {
continue;
}

Expand Down Expand Up @@ -54,6 +54,13 @@ function App() {
b: Math.max(Math.min(converted.b, 1), 0),
a: converted.alpha ?? 1,
};
} else if (/dimension/gi.test(tokenName)) {
if (!/px$/.test(modeValue)) {
console.warn(`Invalid dimension value: ${modeValue}`);
continue;
}

computedModeValue = Number(modeValue.replace("px", ""));
}

if (!computedModeValue) {
Expand Down