Skip to content

Commit

Permalink
Merge 90dec08 into 15f88b1
Browse files Browse the repository at this point in the history
  • Loading branch information
JackHowa committed Jan 18, 2024
2 parents 15f88b1 + 90dec08 commit e269963
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 49 deletions.
60 changes: 11 additions & 49 deletions src/utilities/getVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,16 @@ import { tokenCategoryType } from '@typings/tokenCategory'
import { tokenExportKeyType } from '@typings/tokenExportKey'
import { PropertyType } from '@typings/valueTypes'
import { roundRgba } from './convertColor'
import { changeNotation } from './changeNotation'
import { getVariableTypeByValue } from './getVariableTypeByValue'
import roundWithDecimals from './roundWithDecimals'
import processAliasModes from './processAliasModes'
import handleVariableAlias from './handleVariableAlias'
import { Settings } from '@typings/settings'

const extractVariable = (variable, value) => {
let category: tokenCategoryType = 'color'
let values = {}
if (value.type === 'VARIABLE_ALIAS') {
const resolvedAlias = figma.variables.getVariableById(value.id)
const collection = figma.variables.getVariableCollectionById(resolvedAlias.variableCollectionId)
return {
name: variable.name,
description: variable.description || undefined,
exportKey: tokenTypes.variables.key as tokenExportKeyType,
category: getVariableTypeByValue(Object.values(resolvedAlias.valuesByMode)[0]),
values: `{${collection.name.toLowerCase()}.${changeNotation(resolvedAlias.name, '/', '.')}}`,

// this is being stored so we can properly update the design tokens later to account for all
// modes when using aliases
aliasCollectionName: collection.name.toLowerCase(),
aliasModes: collection.modes
}
return handleVariableAlias(variable, value)
}
switch (variable.resolvedType) {
case 'COLOR':
Expand All @@ -53,42 +40,17 @@ const extractVariable = (variable, value) => {
break
}
return {
name: variable.name,
// name is overridden anyways
// name: variable.name,
description: variable.description || undefined,
exportKey: tokenTypes.variables.key as tokenExportKeyType,
category,
values
}
}

const processAliasModes = (variables) => {
return variables.reduce((collector, variable) => {
// nothing needs to be done to variables that have no alias modes, or only one mode
if (!variable.aliasModes || variable.aliasModes.length < 2) {
collector.push(variable)

return collector
}

const { aliasModes, aliasCollectionName } = variable

// this was only added for this function to process that data so before we return the variables, we can remove it
delete variable.aliasModes
delete variable.aliasCollectionName

for (const aliasMode of aliasModes) {
const modeBasedVariable = { ...variable }
modeBasedVariable.values = modeBasedVariable.values.replace(new RegExp(`({${aliasCollectionName}.)`, "i"), `{${aliasCollectionName}.${aliasMode.name}.`)

collector.push(modeBasedVariable)
}

return collector
}, [])
}

export const getVariables = (figma: PluginAPI, settings: Settings) => {
const excludedCollectionIds = figma.variables.getLocalVariableCollections().filter(collection => !['.', '_', ...settings.exclusionPrefix.split(',')].includes(collection.name.charAt(0))).map(collection => collection.id);
const excludedCollectionIds = figma.variables.getLocalVariableCollections().filter(collection => !['.', '_', ...settings.exclusionPrefix.split(',')].includes(collection.name.charAt(0))).map(collection => collection.id)
// get collections
const collections = Object.fromEntries(figma.variables.getLocalVariableCollections().map((collection) => [collection.id, collection]))
// get variables
Expand All @@ -99,13 +61,13 @@ export const getVariables = (figma: PluginAPI, settings: Settings) => {
// return each mode value as a separate variable
return Object.entries(variable.valuesByMode).map(([id, value]) => {
// Only add mode if there's more than one
let addMode = settings.modeReference && modes.length > 1
const addMode = settings.modeReference && modes.length > 1
return {
...extractVariable(variable, value),
// name is contstructed from collection, mode and variable name
// name is constructed from collection, mode and variable name

name: addMode ? `${collection}/${modes.find(({ modeId }) => modeId === id).name}/${variable.name}` : `${collection}/${variable.name}`,
// add mnetadata to extensions
// add metadata to extensions
extensions: {
[config.key.extensionPluginData]: {
mode: settings.modeReference ? modes.find(({ modeId }) => modeId === id).name : undefined,
Expand All @@ -118,5 +80,5 @@ export const getVariables = (figma: PluginAPI, settings: Settings) => {
}
})
})
return settings.modeReference ? processAliasModes(variables.flat()) : variables.flat();
}
return settings.modeReference ? processAliasModes(variables.flat()) : variables.flat()
}
32 changes: 32 additions & 0 deletions src/utilities/handleVariableAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { tokenTypes } from '@config/tokenTypes'
import { tokenExportKeyType } from '@typings/tokenExportKey'
import { changeNotation } from './changeNotation'
import { getVariableTypeByValue } from './getVariableTypeByValue'

const handleVariableAlias = (variable, value) => {
const resolvedAlias = figma.variables.getVariableById(value.id)
const collection = figma.variables.getVariableCollectionById(
resolvedAlias.variableCollectionId
)
return {
// overridden anyways when extract variable is used
// name: variable.name,
description: variable.description || undefined,
exportKey: tokenTypes.variables.key as tokenExportKeyType,
category: getVariableTypeByValue(
Object.values(resolvedAlias.valuesByMode)[0]
),
values: `{${collection.name.toLowerCase()}.${changeNotation(
resolvedAlias.name,
'/',
'.'
)}}`,

// this is being stored so we can properly update the design tokens later to account for all
// modes when using aliases
aliasCollectionName: collection.name.toLowerCase(),
aliasModes: collection.modes
}
}

export default handleVariableAlias
33 changes: 33 additions & 0 deletions src/utilities/processAliasModes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const processAliasModes = (variables) => {
return variables.reduce((collector, variable) => {
// nothing needs to be done to variables that have no alias modes, or only one mode
if (!variable.aliasModes || variable.aliasModes.length < 2) {
collector.push(variable)

return collector
}

const { aliasModes, aliasCollectionName } = variable

// this was only added for this function to process that data so before we return the variables, we can remove it
delete variable.aliasModes
delete variable.aliasCollectionName

for (const aliasMode of aliasModes) {
const modeBasedVariable = { ...variable }

// replace the prefix collection, like "color", with the "color" and the mode like "dark"
modeBasedVariable.values = modeBasedVariable.values.replace(
// collection is case-insensitive matching i
new RegExp(`({${aliasCollectionName}.)`, 'i'),
`{${aliasCollectionName}.${aliasMode.name}.`
)

collector.push(modeBasedVariable)
}

return collector
}, [])
}

export default processAliasModes
70 changes: 70 additions & 0 deletions tests/unit/handleVariableAlias.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import handleVariableAlias from "../../src/utilities/handleVariableAlias";

import { tokenExportKeyType } from "@typings/tokenExportKey";
import { tokenTypes } from "@config/tokenTypes";

import { getVariableTypeByValue } from "../../src/utilities/getVariableTypeByValue";
import { changeNotation } from "../../src/utilities/changeNotation";

jest.mock("../../src/utilities/getVariableTypeByValue", () => ({
getVariableTypeByValue: jest.fn(),
}));

jest.mock("../../src/utilities/changeNotation", () => ({
changeNotation: jest.fn(),
}));

describe("handleVariableAlias", () => {
beforeEach(() => {
jest.clearAllMocks();
});

beforeAll(() => {
// @ts-ignore
global.figma = {
variables: {
getVariableById: jest.fn(),
getVariableCollectionById: jest.fn(),
},
};
});

it("should return the correct object", () => {
const variable = { description: "test description" };
const value = { id: "test id", };
const resolvedAlias = {
variableCollectionId: "test collection id",
name: "test name",
valuesByMode: { mode1: "value1" },
};
const collection = {
name: "test collection name",
modes: "test modes",
};

// @ts-ignore
global.figma.variables.getVariableById.mockReturnValue(resolvedAlias)

// @ts-ignore
getVariableTypeByValue.mockImplementation(() => "test category")

// @ts-ignore
changeNotation.mockImplementation(() => "test notation")

// @ts-ignore
global.figma.variables.getVariableCollectionById.mockReturnValue(
collection
);

const result = handleVariableAlias(variable, value);

expect(result).toEqual({
description: "test description",
exportKey: tokenTypes.variables.key as tokenExportKeyType,
category: "test category",
values: `{test collection name.test notation}`,
aliasCollectionName: "test collection name",
aliasModes: "test modes",
});
});
});
65 changes: 65 additions & 0 deletions tests/unit/processAliasModes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import processAliasModes from "../../src/utilities/processAliasModes";

describe("processAliasModes", () => {
it("should return the same variables if they have no alias modes or only one mode", () => {
const variables = [
{ values: "{color.black}" },
{ values: "{color.white}", aliasModes: ["mode1"] },
];
const result = processAliasModes(variables);
expect(result).toEqual(variables);
});

it("should process variables with multiple alias modes", () => {
const variables = [
{
values: "{color.black}",
aliasModes: [{ name: "mode1" }, { name: "mode2" }],
aliasCollectionName: "color",
},
];
const result = processAliasModes(variables);
expect(result).toEqual([
{
values: "{color.mode1.black}",
},
{
values: "{color.mode2.black}",
},
]);
});

it("should remove aliasModes and aliasCollectionName properties from the variables", () => {
const variables = [
{
values: "{collection.}",
aliasModes: [{ name: "mode1" }, { name: "mode2" }],
aliasCollectionName: "collection",
},
];
const result = processAliasModes(variables);
result.forEach((variable) => {
expect(variable).not.toHaveProperty("aliasModes");
expect(variable).not.toHaveProperty("aliasCollectionName");
});
});

it('should match aliasCollectionName case-insensitively and return the alias collection name', () => {
const variables = [
{
values: '{CollEctIon.}',
aliasModes: [{ name: 'mode1' }, { name: 'mode2' }],
aliasCollectionName: 'collection',
},
];
const result = processAliasModes(variables);
expect(result).toEqual([
{
values: '{collection.mode1.}',
},
{
values: '{collection.mode2.}',
},
]);
});
});

0 comments on commit e269963

Please sign in to comment.