From 6ae7729fd695d5933fbbb0c8c9e886af7670a2ed Mon Sep 17 00:00:00 2001 From: Marco Christian Krenn Date: Mon, 24 Feb 2025 20:32:50 +0100 Subject: [PATCH] add support for HEX8 --- .changeset/fair-poems-tell.md | 5 ++ src/css/transformHEXRGBa.ts | 67 +++++++++++++++++++++----- test/spec/css/transformHEXRGBa.spec.ts | 26 ++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 .changeset/fair-poems-tell.md diff --git a/.changeset/fair-poems-tell.md b/.changeset/fair-poems-tell.md new file mode 100644 index 0000000..01b6b2c --- /dev/null +++ b/.changeset/fair-poems-tell.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': patch +--- + +Fix support for HEX8 and shorthand HEX formats in the HEXRGBa transform. diff --git a/src/css/transformHEXRGBa.ts b/src/css/transformHEXRGBa.ts index 6ac5716..ad3d0ab 100644 --- a/src/css/transformHEXRGBa.ts +++ b/src/css/transformHEXRGBa.ts @@ -2,7 +2,7 @@ import Color from 'colorjs.io'; import { DesignToken } from 'style-dictionary/types'; /** - * Helper: Transforms hex rgba colors used in figma tokens: + * Helper: Transforms hex to rgba colors used in figma tokens: * rgba(#ffffff, 0.5) =? rgba(255, 255, 255, 0.5). * This is kind of like an alpha() function. */ @@ -11,17 +11,62 @@ export function transformHEXRGBaForCSS(token: DesignToken): DesignToken['value'] const type = token.$type ?? token.type; if (val === undefined) return undefined; - const transformHEXRGBa = (val: string) => { - const regex = /rgba\(\s*(?#.+?)\s*,\s*(?\d*(\.\d*|%)*)\s*\)/g; - return val.replace(regex, (match, hex, alpha) => { - try { - const [r, g, b] = new Color(hex).srgb; - return `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${alpha})`; - } catch (e) { - console.warn(`Tried parsing "${hex}" as a hex value, but failed.`); - return match; + const transformHexColor = (hex: string) => { + try { + // Fast path for invalid hex + if (hex.length < 4) return hex; + + // Determine format based on length + const hexLength = hex.length - 1; // subtract 1 for # + + // Only transform hex colors with alpha channel + const hasAlpha = hexLength === 4 || hexLength === 8; + if (!hasAlpha) return hex; + + let hexColor = hex; + let alpha = '1'; + + // Convert shorthand to full format if necessary + if (hexLength === 4) { + const r = hex[1], + g = hex[2], + b = hex[3], + a = hex[4]; + hexColor = `#${r}${r}${g}${g}${b}${b}`; + alpha = (parseInt(a + a, 16) / 255).toString(); + } else if (hexLength === 8) { + alpha = (parseInt(hex.slice(7), 16) / 255).toString(); + hexColor = hex.slice(0, 7); } - }); + + const [r, g, b] = new Color(hexColor).srgb; + return `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${alpha})`; + } catch (e) { + return hex; + } + }; + + const transformHEXRGBa = (val: string) => { + // Handle standalone hex colors + if (val.startsWith('#')) { + return transformHexColor(val); + } + + // Handle rgba() with hex colors + if (val.includes('rgba(')) { + return val.replace(/rgba\(\s*#[A-Fa-f0-9]+\s*,\s*([0-9.%]+)\s*\)/g, (match, alpha) => { + const hex = match.substring(match.indexOf('#'), match.indexOf(',')); + try { + const [r, g, b] = new Color(hex).srgb; + return `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${alpha})`; + } catch (e) { + console.warn(`Tried parsing "${hex}" as a hex value, but failed.`); + return match; + } + }); + } + + return val; }; const transformProp = (val: Record, prop: string) => { diff --git a/test/spec/css/transformHEXRGBa.spec.ts b/test/spec/css/transformHEXRGBa.spec.ts index 0a1e8ef..04cd0c3 100644 --- a/test/spec/css/transformHEXRGBa.spec.ts +++ b/test/spec/css/transformHEXRGBa.spec.ts @@ -103,4 +103,30 @@ describe('transform HEXRGBa', () => { ).to.eql({ width: '2px', style: 'solid', color: 'rgba(0, 0, 0, 0.5)' }); }); }); + + it('transforms shorthand hex formats correctly', () => { + // 3-digit hex (#RGB) + expect(transformHEXRGBaForCSS({ value: '#F00' })).to.equal('#F00'); + + // 4-digit hex (#RGBA) + expect(transformHEXRGBaForCSS({ value: '#F00F' })).to.equal('rgba(255, 0, 0, 1)'); + expect(transformHEXRGBaForCSS({ value: '#F000' })).to.equal('rgba(255, 0, 0, 0)'); + + // Mixed formats in a single value + expect( + transformHEXRGBaForCSS({ + value: 'linear-gradient(180deg, rgba(#000, 0.5), rgba(#F00F, 0.5))', + }), + ).to.equal('linear-gradient(180deg, rgba(0, 0, 0, 0.5), rgba(255, 0, 0, 0.5))'); + }); + + it('handles invalid hex values gracefully', () => { + expect(transformHEXRGBaForCSS({ value: 'rgba(#GGG, 0.5)' })).to.equal('rgba(#GGG, 0.5)'); + expect(transformHEXRGBaForCSS({ value: 'rgba(#12, 0.5)' })).to.equal('rgba(#12, 0.5)'); + }); + + it('transforms HEX8 format correctly', () => { + expect(transformHEXRGBaForCSS({ value: '#000000FF' })).to.equal('rgba(0, 0, 0, 1)'); + expect(transformHEXRGBaForCSS({ value: '#00000000' })).to.equal('rgba(0, 0, 0, 0)'); + }); });