Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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])),
|
||||
[[]],
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user