Skip to content

Commit

Permalink
Feat(variables-scss): Formatting color tokens #DS-1461
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed Sep 11, 2024
1 parent 457349c commit d88e01d
Show file tree
Hide file tree
Showing 18 changed files with 717 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ $grid-spacing-desktop: 32px !default;

$grid-columns: 12 !default;

$grid-spacings: (
spacing-desktop: $grid-spacing-desktop,
) !default;

$grids: (
spacing: (
desktop: $grid-spacing-desktop,
),
columns: $grid-columns,
) !default;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ColorToken,
DimensionToken,
DimensionTokenValue,
StringToken,
Expand Down Expand Up @@ -122,3 +123,108 @@ export const exampleMockedGroups: TokenGroup[] = [
updatedAt: null,
},
];

export const exampleMockedColorsTokens = new Map<string, Token>();
exampleMockedColorsTokens.set('actionColorRef', {
id: 'actionColorRef',
name: 'active',
tokenType: TokenType.color,
parentGroupId: '1',
origin: {
name: 'action/button/primary/default',
},
value: {
color: {
r: 202,
g: 32,
b: 38,
referencedTokenId: null,
},
opacity: {
unit: 'Raw',
measure: 1,
referencedTokenId: null,
},
referencedTokenId: null,
},
} as ColorToken);
exampleMockedColorsTokens.set('backgroundColorRef', {
id: 'backgroundColorRef',
name: 'primary',
tokenType: TokenType.color,
parentGroupId: '2',
origin: {
name: 'background/primary',
},
value: {
color: {
r: 255,
g: 255,
b: 255,
referencedTokenId: null,
},
opacity: {
unit: 'Raw',
measure: 1,
referencedTokenId: null,
},
referencedTokenId: null,
},
} as ColorToken);

export const exampleMockedColorGroups: TokenGroup[] = [
{
...groupFunctions,
id: '1',
idInVersion: 'idInVersionValue',
brandId: 'brandIdValue',
designSystemVersionId: 'designSystemVersionIdValue',
name: 'primary',
description: '',
isRoot: false,
tokenType: TokenType.color,
childrenIds: ['actionColorRef'],
path: ['action', 'button'],
tokenIds: ['actionColorRef'],
subgroupIds: [],
parentGroupId: 'parent1',
sortOrder: -1,
createdAt: null,
updatedAt: null,
},
{
...groupFunctions,
id: '2',
idInVersion: 'idInVersionValue',
brandId: 'brandIdValue',
designSystemVersionId: 'designSystemVersionIdValue',
name: 'background',
description: '',
isRoot: false,
tokenType: TokenType.color,
childrenIds: ['backgroundColorRef'],
path: [],
tokenIds: ['backgroundColorRef'],
subgroupIds: [],
parentGroupId: 'parent2',
sortOrder: -1,
createdAt: null,
updatedAt: null,
},
];

export const exampleMockedInvariantTokens = new Map<string, Token>();
exampleMockedInvariantTokens.set('radiiRef', {
id: 'radiiRef',
name: 'radius-full',
tokenType: TokenType.dimension,
parentGroupId: '1',
origin: {
name: 'Radius/radius-full',
},
value: {
unit: 'Pixels',
measure: 9999,
referencedTokenId: null,
},
} as DimensionToken);
2 changes: 1 addition & 1 deletion exporters/variables-scss/src/formatters/cssFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const formatCSS = (css: string): string => {

for (const line of lines) {
if (line.includes('(')) {
formattedCSS += `${IDENTATION.repeat(indentationLevel)}${line}\n`;
indentationLevel += 1;
formattedCSS += `${line}\n`;
} else if (line.includes(')')) {
indentationLevel -= 1;
formattedCSS += `${IDENTATION.repeat(indentationLevel)}${line}\n`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('contentGenerator', () => {
const groupNames = ['Grid', 'String'];
const withCssObject = true;
const hasParentPrefix = true;
const sortByNumValue = false;

const fileContent = generateFileContent(
tokens,
Expand All @@ -28,6 +29,7 @@ describe('contentGenerator', () => {
groupNames,
withCssObject,
hasParentPrefix,
sortByNumValue,
);

expect(fileContent).toStrictEqual({ content: mockedExpectedResult });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const dataProvider = [
token: {
id: '3',
name: 'unsupportedToken',
tokenType: TokenType.color,
tokenType: TokenType.duration,
} as Token,
expectedCss: null,
hasParentPrefix: true,
Expand All @@ -53,9 +53,16 @@ describe('cssGenerator', () => {

describe('generateCssFromTokens', () => {
it('should generate CSS from tokens', () => {
const css = generateCssFromTokens(Array.from(exampleMockedTokens.values()), mappedTokens, tokenGroups, true);
const css = generateCssFromTokens(
Array.from(exampleMockedTokens.values()),
mappedTokens,
tokenGroups,
'Grid',
true,
false,
);

expect(css).toBe('$grid-spacing-desktop: 32px !default;\n$grid-columns: 12 !default;');
expect(css).toBe('$grid-columns: 12 !default;\n\n$grid-spacing-desktop: 32px !default;');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { Token, TokenGroup } from '@supernovaio/sdk-exporters';
import { generateCssObjectFromTokens, generateObjectContent } from '../cssObjectGenerator';
import { exampleMockedGroups, exampleMockedTokens } from '../../formatters/__fixtures__/mockedExampleTokens';
import { Token, TokenGroup, TokenType } from '@supernovaio/sdk-exporters';
import {
createGlobalColorsObject,
createObjectStructureFromTokenNameParts,
generateCssObjectFromTokens,
getTokenAlias,
normalizeFirstNamePart,
} from '../cssObjectGenerator';
import {
exampleMockedColorGroups,
exampleMockedColorsTokens,
exampleMockedGroups,
exampleMockedInvariantTokens,
exampleMockedTokens,
} from '../../formatters/__fixtures__/mockedExampleTokens';

const mappedTokens: Map<string, Token> = new Map([]);
const tokenGroups: Array<TokenGroup> = exampleMockedGroups;
Expand All @@ -15,21 +27,88 @@ describe('cssObjectGenerator', () => {
true,
);

expect(css).toBe(
'$grid-spacings: (\nspacing-desktop: $grid-spacing-desktop,\n) !default;\n\n$grids: (\ncolumns: $grid-columns,\n) !default;\n\n',
expect(css).toStrictEqual({
$grids: { columns: '$grid-columns', spacing: { desktop: '$grid-spacing-desktop' } },
});
});

it('should generate CSS object from tokens with colors', () => {
const css = generateCssObjectFromTokens(
Array.from(exampleMockedColorsTokens.values()),
mappedTokens,
exampleMockedColorGroups,
true,
);

expect(css).toStrictEqual({
'$action-colors': {
button: {
primary: {
active: '$action-button-primary-active',
},
},
},
'$background-colors': {
primary: '$background-primary',
},
$colors: {
action: '$action-colors',
background: '$background-colors',
},
});
});
});

describe('generateObjectContent', () => {
it('should generate object content', () => {
const objectContent = generateObjectContent(
[exampleMockedTokens.get('dimensionRef') as Token],
describe('createObjectStructureFromTokenNameParts', () => {
it('should create object structure from token name parts', () => {
const cssObject = createObjectStructureFromTokenNameParts(
exampleMockedTokens.get('dimensionRef') as Token,
tokenGroups,
true,
{ $grids: { columns: '$grid-columns' } },
);

expect(objectContent).toBe('spacing-desktop: $grid-spacing-desktop,\n');
expect(cssObject).toStrictEqual({
$grids: { columns: '$grid-columns', spacing: { desktop: '$grid-spacing-desktop' } },
});
});
});

describe('handleInvariantTokens', () => {
it('should return token alias for invariant case', () => {
const token = exampleMockedInvariantTokens.get('radiiRef') as Token;
expect(getTokenAlias(token)).toBe('full');
});

it('should return token alias for non-invariant case', () => {
const token = exampleMockedTokens.get('dimensionRef') as Token;
expect(getTokenAlias(token)).toBe('desktop');
});
});

describe('getTokenAlias', () => {
it('should return token alias for non-numeric', () => {
const token = exampleMockedTokens.get('dimensionRef') as Token;
expect(getTokenAlias(token)).toBe('desktop');
});
});

describe('normalizeFirstNamePart', () => {
it('should return correct first part name for token type dimension', () => {
expect(normalizeFirstNamePart('grid', TokenType.dimension)).toBe('$grids');
});

it('should return correct first part name for token type color', () => {
expect(normalizeFirstNamePart('action', TokenType.color)).toBe('$action-colors');
});
});

describe('createGlobalColorsObject', () => {
it('should create global colors object', () => {
const colorKeys = ['$action-colors', '$background-colors'];
const colorsObject = createGlobalColorsObject(colorKeys);

expect(colorsObject).toStrictEqual({ action: '$action-colors', background: '$background-colors' });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('fileGenerator', () => {
{ fileName: '_other.scss', content: mockedExpectedResult },
{ fileName: '_radii.scss', content: emptyFile },
{ fileName: '_spacing.scss', content: emptyFile },
{ fileName: '_colors.scss', content: emptyFile },
]);
});
});
30 changes: 24 additions & 6 deletions exporters/variables-scss/src/generators/contentGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Token, TokenGroup, TokenType } from '@supernovaio/sdk-exporters';
import { generateCssFromTokens } from './cssGenerator';
import { generateCssObjectFromTokens } from './cssObjectGenerator';
import { CssObjectType, generateCssObjectFromTokens } from './cssObjectGenerator';
import { formatCSS } from '../formatters/cssFormatter';
import { convertToScss, deepMergeObjects } from '../helpers/cssObjectHelper';

// Add disclaimer to the top of the content
export const addDisclaimer = (content: string): string => {
Expand All @@ -12,6 +13,7 @@ export const filterTokensByTypeAndGroup = (tokens: Token[], type: TokenType, gro
return tokens.filter((token) => token.tokenType === type && token.origin?.name?.includes(group));
};

// TODO: refactor to use fileData instead of destructuring
export const generateFileContent = (
tokens: Token[],
mappedTokens: Map<string, Token>,
Expand All @@ -20,25 +22,41 @@ export const generateFileContent = (
groupNames: string[],
withCssObject: boolean,
hasParentPrefix: boolean,
sortByNumValue: boolean,
) => {
let cssTokens = '';
let cssObject = '';
let cssObject: CssObjectType = {};

// Iterate over token types and group names to filter tokens
tokenTypes.forEach((tokenType) => {
groupNames.forEach((group) => {
const filteredTokens = filterTokensByTypeAndGroup(tokens, tokenType, group);

// Generate css tokens
cssTokens += generateCssFromTokens(filteredTokens, mappedTokens, tokenGroups, hasParentPrefix);
cssTokens += generateCssFromTokens(
filteredTokens,
mappedTokens,
tokenGroups,
group,
hasParentPrefix,
sortByNumValue,
);
cssTokens += '\n\n';

// Generate css object
cssObject += generateCssObjectFromTokens(filteredTokens, mappedTokens, tokenGroups, hasParentPrefix);
// Generate css object and merge it with the existing one
const groupCssObject = generateCssObjectFromTokens(filteredTokens, mappedTokens, tokenGroups, hasParentPrefix);
cssObject = deepMergeObjects(cssObject, groupCssObject);
});
});

const content = withCssObject ? `${cssTokens}${cssObject}` : cssTokens;
let content = cssTokens;

// convert css object to scss structure
if (withCssObject) {
content += Object.entries(cssObject)
.map(([key, obj]) => `${key}: (\n${convertToScss(obj as CssObjectType)}\n) !default;\n\n`)
.join('');
}

return {
content: addDisclaimer(formatCSS(content)),
Expand Down
Loading

0 comments on commit d88e01d

Please sign in to comment.