-
-
Save realvjy/fe8c8754c747de07f0bf177084d68540 to your computer and use it in GitHub Desktop.
These fuctions will help you easy managing Variable and Collection in Figma, if you creating a plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Figma Variable Manager | |
// Authoer: vijay verma | |
// Twitter/X: https://x.com/realvjy | |
// Date: 22 Nov, 2024 | |
interface VariableConfig { | |
name: string; | |
type: VariableResolvedDataType; | |
value: string | number | boolean | RGB | RGBA; | |
description?: string; | |
scopes?: VariableScope[]; | |
collectionId?: string; | |
} | |
// Basic interfaces | |
interface CollectionInfo { | |
id: string; | |
name: string; | |
modes: { name: string; modeId: string }[]; // Keep modeId in info only for reference | |
} | |
interface CollectionConfig { | |
name: string; | |
modes?: { name: string }[]; | |
description?: string; | |
} | |
interface ModeUpdate { | |
oldName: string; | |
newName: string; | |
} | |
export class FigmaVariablesManager { | |
/** | |
* Gets or creates a variable in the specified collection | |
*/ | |
static getOrCreateVariable( | |
name: string, | |
collection: VariableCollection, | |
type: VariableResolvedDataType = 'COLOR' | |
): Variable { | |
try { | |
// Try to find existing variable | |
const existingVariable = figma.variables.getLocalVariables(type) | |
.find(v => v.name === name && v.variableCollectionId === collection.id); | |
if (existingVariable) { | |
return existingVariable; | |
} | |
// Create new if not found | |
return figma.variables.createVariable(name, collection as any, type); | |
} catch (error) { | |
console.error(`Error in getOrCreateVariable for ${name}:`, error); | |
throw error; | |
} | |
} | |
/** | |
* Creates a new variable with additional properties | |
*/ | |
static async createVariable({ | |
name, | |
type, | |
value, | |
description = '', | |
scopes = ['ALL_SCOPES'], | |
collectionId, | |
}: VariableConfig): Promise<Variable | null> { | |
try { | |
// Get or create collection if not provided | |
const collections = figma.variables.getLocalVariableCollections(); | |
const collection = collectionId | |
? collections.find(c => c.id === collectionId) | |
: collections[0] || figma.variables.createVariableCollection('Default Collection'); | |
if (!collection) { | |
throw new Error('Unable to find or create variable collection'); | |
} | |
// Get or create the variable | |
const variable = this.getOrCreateVariable(name, collection, type); | |
// Set additional properties | |
const modeId = collection.defaultModeId; | |
variable.setValueForMode(modeId, value); | |
variable.description = description; | |
variable.scopes = scopes; | |
return variable; | |
} catch (error) { | |
console.error('Error creating variable:', error); | |
return null; | |
} | |
} | |
/** | |
* Updates an existing variable by name and collection | |
*/ | |
static async updateVariable( | |
name: string, | |
updates: Partial<VariableConfig> | |
): Promise<Variable | null> { | |
try { | |
const type = updates.type || 'COLOR'; // Default type if not provided | |
const variable = figma.variables.getLocalVariables(type) | |
.find(v => v.name === name); | |
if (!variable) { | |
throw new Error(`Variable with name ${name} not found`); | |
} | |
// Update basic properties | |
if (updates.name) variable.name = updates.name; | |
if (updates.description) variable.description = updates.description; | |
if (updates.scopes) variable.scopes = updates.scopes; | |
// Update value if provided | |
if (updates.value) { | |
const collection = figma.variables.getLocalVariableCollections() | |
.find(c => c.id === variable.variableCollectionId); | |
if (collection) { | |
const modeId = collection.defaultModeId; | |
variable.setValueForMode(modeId, updates.value); | |
} | |
} | |
return variable; | |
} catch (error) { | |
console.error('Error updating variable:', error); | |
return null; | |
} | |
} | |
/** | |
* Deletes a variable by name and type | |
*/ | |
static async deleteVariable(name: string, type: VariableResolvedDataType = 'COLOR'): Promise<boolean> { | |
try { | |
const variable = figma.variables.getLocalVariables(type) | |
.find(v => v.name === name); | |
if (!variable) { | |
throw new Error(`Variable with name ${name} not found`); | |
} | |
variable.remove(); | |
return true; | |
} catch (error) { | |
console.error('Error deleting variable:', error); | |
return false; | |
} | |
} | |
/** | |
* Gets all variables in a collection | |
*/ | |
static getCollectionVariables(collectionId: string): Variable[] { | |
const collection = figma.variables.getLocalVariableCollections() | |
.find(c => c.id === collectionId); | |
if (!collection) return []; | |
return Array.from(collection.variableIds) | |
.map(id => | |
figma.variables.getLocalVariables() | |
.find(v => v.id === id) | |
) | |
.filter(Boolean) as Variable[]; | |
} | |
/** | |
* Bulk creates variables from a config array | |
*/ | |
static async bulkCreateVariables( | |
configs: VariableConfig[] | |
): Promise<(Variable | null)[]> { | |
const results = await Promise.all( | |
configs.map(config => this.createVariable(config)) | |
); | |
return results; | |
} | |
} | |
// Example usage: | |
/* | |
// Create a new collection with multiple modes | |
const collection = FigmaVariablesManager.createCollection({ | |
name: 'Theme Variables', | |
modes: [ | |
{ name: 'Light', modeId: '1:0' }, | |
{ name: 'Dark', modeId: '1:1' } | |
], | |
description: 'Theme variables for the design system' | |
}); | |
// Update collection | |
const updatedCollection = FigmaVariablesManager.updateCollection(collection.id, { | |
name: 'Updated Theme', | |
modes: [ | |
{ name: 'Light Mode', modeId: '1:0' }, | |
{ name: 'Dark Mode', modeId: '1:1' }, | |
{ name: 'High Contrast', modeId: '1:2' } | |
] | |
}); | |
// Rename a mode | |
FigmaVariablesManager.renameMode(collection.id, '1:0', 'Default Light'); | |
// Copy variables between collections | |
const copiedVars = FigmaVariablesManager.copyVariablesToCollection( | |
sourceCollectionId, | |
targetCollectionId, | |
['primary-color', 'secondary-color'] | |
); | |
*/ | |
///////// COllection Manager ////// | |
export class CollectionsManager { | |
/** | |
* Lists all available collections with their info | |
*/ | |
static listCollections(): CollectionInfo[] { | |
return figma.variables.getLocalVariableCollections() | |
.map(collection => ({ | |
id: collection.id, | |
name: collection.name, | |
modes: collection.modes | |
})); | |
} | |
/** | |
* Gets collection by ID | |
*/ | |
static findCollectionById(id: string): VariableCollection | null { | |
return figma.variables.getLocalVariableCollections() | |
.find(c => c.id === id) || null; | |
} | |
/** | |
* Gets collection info by name | |
*/ | |
static findCollectionByName(name: string): CollectionInfo | null { | |
const collection = figma.variables.getLocalVariableCollections() | |
.find(c => c.name === name); | |
if (!collection) return null; | |
return { | |
id: collection.id, | |
name: collection.name, | |
modes: collection.modes | |
}; | |
} | |
/** | |
* Creates a new collection with specified modes | |
*/ | |
static createCollection({ | |
name, | |
modes = [], | |
description = '' | |
}: CollectionConfig): VariableCollection { | |
try { | |
// Check for existing collection | |
const existingCollection = this.findCollectionByName(name); | |
if (existingCollection) { | |
throw new Error(`Collection with name "${name}" already exists`); | |
} | |
// Create collection (comes with default "Mode 1") | |
const collection = figma.variables.createVariableCollection(name); | |
if (description) { | |
// @ts-ignore - Description might be supported in future | |
collection.description = description; | |
} | |
// Rename default mode if first mode provided | |
if (modes.length > 0) { | |
collection.renameMode(collection.defaultModeId, modes[0].name); | |
} | |
// Add additional modes | |
modes.slice(1).forEach(mode => { | |
collection.addMode(mode.name); | |
}); | |
return collection; | |
} catch (error) { | |
console.error('Error creating collection:', error); | |
throw error; | |
} | |
} | |
/** | |
* Gets or creates a collection | |
*/ | |
static getOrCreateCollection(config: CollectionConfig): VariableCollection { | |
const existingCollection = this.findCollectionByName(config.name); | |
if (existingCollection) { | |
return this.findCollectionById(existingCollection.id)!; | |
} | |
return this.createCollection(config); | |
} | |
/** | |
* Updates a collection by name | |
*/ | |
static updateCollectionByName( | |
name: string, | |
updates: Partial<CollectionConfig> | |
): VariableCollection | null { | |
const collection = this.findCollectionByName(name); | |
if (!collection) return null; | |
return this.updateCollection(collection.id, updates); | |
} | |
/** | |
* Updates an existing collection by ID | |
*/ | |
static updateCollection( | |
collectionId: string, | |
updates: Partial<CollectionConfig> | |
): VariableCollection | null { | |
try { | |
const collection = this.findCollectionById(collectionId); | |
if (!collection) { | |
throw new Error(`Collection with ID ${collectionId} not found`); | |
} | |
// Update name if provided | |
if (updates.name) { | |
collection.name = updates.name; | |
} | |
// Update description if provided | |
if (updates.description) { | |
// @ts-ignore - Description might be supported in future | |
collection.description = updates.description; | |
} | |
// Update modes if provided | |
if (updates.modes?.length) { | |
// Rename default mode to first mode name | |
collection.renameMode(collection.defaultModeId, updates.modes[0].name); | |
// Remove all non-default modes | |
collection.modes.forEach(mode => { | |
if (mode.modeId !== collection.defaultModeId) { | |
collection.removeMode(mode.modeId); | |
} | |
}); | |
// Add remaining modes | |
updates.modes.slice(1).forEach(mode => { | |
collection.addMode(mode.name); | |
}); | |
} | |
return collection; | |
} catch (error) { | |
console.error('Error updating collection:', error); | |
return null; | |
} | |
} | |
/** | |
* Deletes a collection by name | |
*/ | |
static deleteCollectionByName(name: string, force = false): boolean { | |
const collection = this.findCollectionByName(name); | |
if (!collection) return false; | |
return this.deleteCollection(collection.id, force); | |
} | |
/** | |
* Deletes a collection by ID | |
*/ | |
static deleteCollection(collectionId: string, force = false): boolean { | |
try { | |
const collection = this.findCollectionById(collectionId); | |
if (!collection) { | |
throw new Error(`Collection with ID ${collectionId} not found`); | |
} | |
if (!force && figma.variables.getLocalVariables() | |
.some(v => v.variableCollectionId === collectionId)) { | |
throw new Error('Collection contains variables. Use force=true to delete anyway'); | |
} | |
collection.remove(); | |
return true; | |
} catch (error) { | |
console.error('Error deleting collection:', error); | |
return false; | |
} | |
} | |
} | |
// Usage example: | |
/* | |
// Create a new collection | |
const themeCollection = CollectionsManager.createCollection({ | |
name: 'Theme', | |
modes: [ | |
{ name: 'Light', modeId: '1:0' }, | |
{ name: 'Dark', modeId: '1:1' } | |
], | |
description: 'Theme variables for the design system' | |
}); | |
// Get or create a collection | |
const systemCollection = CollectionsManager.getOrCreateCollection({ | |
name: 'Design System', | |
modes: [ | |
{ name: 'Default', modeId: '1:0' }, | |
{ name: 'High Contrast', modeId: '1:1' } | |
] | |
}); | |
// Update collection with new modes and renames | |
const updatedCollection = CollectionsManager.updateCollectionByName('Theme', { | |
modes: [ | |
{ name: 'Light Mode', modeId: '1:0' }, | |
{ name: 'Dark Mode', modeId: '1:1' }, | |
{ name: 'System', modeId: '1:2' } | |
], | |
renameMode: [ | |
{ oldName: 'Light', newName: 'Light 2.0' }, | |
{ oldName: 'Dark', newName: 'Dark 2.0' } | |
], | |
defaultModeId: '1:0' | |
}); | |
// List all collections | |
const collections = CollectionsManager.listCollections(); | |
console.log('Available collections:', collections); | |
// Delete a collection | |
const deleted = CollectionsManager.deleteCollectionByName('Theme', true); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment