Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ interface StyleProcessor {
bindingKey?: string;
process: (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
) => Promise<ProcessedValue | null>;
}
```
Expand Down
4 changes: 3 additions & 1 deletion src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function transformTokensToStylesheet(
case 'scss':
return transformToScss(tokens, useCombinatorialParsing);
case 'css':
return transformToCss(tokens);
return transformToCss(tokens, useCombinatorialParsing);
case 'tailwind-scss':
return transformToTailwindSassClass(tokens, useCombinatorialParsing);
case 'tailwind-v4':
Expand Down Expand Up @@ -186,6 +186,8 @@ figma.ui.onmessage = async (msg: MessageToMainThreadPayload) => {
const node = await figma.getNodeByIdAsync(msg.nodeId);
if (node && 'type' in node) {
// This checks if it's a SceneNode
// TODO This doesn't properly verify that it's a SceneNode
// we should stick to BaseNode | (BaseNode & ChildrenMixin)
figma.currentPage.selection = [node as SceneNode];
figma.viewport.scrollAndZoomIntoView([node]);
}
Expand Down
8 changes: 4 additions & 4 deletions src/processors/background.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export const backgroundProcessors: StyleProcessor[] = [
bindingKey: 'fills',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
if (node && 'fills' in node && Array.isArray(node.fills)) {
if ('fills' in node && Array.isArray(node.fills)) {
const visibleFills = node.fills.filter((fill) => fill.visible);
if (!visibleFills.length) return null;

Expand Down Expand Up @@ -60,9 +60,9 @@ export const backgroundProcessors: StyleProcessor[] = [
bindingKey: 'opacity',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
if (node && 'opacity' in node && node.opacity !== 1) {
if ('opacity' in node && node.opacity !== 1) {
const opacityVariable = variableTokenMapByProperty.get('opacity');
if (opacityVariable) {
return {
Expand Down
31 changes: 12 additions & 19 deletions src/processors/border.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export const borderProcessors: StyleProcessor[] = [
property: 'border',
bindingKey: 'strokes',
process: async (variableTokenMapByProperty, node): Promise<ProcessedValue | null> => {
if (!node) return null;

// For non-rectangular shapes, we don't care about strokeAlign
const isRectangular =
node.type === 'RECTANGLE' || node.type === 'COMPONENT' || node.type === 'INSTANCE';
Expand Down Expand Up @@ -60,8 +58,7 @@ export const borderProcessors: StyleProcessor[] = [
)
: getBorderWidth('strokeTopWeight', weights.top, variableTokenMapByProperty);

const type =
node && 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';
const type = 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';

const value = `${width.value} ${type} ${color.value}`;
const rawValue = `${width.rawValue} ${type} ${color.rawValue}`;
Expand Down Expand Up @@ -121,7 +118,7 @@ export const borderProcessors: StyleProcessor[] = [
property: 'outline',
bindingKey: undefined,
process: async (variableTokenMapByProperty, node): Promise<ProcessedValue | null> => {
if (node && 'strokeAlign' in node && node.strokeAlign !== 'OUTSIDE') {
if ('strokeAlign' in node && node.strokeAlign !== 'OUTSIDE') {
return null;
}

Expand All @@ -138,8 +135,7 @@ export const borderProcessors: StyleProcessor[] = [
Object.values(weights).find((w) => w > 0) || 0,
variableTokenMapByProperty,
);
const type =
node && 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';
const type = 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';

const value = `${width.value} ${type} ${color.value}`;
const rawValue = `${width.rawValue} ${type} ${color.rawValue}`;
Expand All @@ -155,7 +151,7 @@ export const borderProcessors: StyleProcessor[] = [
property: 'box-shadow',
bindingKey: undefined,
process: async (variableTokenMapByProperty, node): Promise<ProcessedValue | null> => {
if (node && 'strokeAlign' in node && node.strokeAlign !== 'INSIDE') {
if ('strokeAlign' in node && node.strokeAlign !== 'INSIDE') {
return null;
}

Expand Down Expand Up @@ -234,7 +230,6 @@ export const borderProcessors: StyleProcessor[] = [
}

// Handle nodes with cornerRadius
if (!node) return null;
const radii = getCornerRadii(node, variableTokenMapByProperty);
if (!radii) return null;

Expand All @@ -248,9 +243,7 @@ export const borderProcessors: StyleProcessor[] = [
];

// Utility functions for border processing
const getBorderWeights = (node?: SceneNode): BorderWeights => {
if (!node) return { top: 0, right: 0, bottom: 0, left: 0 };

const getBorderWeights = (node: SceneNode): BorderWeights => {
// For lines, vectors, and ellipses, they use a single strokeWeight
if (node.type === 'LINE' || node.type === 'VECTOR' || node.type === 'ELLIPSE') {
const weight: number = 'strokeWeight' in node ? Number(node.strokeWeight) : 0;
Expand Down Expand Up @@ -282,8 +275,8 @@ const areAllBordersEqual = (weights: BorderWeights): boolean => {
return nonZeroWeights.length > 0 && nonZeroWeights.every((w) => w === nonZeroWeights[0]);
};

const shouldUseShorthand = (node?: SceneNode, weights?: BorderWeights): boolean => {
if (!node || !weights) return false;
const shouldUseShorthand = (node: SceneNode, weights?: BorderWeights): boolean => {
if (!weights) return false;

// For lines, vectors, and ellipses, always use shorthand
if (node.type === 'LINE' || node.type === 'VECTOR' || node.type === 'ELLIPSE') {
Expand All @@ -295,7 +288,7 @@ const shouldUseShorthand = (node?: SceneNode, weights?: BorderWeights): boolean
};

const getBorderColor = (
node?: SceneNode,
node: SceneNode,
variableTokenMapByProperty?: Map<string, VariableToken>,
): BorderColor | null => {
const borderVariable = variableTokenMapByProperty?.get('strokes');
Expand All @@ -307,7 +300,7 @@ const getBorderColor = (
};
}

if (node && 'strokes' in node && Array.isArray(node.strokes) && node.strokes.length > 0) {
if ('strokes' in node && Array.isArray(node.strokes) && node.strokes.length > 0) {
const stroke = node.strokes[0] as Paint;
if (stroke?.type === 'SOLID') {
const { r, g, b } = stroke.color;
Expand Down Expand Up @@ -345,11 +338,11 @@ const getBorderWidth = (
const processBorderSide = async (
config: BorderSideConfig,
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
processedProperties?: Set<string>,
): Promise<ProcessedValue | null> => {
// For lines, vectors, and ellipses, don't process individual sides
if (node && (node.type === 'LINE' || node.type === 'VECTOR' || node.type === 'ELLIPSE')) {
if (node.type === 'LINE' || node.type === 'VECTOR' || node.type === 'ELLIPSE') {
return null;
}

Expand All @@ -371,7 +364,7 @@ const processBorderSide = async (
weights[config.weightKey],
variableTokenMapByProperty,
);
const type = node && 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';
const type = 'dashPattern' in node && node.dashPattern.length > 0 ? 'dashed' : 'solid';

const value = `${width.value} ${type} ${color.value}`;
const rawValue = `${width.rawValue} ${type} ${color.rawValue}`;
Expand Down
46 changes: 23 additions & 23 deletions src/processors/font.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const fontProcessors: StyleProcessor[] = [
bindingKey: 'fontFamily',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
const fontVariable = variableTokenMapByProperty.get('fontFamily');
if (fontVariable) {
Expand All @@ -72,7 +72,7 @@ export const fontProcessors: StyleProcessor[] = [
};
}

if (node?.type === 'TEXT' && node.fontName && typeof node.fontName === 'object') {
if (node.type === 'TEXT' && node.fontName && typeof node.fontName === 'object') {
const value = node.fontName.family;
return { value, rawValue: value };
}
Expand All @@ -84,7 +84,7 @@ export const fontProcessors: StyleProcessor[] = [
bindingKey: 'fontSize',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
const sizeVariable = variableTokenMapByProperty.get('fontSize');
if (sizeVariable) {
Expand All @@ -95,7 +95,7 @@ export const fontProcessors: StyleProcessor[] = [
};
}

if (node?.type === 'TEXT') {
if (node.type === 'TEXT') {
const value = `${String(node.fontSize)}px`;
return { value, rawValue: value, valueType: 'px' };
}
Expand All @@ -107,9 +107,9 @@ export const fontProcessors: StyleProcessor[] = [
bindingKey: 'fontWeight',
process: async (
variableTokenMapByProperty,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
if (!node || !hasFont(node)) return null;
if (!hasFont(node)) return null;

if (node.fontWeight) {
if (isSymbol(node.fontWeight)) {
Expand Down Expand Up @@ -190,7 +190,7 @@ export const fontProcessors: StyleProcessor[] = [
bindingKey: 'lineHeight',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
const heightVariable = variableTokenMapByProperty.get('lineHeight');
if (heightVariable) {
Expand All @@ -200,7 +200,7 @@ export const fontProcessors: StyleProcessor[] = [
};
}

if (node?.type === 'TEXT' && 'lineHeight' in node) {
if (node.type === 'TEXT' && 'lineHeight' in node) {
const lineHeight = node.lineHeight;
if (typeof lineHeight === 'object') {
if (lineHeight.unit === 'AUTO') {
Expand All @@ -223,7 +223,7 @@ export const fontProcessors: StyleProcessor[] = [
bindingKey: 'letterSpacing',
process: async (
variableTokenMapByProperty: Map<string, VariableToken>,
node?: SceneNode,
node: SceneNode,
): Promise<ProcessedValue | null> => {
const spacingVariable = variableTokenMapByProperty.get('letterSpacing');
if (spacingVariable) {
Expand All @@ -234,7 +234,7 @@ export const fontProcessors: StyleProcessor[] = [
};
}

if (node?.type === 'TEXT' && 'letterSpacing' in node) {
if (node.type === 'TEXT' && 'letterSpacing' in node) {
const letterSpacing = node.letterSpacing;
if (typeof letterSpacing === 'object' && letterSpacing.value !== 0) {
const type = letterSpacing.unit.toLowerCase() === 'percent' ? '%' : 'px';
Expand All @@ -248,10 +248,10 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'display',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
// Only apply flex if text has explicit sizing/alignment
if (
node?.type === 'TEXT' &&
node.type === 'TEXT' &&
(node.textAutoResize !== 'WIDTH_AND_HEIGHT' || node.textAlignVertical !== 'TOP')
) {
return { value: 'flex', rawValue: 'flex' };
Expand All @@ -262,10 +262,10 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'flex-direction',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
// Only apply if we're using flex display
if (
node?.type === 'TEXT' &&
node.type === 'TEXT' &&
(node.textAutoResize !== 'WIDTH_AND_HEIGHT' || node.textAlignVertical !== 'TOP')
) {
return { value: 'column', rawValue: 'column' };
Expand All @@ -276,9 +276,9 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'justify-content',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
// Only apply if we're using flex display and have vertical alignment
if (node?.type === 'TEXT' && node.textAlignVertical !== 'TOP') {
if (node.type === 'TEXT' && node.textAlignVertical !== 'TOP') {
const alignMap = {
TOP: 'flex-start',
CENTER: 'center',
Expand All @@ -293,10 +293,10 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'text-align',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
if (!node || !hasTextAlign(node)) return null;
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
if (!hasTextAlign(node)) return null;

if (node?.type === 'TEXT' && node.textAlignHorizontal !== 'LEFT') {
if (node.type === 'TEXT' && node.textAlignHorizontal !== 'LEFT') {
const alignmentMap: Record<string, string> = {
LEFT: 'left',
CENTER: 'center',
Expand All @@ -318,9 +318,9 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'width',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
// Only apply width if text doesn't auto-resize width
if (node?.type === 'TEXT' && !['WIDTH_AND_HEIGHT', 'WIDTH'].includes(node.textAutoResize)) {
if (node.type === 'TEXT' && !['WIDTH_AND_HEIGHT', 'WIDTH'].includes(node.textAutoResize)) {
return { value: `${node.width}px`, rawValue: `${node.width}px`, valueType: 'px' };
}
return null;
Expand All @@ -329,9 +329,9 @@ export const fontProcessors: StyleProcessor[] = [
{
property: 'height',
bindingKey: undefined,
process: async (_, node?: SceneNode): Promise<ProcessedValue | null> => {
process: async (_, node: SceneNode): Promise<ProcessedValue | null> => {
// Only apply height if text doesn't auto-resize height
if (node?.type === 'TEXT' && !['WIDTH_AND_HEIGHT', 'HEIGHT'].includes(node.textAutoResize)) {
if (node.type === 'TEXT' && !['WIDTH_AND_HEIGHT', 'HEIGHT'].includes(node.textAutoResize)) {
return { value: `${node.height}px`, rawValue: `${node.height}px`, valueType: 'px' };
}
return null;
Expand Down
Loading