Skip to content

Commit

Permalink
added back basic linear-gradient
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattchewone committed Jan 29, 2025
1 parent 53477aa commit 3f70ecd
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 59 deletions.
6 changes: 4 additions & 2 deletions src/processors/background.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ export const backgroundProcessor: StyleProcessor = {

// TODO get gradient working
if (fill.type.startsWith('GRADIENT_')) {
// const value = processGradient(fill as GradientPaint, node.width, node.height);
// return { value, rawValue: value };
const value = processGradient(fill as GradientPaint);
if (value) {
return { value, rawValue: value };
}
}

return null;
Expand Down
21 changes: 21 additions & 0 deletions src/tests/__snapshots__/background-processors.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Background Processors should process background gradient correctly: gradient styles 1`] = `
{
"angular": null,
"linear": "background: linear-gradient(224deg, #00464A 0%, #04646A 100%);",
"linearAlpha": "background: linear-gradient(224deg, rgba(0, 70, 74, 0.5) 0%, #04646A 100%);",
"radial": null,
}
`;

exports[`Background Processors should process background solid correctly - sass: solid styles 1`] = `
"// Generated SCSS Variables
$brand-primary: #00464a
Expand All @@ -16,12 +25,24 @@ $teal-800: #00464a
@mixin background-solid-alpha-variable
background: $teal-800-50
@mixin background-gradient-linear-style
background: linear-gradient(224deg, #00464A 0%, #04646A 100%)
@mixin background-gradient-linear-alpha-style
background: linear-gradient(224deg, rgba(0, 70, 74, 0.5) 0%, #04646A 100%)
@mixin background-solid-custom
background: #00464a
@mixin background-solid-alpha-custom
background: rgba(0, 70, 74, 0.5)
@mixin background-gradient-linear-custom
background: linear-gradient(224deg, #00464A 0%, #04646A 100%)
@mixin background-gradient-linear-alpha-custom
background: linear-gradient(224deg, rgba(0, 70, 74, 0.5) 0%, #04646A 100%)
@mixin background-solid-hex-style
background: #00464a
Expand Down
6 changes: 3 additions & 3 deletions src/tests/background-processors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Background Processors', () => {
expect(scss).toMatchSnapshot('solid styles');
});

it.skip('should process background gradient correctly', async () => {
it('should process background gradient correctly', async () => {
// Create proper node hierarchy in test data
const pageNode = {
...testData,
Expand Down Expand Up @@ -134,7 +134,7 @@ describe('Background Processors', () => {
expect(styles).toMatchSnapshot('gradient styles');

// Keep direct assertions for critical values
expect(styles.linear).toBe('background: linear-gradient(224deg, #00464A 3.74%, #04646A 98.29%);');
expect(styles.linearAlpha).toBe('background: linear-gradient(224deg, rgba(0, 70, 74, 0.50) 3.74%, #04646A 98.29%);');
expect(styles.linear).toBe('background: linear-gradient(224deg, #00464A 0%, #04646A 100%);');
expect(styles.linearAlpha).toBe('background: linear-gradient(224deg, rgba(0, 70, 74, 0.5) 0%, #04646A 100%);');
});
});
104 changes: 50 additions & 54 deletions src/utils/gradient.utils.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,72 @@
function calculateGradientAngle(matrix: Transform): number {
const [[a, b]] = matrix;
// Calculate angle using arctangent of matrix components
const angle = Math.atan2(-b, -a) * (180 / Math.PI);
return Math.round((angle + 180) % 180);
const [[a], [b]] = matrix;

// Calculate angle in radians using arctangent
let angleRad = Math.atan2(b, a);

// Convert to degrees and adjust for CSS gradient angle convention
// CSS gradients: 0deg = to top, 90deg = to right, 180deg = to bottom, 270deg = to left
let angleDeg = (angleRad * 180) / Math.PI;

// Adjust angle to match CSS gradient convention:
// (figma starts at 90deg)
// 1. Add 90 to rotate coordinate system (CSS 0deg is up, math 0deg is right)
// 2. Negate the angle (CSS rotates clockwise, math rotates counter-clockwise)
// 3. Add 360 and mod 360 to ensure positive angle
angleDeg = ((-angleDeg + 90) + 360) % 360;

return Math.round(angleDeg);
}

function calculateStopPositions(
gradientTransform: Transform,
stops: readonly ColorStop[],
): string[] {
const [[a, b, tx], [c, d, ty]] = gradientTransform;

// Calculate gradient vector length for normalization
const vectorLength = Math.sqrt(a * a + c * c);

return stops.map((stop) => {
const color = stop.color.a !== 1
? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
: `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();

// Apply affine transformation to the stop position
const t = stop.position;

// Transform point using matrix multiplication
const transformedX = (a * t) + tx;
const transformedY = (c * t) + ty;

// Project point onto gradient vector and normalize
const dotProduct = (transformedX * a + transformedY * c);
const position = (dotProduct / (vectorLength * vectorLength)) * 100;

return `${color} ${position.toFixed(2)}%`;
return `${color} ${stop.position * 100}%`;
});
}

export function processGradient(fill: GradientPaint, width: number, height: number): string {
export function processGradient(fill: GradientPaint): string {
switch (fill.type) {
case 'GRADIENT_LINEAR': {
const angle = calculateGradientAngle(fill.gradientTransform);
const stops = calculateStopPositions(fill.gradientTransform, fill.gradientStops);
const stops = calculateStopPositions(fill.gradientStops);
return `linear-gradient(${angle}deg, ${stops.join(', ')})`;
}
case 'GRADIENT_RADIAL': {
const stops = fill.gradientStops.map(stop => {
const color = stop.color.a !== 1
? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
: `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
return `${color} ${stop.position * 100}%`;
}).join(', ');
return `radial-gradient(100% 100% at 100% 100%, ${stops})`;
}
case 'GRADIENT_ANGULAR': {
const stops = fill.gradientStops.map(stop => {
const color = stop.color.a !== 1
? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
: `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
return `${color} ${stop.position * 360}deg`;
}).join(', ');
return `conic-gradient(from 134deg at 50% 50%, ${stops})`;
}
case 'GRADIENT_DIAMOND': {
const stops = fill.gradientStops.map(stop => {
const color = stop.color.a !== 1
? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
: `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
return `${color} ${stop.position * 100}%`;
}).join(', ');
return `linear-gradient(to bottom right, ${stops}) bottom right / 50% 50% no-repeat, ` +
`linear-gradient(to bottom left, ${stops}) bottom left / 50% 50% no-repeat, ` +
`linear-gradient(to top left, ${stops}) top left / 50% 50% no-repeat, ` +
`linear-gradient(to top right, ${stops}) top right / 50% 50% no-repeat`;
}
// case 'GRADIENT_RADIAL': {
// const stops = fill.gradientStops.map(stop => {
// const color = stop.color.a !== 1
// ? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
// : `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
// return `${color} ${stop.position * 100}%`;
// }).join(', ');
// return `radial-gradient(100% 100% at 100% 100%, ${stops})`;
// }
// case 'GRADIENT_ANGULAR': {
// const stops = fill.gradientStops.map(stop => {
// const color = stop.color.a !== 1
// ? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
// : `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
// return `${color} ${stop.position * 360}deg`;
// }).join(', ');
// return `conic-gradient(from 134deg at 50% 50%, ${stops})`;
// }
// case 'GRADIENT_DIAMOND': {
// const stops = fill.gradientStops.map(stop => {
// const color = stop.color.a !== 1
// ? `rgba(${Math.round(stop.color.r * 255)}, ${Math.round(stop.color.g * 255)}, ${Math.round(stop.color.b * 255)}, ${stop.color.a})`
// : `#${Math.round(stop.color.r * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.g * 255).toString(16).padStart(2, '0')}${Math.round(stop.color.b * 255).toString(16).padStart(2, '0')}`.toUpperCase();
// return `${color} ${stop.position * 100}%`;
// }).join(', ');
// return `linear-gradient(to bottom right, ${stops}) bottom right / 50% 50% no-repeat, ` +
// `linear-gradient(to bottom left, ${stops}) bottom left / 50% 50% no-repeat, ` +
// `linear-gradient(to top left, ${stops}) top left / 50% 50% no-repeat, ` +
// `linear-gradient(to top right, ${stops}) top right / 50% 50% no-repeat`;
// }
default:
return '';
}
Expand Down

0 comments on commit 3f70ecd

Please sign in to comment.