Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/common/networkSides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface SelectionData {
hasSelection: boolean;
nodeCount: number;
colorTokens: ColorToken[];
variableTokens: ColorToken[];
}

export const UI = Networker.createSide('UI-side').listens<{
Expand Down
74 changes: 58 additions & 16 deletions src/plugin/plugin.network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,19 @@ function getSelectionTokens(): SelectionData {
return {
hasSelection: false,
nodeCount: 0,
colorTokens: []
colorTokens: [],
variableTokens: []
};
}

// Use Figma's built-in API to get colors from selection
const selectionColors = figma.getSelectionColors();

if (!selectionColors || !selectionColors.styles) {
return {
hasSelection: true,
nodeCount: selection.length,
colorTokens: []
};
}

const allTokens: ColorToken[] = [];

// Process paint styles
if (selectionColors.styles && selectionColors.styles.length > 0) {
const styleTokens: ColorToken[] = [];
if (selectionColors && selectionColors.styles && selectionColors.styles.length > 0) {
for (const style of selectionColors.styles) {
const paint = style.paints[0];
if (paint && paint.type === 'SOLID') {
allTokens.push({
styleTokens.push({
id: style.id,
name: style.name,
type: 'STYLE',
Expand All @@ -66,10 +56,62 @@ function getSelectionTokens(): SelectionData {
}
}

const variableTokens: ColorToken[] = [];
const seenVariableIds = new Set<string>();

function collectVariablesFromNode(node: SceneNode) {
if ('boundVariables' in node && node.boundVariables) {
const boundVars = node.boundVariables as Record<string, VariableAlias | VariableAlias[]>;
for (const aliasOrArray of Object.values(boundVars)) {
const aliases = Array.isArray(aliasOrArray) ? aliasOrArray : [aliasOrArray];
for (const alias of aliases) {
if (alias && alias.type === 'VARIABLE_ALIAS' && !seenVariableIds.has(alias.id)) {
seenVariableIds.add(alias.id);
try {
const variable = figma.variables.getVariableById(alias.id);
if (variable && variable.resolvedType === 'COLOR') {
const modeIds = Object.keys(variable.valuesByMode);
if (modeIds.length > 0) {
const value = variable.valuesByMode[modeIds[0]] as RGBA;
if (value && typeof value === 'object' && 'r' in value) {
variableTokens.push({
id: variable.id,
name: variable.name,
type: 'VARIABLE',
color: {
r: value.r,
g: value.g,
b: value.b,
a: value.a ?? 1
},
hex: rgbToHex(value.r, value.g, value.b)
});
}
}
}
} catch (e) {
console.log('Error processing variable id:', alias.id, e);
}
}
}
}
}
if ('children' in node) {
for (const child of (node as ChildrenMixin).children) {
collectVariablesFromNode(child as SceneNode);
}
}
}

for (const node of selection) {
collectVariablesFromNode(node);
}

return {
hasSelection: true,
nodeCount: selection.length,
colorTokens: allTokens
colorTokens: styleTokens,
variableTokens
};
}

Expand Down
130 changes: 70 additions & 60 deletions src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ async function bootstrap() {
PLUGIN_CHANNEL.emit(UI, "selectionUpdate", [{
hasSelection: false,
nodeCount: 0,
colorTokens: []
colorTokens: [],
variableTokens: []
}]);
} else {
// Request tokens for current selection
Expand All @@ -40,35 +41,26 @@ async function bootstrap() {
return {
hasSelection: false,
nodeCount: 0,
colorTokens: []
colorTokens: [],
variableTokens: []
};
}

// Use Figma's built-in API to get colors from selection
const selectionColors = figma.getSelectionColors();
console.log('Selection colors:', selectionColors);

if (!selectionColors || !selectionColors.styles) {
console.log('No colors found in selection');
return {
hasSelection: true,
nodeCount: selection.length,
colorTokens: []
};
}

const allTokens: any[] = [];

// Process paint styles
if (selectionColors.styles && selectionColors.styles.length > 0) {

const styleTokens: any[] = [];

if (selectionColors && selectionColors.styles && selectionColors.styles.length > 0) {
console.log('Found', selectionColors.styles.length, 'paint styles in selection');

for (const style of selectionColors.styles) {
console.log('Processing style:', style.name, style.id);

const paint = style.paints[0];
if (paint && paint.type === "SOLID") {
allTokens.push({
styleTokens.push({
id: style.id,
name: style.name,
type: "STYLE",
Expand All @@ -80,52 +72,70 @@ async function bootstrap() {
},
hex: rgbToHex(paint.color.r, paint.color.g, paint.color.b)
});
console.log('Added paint style:', style.name, 'hex:', rgbToHex(paint.color.r, paint.color.g, paint.color.b));
}
}
}

// Process variables if they exist (commented out temporarily)
// if (selectionColors.variables && selectionColors.variables.length > 0) {
// console.log('Found', selectionColors.variables.length, 'variables in selection');

// for (const variable of selectionColors.variables) {
// console.log('Processing variable:', variable.name, variable.id);
//
// try {
// const collection = figma.variables.getVariableCollectionById(variable.variableCollectionId);
// if (collection) {
// const mode = collection.defaultModeId;
// const value = variable.valuesByMode[mode];
//
// if (value && typeof value === 'object' && 'r' in value) {
// allTokens.push({
// id: variable.id,
// name: variable.name,
// type: "VARIABLE",
// color: {
// r: value.r,
// g: value.g,
// b: value.b,
// a: value.a ?? 1
// },
// hex: rgbToHex(value.r, value.g, value.b)
// });
// console.log('Added color variable:', variable.name, 'hex:', rgbToHex(value.r, value.g, value.b));
// }
// }
// } catch (e) {
// console.log('Error processing variable:', variable.name, e);
// }
// }
// }

console.log('Final results - Tokens found:', allTokens.length);


// Extract color variables from the selection
const variableTokens: any[] = [];
const seenVariableIds = new Set<string>();

function collectVariablesFromNode(node: SceneNode) {
if ('boundVariables' in node && node.boundVariables) {
const boundVars = node.boundVariables as Record<string, VariableAlias | VariableAlias[]>;
for (const aliasOrArray of Object.values(boundVars)) {
const aliases = Array.isArray(aliasOrArray) ? aliasOrArray : [aliasOrArray];
for (const alias of aliases) {
if (alias && alias.type === 'VARIABLE_ALIAS' && !seenVariableIds.has(alias.id)) {
seenVariableIds.add(alias.id);
try {
const variable = figma.variables.getVariableById(alias.id);
if (variable && variable.resolvedType === 'COLOR') {
const modeIds = Object.keys(variable.valuesByMode);
if (modeIds.length > 0) {
const rawValue = variable.valuesByMode[modeIds[0]];
const value = rawValue as RGBA;
if (value && typeof value === 'object' && 'r' in value) {
variableTokens.push({
id: variable.id,
name: variable.name,
type: 'VARIABLE',
color: {
r: value.r,
g: value.g,
b: value.b,
a: value.a ?? 1
},
hex: rgbToHex(value.r, value.g, value.b)
});
}
}
}
} catch (e) {
console.log('Error processing variable id:', alias.id, e);
}
}
}
}
}
if ('children' in node) {
for (const child of (node as ChildrenMixin).children) {
collectVariablesFromNode(child as SceneNode);
}
}
}

for (const node of selection) {
collectVariablesFromNode(node);
}

console.log('Final results - Style tokens:', styleTokens.length, 'Variable tokens:', variableTokens.length);

return {
hasSelection: true,
nodeCount: selection.length,
colorTokens: allTokens
colorTokens: styleTokens,
variableTokens
};
}

Expand Down
18 changes: 16 additions & 2 deletions src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,47 @@ import { useEffect, useState } from 'react';
import { AppHeader } from './components/organisms/AppHeader';
import { EmptyState } from './components/organisms/EmptyState';
import { TokensList } from './components/organisms/TokensList';
import { SourceSelector, SourceMethod } from './components/molecules/SourceSelector';

import '@ui/styles/main.css';

function App() {
const [selectionData, setSelectionData] = useState<SelectionData>({
hasSelection: false,
nodeCount: 0,
colorTokens: []
colorTokens: [],
variableTokens: []
});
const [sourceMethod, setSourceMethod] = useState<SourceMethod>('both');

useEffect(() => {
UI_CHANNEL.subscribe('selectionUpdate', (data: SelectionData) => {
setSelectionData(data);
});
}, []);

const displayedTokens = selectionData.hasSelection
? sourceMethod === 'styles'
? selectionData.colorTokens
: sourceMethod === 'variables'
? selectionData.variableTokens
: [...selectionData.colorTokens, ...selectionData.variableTokens]
: [];

return (
<div className="h-full min-h-screen overflow-auto bg-white px-4 pb-4">
<div className="mx-auto">
<AppHeader />

<SourceSelector value={sourceMethod} onChange={setSourceMethod} />

{!selectionData.hasSelection ? (
<EmptyState />
) : (
<TokensList
tokens={selectionData.colorTokens}
tokens={displayedTokens}
nodeCount={selectionData.nodeCount}
sourceMethod={sourceMethod}
/>
)}
</div>
Expand Down
Loading