Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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