From 6f239b9138082315f8af164bd69f39fcbdc5ba00 Mon Sep 17 00:00:00 2001 From: Marco Christian Krenn Date: Thu, 12 Dec 2024 21:50:50 +0100 Subject: [PATCH] add scope by type node --- .changeset/thick-lemons-obey.md | 5 + packages/nodes-figma/src/nodes/index.ts | 4 +- packages/nodes-figma/src/nodes/scopeByType.ts | 87 +++++++++++++ .../nodes-figma/tests/scopeByType.test.ts | 118 ++++++++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 .changeset/thick-lemons-obey.md create mode 100644 packages/nodes-figma/src/nodes/scopeByType.ts create mode 100644 packages/nodes-figma/tests/scopeByType.test.ts diff --git a/.changeset/thick-lemons-obey.md b/.changeset/thick-lemons-obey.md new file mode 100644 index 00000000..f1b40005 --- /dev/null +++ b/.changeset/thick-lemons-obey.md @@ -0,0 +1,5 @@ +--- +"@tokens-studio/graph-engine-nodes-figma": patch +--- + +Add new node to add a variables scope by the token type. diff --git a/packages/nodes-figma/src/nodes/index.ts b/packages/nodes-figma/src/nodes/index.ts index fca1e99b..2105c396 100644 --- a/packages/nodes-figma/src/nodes/index.ts +++ b/packages/nodes-figma/src/nodes/index.ts @@ -1,15 +1,17 @@ import codeSyntax from "./codeSyntax.js"; import publish from "./publish.js"; import scopeAll from "./scopeAll.js"; +import scopeByType from "./scopeByType.js"; import scopeColor from "./scopeColor.js"; import scopeNumber from "./scopeNumber.js"; import scopeString from "./scopeString.js"; export const nodes = [ + codeSyntax, publish, scopeAll, + scopeByType, scopeColor, scopeNumber, scopeString, - codeSyntax, ]; diff --git a/packages/nodes-figma/src/nodes/scopeByType.ts b/packages/nodes-figma/src/nodes/scopeByType.ts new file mode 100644 index 00000000..13bb2114 --- /dev/null +++ b/packages/nodes-figma/src/nodes/scopeByType.ts @@ -0,0 +1,87 @@ +import { FigmaScope } from "../types/scopes.js"; +import { + INodeDefinition, + Node, + ToInput, + ToOutput, +} from "@tokens-studio/graph-engine"; +import { SingleToken } from "@tokens-studio/types"; +import { TokenSchema } from "@tokens-studio/graph-engine-nodes-design-tokens/schemas/index.js"; +import { mergeTokenExtensions } from "../utils/tokenMerge.js"; + +export default class NodeDefinition extends Node { + static title = "Scope By Type"; + static type = "studio.tokens.figma.scopeByType"; + static description = "Automatically sets Figma scopes based on token type"; + + declare inputs: ToInput<{ + token: SingleToken; + }>; + declare outputs: ToOutput<{ + token: SingleToken; + }>; + + constructor(props: INodeDefinition) { + super(props); + + this.addInput("token", { + type: { + ...TokenSchema, + description: "The design token to automatically scope", + }, + }); + + this.addOutput("token", { + type: TokenSchema, + }); + } + + private getScopesByType(token: SingleToken): FigmaScope[] { + switch (token.type) { + case "color": + return ["ALL_FILLS", "STROKE_COLOR", "EFFECT_COLOR"]; + case "dimension": + return [ + "GAP", + "WIDTH_HEIGHT", + "CORNER_RADIUS", + "STROKE_FLOAT", + "EFFECT_FLOAT", + "PARAGRAPH_INDENT", + ]; + case "spacing": + return ["GAP", "WIDTH_HEIGHT"]; + case "borderRadius": + return ["CORNER_RADIUS"]; + case "fontFamilies": + return ["FONT_FAMILY"]; + case "fontWeights": + return ["FONT_WEIGHT"]; + case "fontSizes": + return ["FONT_SIZE"]; + case "lineHeights": + return ["LINE_HEIGHT"]; + case "letterSpacing": + return ["LETTER_SPACING"]; + case "paragraphSpacing": + return ["PARAGRAPH_SPACING"]; + case "opacity": + return ["OPACITY"]; + case "sizing": + return ["WIDTH_HEIGHT"]; + default: + return []; + } + } + + execute(): void | Promise { + const { token } = this.getAllInputs(); + const newScopes = this.getScopesByType(token); + + const modifiedToken = mergeTokenExtensions(token, { + scopes: newScopes, + }); + + this.outputs.token.set(modifiedToken); + } +} diff --git a/packages/nodes-figma/tests/scopeByType.test.ts b/packages/nodes-figma/tests/scopeByType.test.ts new file mode 100644 index 00000000..f9c57821 --- /dev/null +++ b/packages/nodes-figma/tests/scopeByType.test.ts @@ -0,0 +1,118 @@ +import { Graph } from "@tokens-studio/graph-engine"; +import { SingleToken } from "@tokens-studio/types"; +import { describe, expect, test } from "vitest"; +import Node from "../src/nodes/scopeByType.js"; + +describe("nodes/scopeByType", () => { + test("adds color scopes to color token", async () => { + const graph = new Graph(); + const node = new Node({ graph }); + + const mockToken = { + name: "test", + value: "#ff0000", + type: "color", + } as SingleToken; + + node.inputs.token.setValue(mockToken); + await node.execute(); + + expect(node.outputs.token.value).toEqual({ + ...mockToken, + $extensions: { + "com.figma": { + scopes: ["ALL_FILLS", "STROKE_COLOR", "EFFECT_COLOR"], + }, + }, + }); + }); + + test("adds dimension scopes to dimension token", async () => { + const graph = new Graph(); + const node = new Node({ graph }); + + const mockToken = { + name: "test", + value: "16px", + type: "dimension", + } as SingleToken; + + node.inputs.token.setValue(mockToken); + await node.execute(); + + expect(node.outputs.token.value).toEqual({ + ...mockToken, + $extensions: { + "com.figma": { + scopes: [ + "GAP", + "WIDTH_HEIGHT", + "CORNER_RADIUS", + "STROKE_FLOAT", + "EFFECT_FLOAT", + "PARAGRAPH_INDENT", + ], + }, + }, + }); + }); + + test("adds font-related scopes to typography tokens", async () => { + const graph = new Graph(); + const node = new Node({ graph }); + + const mockToken = { + name: "test", + value: "Inter", + type: "fontFamilies", + } as SingleToken; + + node.inputs.token.setValue(mockToken); + await node.execute(); + + expect(node.outputs.token.value).toEqual({ + ...mockToken, + $extensions: { + "com.figma": { + scopes: ["FONT_FAMILY"], + }, + }, + }); + }); + + test("preserves existing extensions and merges scopes", async () => { + const graph = new Graph(); + const node = new Node({ graph }); + + const mockToken = { + name: "test", + value: "#ff0000", + type: "color", + $extensions: { + "com.figma": { + scopes: ["TEXT_FILL"], + otherProp: true, + }, + "other.extension": { + someProp: "value", + }, + }, + } as unknown as SingleToken; + + node.inputs.token.setValue(mockToken); + await node.execute(); + + expect(node.outputs.token.value).toEqual({ + ...mockToken, + $extensions: { + "com.figma": { + scopes: ["TEXT_FILL", "ALL_FILLS", "STROKE_COLOR", "EFFECT_COLOR"], + otherProp: true, + }, + "other.extension": { + someProp: "value", + }, + }, + }); + }); +});