diff --git a/src/processors/background.processor.ts b/src/processors/background.processor.ts index 53d4e07..1a2f94c 100644 --- a/src/processors/background.processor.ts +++ b/src/processors/background.processor.ts @@ -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; diff --git a/src/tests/__snapshots__/background-processors.test.ts.snap b/src/tests/__snapshots__/background-processors.test.ts.snap index efe0cc0..7eca91c 100644 --- a/src/tests/__snapshots__/background-processors.test.ts.snap +++ b/src/tests/__snapshots__/background-processors.test.ts.snap @@ -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 @@ -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 diff --git a/src/tests/background-processors.test.ts b/src/tests/background-processors.test.ts index bbbe3f2..f338fbe 100644 --- a/src/tests/background-processors.test.ts +++ b/src/tests/background-processors.test.ts @@ -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, @@ -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%);'); }); }); \ No newline at end of file diff --git a/src/utils/gradient.utils.ts b/src/utils/gradient.utils.ts index f929ec7..98f4f31 100644 --- a/src/utils/gradient.utils.ts +++ b/src/utils/gradient.utils.ts @@ -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 ''; }