Files
galaxy/resources/bundled/mcp_skills/figma/figma-generate-library/scripts/createComponentWithVariants.js
T

149 lines
4.8 KiB
JavaScript

/**
* createComponentWithVariants
*
* Creates a component set by generating all combinations of `variantAxes`,
* building one Figma component per combination, then calling
* `figma.combineAsVariants` to produce the component set. After combining,
* the variants are repositioned into a grid so they don't all stack at (0, 0).
*
* @param {{
* name: string,
* variantAxes: Record<string, string[]>,
* baseProps: {
* width: number,
* height: number,
* fills?: Paint[],
* padding?: {top?: number, bottom?: number, left?: number, right?: number},
* radius?: number,
* layoutMode?: 'HORIZONTAL' | 'VERTICAL' | 'NONE',
* itemSpacing?: number
* },
* page: PageNode
* }} config
* - `name`: Component set name (e.g. "Button").
* - `variantAxes`: Each key is a variant property name; each value is an array of
* allowed values. All combinations are generated (Cartesian product).
* Example: { Size: ['Small', 'Medium', 'Large'], Style: ['Primary', 'Ghost'] }
* produces 6 variants.
* - `baseProps`: Visual properties applied to every variant.
* - `page`: The PageNode to create components on (must be set as current page by caller).
* @param {string} [runId] - Optional dsb_run_id to tag every node.
* @returns {Promise<{
* componentSet: ComponentSetNode,
* variants: ComponentNode[]
* }>}
*/
async function createComponentWithVariants(config, runId) {
const { name, variantAxes, baseProps, page } = config
// Ensure we are on the correct page
await figma.setCurrentPageAsync(page)
// Compute Cartesian product of variant axes
const axisNames = Object.keys(variantAxes)
const axisValues = axisNames.map((k) => variantAxes[k])
const combinations = cartesianProduct(axisValues)
// Build one component per combination
const components = []
for (const combo of combinations) {
const comp = figma.createComponent()
// Name: "Property=Value, Property=Value, ..."
comp.name = axisNames.map((ax, i) => `${ax}=${combo[i]}`).join(', ')
// Base geometry
comp.resize(baseProps.width, baseProps.height)
// Fills
if (baseProps.fills !== undefined) {
comp.fills = baseProps.fills
} else {
comp.fills = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }]
}
// Corner radius
if (baseProps.radius !== undefined) {
comp.cornerRadius = baseProps.radius
}
// Auto-layout
if (baseProps.layoutMode && baseProps.layoutMode !== 'NONE') {
comp.layoutMode = baseProps.layoutMode
comp.primaryAxisAlignItems = 'CENTER'
comp.counterAxisAlignItems = 'CENTER'
if (baseProps.itemSpacing !== undefined) {
comp.itemSpacing = baseProps.itemSpacing
}
}
// Padding
if (baseProps.padding) {
comp.paddingTop = baseProps.padding.top ?? 0
comp.paddingBottom = baseProps.padding.bottom ?? 0
comp.paddingLeft = baseProps.padding.left ?? 0
comp.paddingRight = baseProps.padding.right ?? 0
}
// Plugin data
const variantKey = axisNames.map((ax, i) => `${ax}:${combo[i]}`).join('|')
comp.setPluginData('dsb_key', `component/${name}/${variantKey}`)
if (runId) {
comp.setPluginData('dsb_run_id', runId)
}
page.appendChild(comp)
components.push(comp)
}
// Combine into a component set
const componentSet = figma.combineAsVariants(components, page)
componentSet.name = name
componentSet.setPluginData('dsb_key', `componentSet/${name}`)
if (runId) {
componentSet.setPluginData('dsb_run_id', runId)
}
// Grid layout — variants stack at (0, 0) after combineAsVariants; reposition them.
const GRID_GAP = 16
const cols = Math.max(1, axisValues[axisValues.length - 1]?.length ?? 1)
const variantWidth = baseProps.width
const variantHeight = baseProps.height
componentSet.children.forEach((variant, idx) => {
const col = idx % cols
const row = Math.floor(idx / cols)
variant.x = col * (variantWidth + GRID_GAP)
variant.y = row * (variantHeight + GRID_GAP)
})
// Resize component set to wrap its children with padding
const totalCols = Math.min(cols, combinations.length)
const totalRows = Math.ceil(combinations.length / cols)
const PADDING = 40
componentSet.resize(
totalCols * variantWidth + (totalCols - 1) * GRID_GAP + PADDING * 2,
totalRows * variantHeight + (totalRows - 1) * GRID_GAP + PADDING * 2,
)
// Position component set at a safe canvas location
componentSet.x = 480
componentSet.y = 80
return { componentSet, variants: componentSet.children }
}
/**
* Computes the Cartesian product of multiple arrays.
* cartesianProduct([[A, B], [1, 2]]) → [[A,1], [A,2], [B,1], [B,2]]
*
* @param {Array<string[]>} arrays
* @returns {string[][]}
*/
function cartesianProduct(arrays) {
return arrays.reduce(
(acc, curr) => acc.flatMap((combo) => curr.map((val) => [...combo, val])),
[[]],
)
}