Skip to content

Commit

Permalink
Feat(exporter-tokens): Add mixin css-variables to theme colors #DS-1542
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed Nov 7, 2024
1 parent a4d33bd commit 6b79489
Show file tree
Hide file tree
Showing 14 changed files with 286 additions and 103 deletions.
70 changes: 39 additions & 31 deletions exporters/tokens/generated/exporter.cjs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions exporters/tokens/src/config/fileConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type FileData = {
tokenTypes: TokenType[];
groupNames?: string[];
excludeGroupNames?: string[] | null;
hasMixin?: boolean;
hasStylesObject?: boolean;
hasParentPrefix?: boolean;
sortByNumValue?: boolean;
Expand Down Expand Up @@ -59,5 +60,9 @@ export const themedFilesData: FileData[] = [
{
fileName: 'colors',
tokenTypes: [TokenType.color],
hasMixin: true,
hasStylesObject: false,
},
];

export const commonThemedFilesData: FileData[] = [{ fileName: 'color-tokens', tokenTypes: [TokenType.color] }];
10 changes: 10 additions & 0 deletions exporters/tokens/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const COLOR_JS_SUFFIX = 'Colors';
export const COLOR_KEY = 'colors';
export const COLOR_SCSS_SUFFIX = '-colors';
export const GLOBAL_DIRECTORY = 'global-tokens';
export const JS_DIRECTORY = 'js';
export const JS_INDENTATION = ' ';
export const SCSS_DIRECTORY = 'scss';
export const SCSS_INDENTATION = ' ';
export const THEMES_DIRECTORY = 'themes';
export const TYPOGRAPHY_KEY = 'styles';
6 changes: 4 additions & 2 deletions exporters/tokens/src/formatters/stylesFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JS_INDENTATION, SCSS_INDENTATION } from '../constants';

export const removeExtraBlankLines = (css: string): string => {
return css.replace(/\n{3,}/g, '\n\n');
};
Expand All @@ -8,12 +10,12 @@ export const formatLinesAtEndOfTheFile = (css: string): string => {

const formattingConfig = {
js: {
indentation: ' ',
indentation: JS_INDENTATION,
openingBracket: '{',
closingBracket: '}',
},
scss: {
indentation: ' ',
indentation: SCSS_INDENTATION,
openingBracket: '(',
closingBracket: ')',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
@use 'sass:meta';
@use 'themes/theme-light';
@use 'themes/theme-light-inverted';

// The first theme is the default theme, as the left column in the Figma table.
$themes: (
theme-light: (
colors: theme-light.$colors,
variables: meta.module-variables(theme-light),
mixins: meta.module-mixins(theme-light),
),
theme-light-inverted: (
colors: theme-light-inverted.$colors,
variables: meta.module-variables(theme-light-inverted),
mixins: meta.module-mixins(theme-light-inverted),
),
);
70 changes: 43 additions & 27 deletions exporters/tokens/src/generators/__tests__/fileGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const tokenGroups: Array<TokenGroup> = exampleGroups;
const emptyFile = "/* This file was generated by Supernova, don't change manually */\n\n";
const barrelFile = fs.readFileSync(path.join(__dirname, '../__fixtures__/barrelFileMock.scss'), 'utf-8');
const barrelColorFile = "@forward 'colors';\n";
const barrelColorTokensFile = "@forward 'color-tokens';\n";
const barrelJsFile = `export * from './borders';
export * from './gradients';
export * from './other';
Expand Down Expand Up @@ -51,14 +52,26 @@ import * as themeLightInverted from './theme-light-inverted';\n
// The first theme is the default theme, as the left column in the Figma table.
export const themes = {
themeLight: {
colors: themeLight.colors,
tokens: themeLight,
},
themeLightInverted: {
colors: themeLightInverted.colors,
tokens: themeLightInverted,
},
};
`;

const mockedColorTokensFile = `/* This file was generated by Supernova, don't change manually */
\n\n@mixin css-variables {\n\n}\n`;

const mockedThemeRootFileContent =
'theme-light: (\n' +
'variables: meta.module-variables(theme-light),\n' +
'mixins: meta.module-mixins(theme-light),\n),\n' +
'theme-light-inverted: (\n' +
'variables: meta.module-variables(theme-light-inverted),\n' +
'mixins: meta.module-mixins(theme-light-inverted),\n' +
'),';

describe('fileGenerator', () => {
describe('generateOutputFilesByThemes', () => {
it('should generate output files by themes', async () => {
Expand All @@ -79,38 +92,44 @@ describe('fileGenerator', () => {

expect(outputFiles).toStrictEqual([
// Global files
{ path: './scss/global', fileName: '_borders.scss', content: emptyFile },
{ path: './scss/global', fileName: '_other.scss', content: mockedExpectedResult },
{ path: './scss/global', fileName: '_radii.scss', content: emptyFile },
{ path: './scss/global', fileName: '_spacing.scss', content: emptyFile },
{ path: './scss/global', fileName: '_shadows.scss', content: emptyFile },
{ path: './scss/global', fileName: '_gradients.scss', content: emptyFile },
{ path: './scss/global', fileName: '_typography.scss', content: emptyFile },
{ path: './js/global/', fileName: 'borders.ts', content: emptyFile },
{ path: './js/global/', fileName: 'other.ts', content: mockedTsFile },
{ path: './js/global/', fileName: 'radii.ts', content: emptyFile },
{ path: './js/global/', fileName: 'spacing.ts', content: emptyFile },
{ path: './js/global/', fileName: 'shadows.ts', content: emptyFile },
{ path: './js/global/', fileName: 'gradients.ts', content: emptyFile },
{ path: './js/global/', fileName: 'typography.ts', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_borders.scss', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_other.scss', content: mockedExpectedResult },
{ path: './scss/global-tokens', fileName: '_radii.scss', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_spacing.scss', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_shadows.scss', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_gradients.scss', content: emptyFile },
{ path: './scss/global-tokens', fileName: '_typography.scss', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'borders.ts', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'other.ts', content: mockedTsFile },
{ path: './js/global-tokens/', fileName: 'radii.ts', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'spacing.ts', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'shadows.ts', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'gradients.ts', content: emptyFile },
{ path: './js/global-tokens/', fileName: 'typography.ts', content: emptyFile },
// Global barrel files
{ path: './scss/global/', fileName: 'index.scss', content: barrelFile },
{ path: './js/global/', fileName: 'index.ts', content: barrelJsFile },
{ path: './scss/global-tokens/', fileName: 'index.scss', content: barrelFile },
{ path: './js/global-tokens/', fileName: 'index.ts', content: barrelJsFile },
// Root barrel files
{ path: './scss/', fileName: '@global.scss', content: "@forward 'global';\n" },
{ path: './js/', fileName: 'index.ts', content: "export * from './global';\nexport * from './themes';\n" },
{ path: './scss/', fileName: '@tokens.scss', content: "@forward 'global-tokens';\n@forward 'themes';\n" },
{
path: './js/',
fileName: 'index.ts',
content: "export * from './global-tokens';\nexport * from './themes';\n",
},
// Themes files
{ path: './scss/themes/theme-light/', fileName: '_colors.scss', content: emptyFile },
{ path: './scss/themes/theme-light/', fileName: '_colors.scss', content: mockedColorTokensFile },
{ path: './js/themes/theme-light/', fileName: 'colors.ts', content: emptyFile },
{ path: './scss/themes/theme-light/', fileName: 'index.scss', content: barrelColorFile },
{ path: './js/themes/theme-light/', fileName: 'index.ts', content: barrelJsColorFile },
{ path: './scss/themes/theme-light-inverted/', fileName: '_colors.scss', content: emptyFile },
{ path: './scss/themes/theme-light-inverted/', fileName: '_colors.scss', content: mockedColorTokensFile },
{ path: './js/themes/theme-light-inverted/', fileName: 'colors.ts', content: emptyFile },
{ path: './scss/themes/theme-light-inverted/', fileName: 'index.scss', content: barrelColorFile },
{ path: './js/themes/theme-light-inverted/', fileName: 'index.ts', content: barrelJsColorFile },
// Themes root barrel files
{ path: './scss/', fileName: '@themes.scss', content: mockedRootThemeFile },
{ path: './js/themes', fileName: 'index.ts', content: mockedRootThemeJsFile },
{ path: './scss/themes', fileName: 'index.scss', content: barrelColorTokensFile },
{ path: './scss/themes', fileName: '_color-tokens.scss', content: emptyFile },
]);
});
});
Expand Down Expand Up @@ -189,10 +208,7 @@ describe('fileGenerator', () => {
const themes = [{ name: 'theme-light' }, { name: 'theme-light-inverted' }];
const content = generateRootThemesFileContent(themes as TokenTheme[], false);

expect(content).toBe(
// eslint-disable-next-line prettier/prettier, quotes -- special characters in the string
'theme-light: (\ncolors: theme-light.$colors,\n),\ntheme-light-inverted: (\ncolors: theme-light-inverted.$colors,\n),',
);
expect(content).toBe(mockedThemeRootFileContent);
});

it('should generate root themes file content with js output', () => {
Expand All @@ -201,7 +217,7 @@ describe('fileGenerator', () => {

expect(content).toBe(
// eslint-disable-next-line prettier/prettier, quotes -- special characters in the string
'themeLight: {\ncolors: themeLight.colors,\n},\nthemeLightInverted: {\ncolors: themeLightInverted.colors,\n},',
'themeLight: {\ntokens: themeLight,\n},\nthemeLightInverted: {\ntokens: themeLightInverted,\n},',
);
});
});
Expand Down
51 changes: 51 additions & 0 deletions exporters/tokens/src/generators/__tests__/mixinGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Token, TokenGroup } from '@supernovaio/sdk-exporters';
import { exampleGroups } from '../../../tests/fixtures/exampleGroups';
import { examplePrefixToken } from '../../../tests/fixtures/examplePrefixToken';
import { findTokenPrefix } from '../../helpers/findTokenPrefix';
import { generateMixinFromTokens } from '../mixinGenerator';
import { exampleColorsTokens } from '../../../tests/fixtures/exampleColorTokens';
import { SCSS_INDENTATION } from '../../constants';

const mappedTokens: Map<string, Token> = new Map([]);
const tokenGroups: Array<TokenGroup> = exampleGroups;

describe('mixinGenerator', () => {
const dataProvider = [
{
tokens: exampleColorsTokens,
groupName: '',
hasParentPrefix: false,
hasTokenPrefix: true,
description: 'should generate mixin from tokens',
expectedStyles: `${SCSS_INDENTATION}--spirit-color-active: #{$active};\n${SCSS_INDENTATION}--spirit-color-primary: #{$primary};`,
},
{
tokens: exampleColorsTokens,
groupName: '',
hasParentPrefix: false,
hasTokenPrefix: false,
description: 'should generate mixin with parent prefix and no token prefix',
expectedStyles: `${SCSS_INDENTATION}--color-active: #{$active};\n${SCSS_INDENTATION}--color-primary: #{$primary};`,
},
];

it.each(dataProvider)(
'should correctly generate mixin for $description',
({ tokens, expectedStyles, groupName, hasParentPrefix, hasTokenPrefix }) => {
const prefixTokens = Array.from(examplePrefixToken.values());
const tokenPrefix = hasTokenPrefix ? findTokenPrefix(prefixTokens) : '';

const styles = generateMixinFromTokens(
Array.from(tokens.values()),
mappedTokens,
tokenGroups,
tokenPrefix,
groupName,
hasParentPrefix,
false,
);

expect(styles).toBe(`@mixin css-variables {\n${expectedStyles}\n}\n`);
},
);
});
33 changes: 30 additions & 3 deletions exporters/tokens/src/generators/__tests__/stylesGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,15 @@ describe('stylesGenerator', () => {
({ token, expectedStyles, hasParentPrefix, hasTokenPrefix, hasJsOutput }) => {
const prefixTokens = Array.from(examplePrefixToken.values());
const tokenPrefix = hasTokenPrefix ? findTokenPrefix(prefixTokens) : '';
const styles = tokenToStyleByType(token, mappedTokens, tokenGroups, tokenPrefix, hasParentPrefix, hasJsOutput);
const styles = tokenToStyleByType(
token,
mappedTokens,
tokenGroups,
tokenPrefix,
false,
hasParentPrefix,
hasJsOutput,
);

expect(styles).toBe(expectedStyles);
},
Expand All @@ -152,6 +160,7 @@ describe('stylesGenerator', () => {
tokens: exampleDimensionAndStringTokens,
groupName: 'Grid',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: true,
hasTokenPrefix: false,
description: 'should generate styles from tokens',
Expand All @@ -161,6 +170,7 @@ describe('stylesGenerator', () => {
tokens: exampleDimensionAndStringTokens,
groupName: 'Grid',
hasJsOutput: true,
hasMixin: false,
hasParentPrefix: true,
hasTokenPrefix: false,
description: 'should generate styles from tokens with js output',
Expand All @@ -170,15 +180,27 @@ describe('stylesGenerator', () => {
tokens: exampleColorsTokens,
groupName: '',
hasJsOutput: false,
hasMixin: true,
hasParentPrefix: false,
hasTokenPrefix: false,
description: 'should generate styles from tokens with colors',
description: 'should generate styles from tokens with colors and mixin',
expectedStyles: '$active: #ca2026 !default;\n\n$primary: #fff !default;',
},
{
tokens: exampleColorsTokens,
groupName: '',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: false,
hasTokenPrefix: true,
description: 'should generate styles from tokens with colors and without mixin',
expectedStyles: '$active: var(--spirit-color-active);\n\n$primary: var(--spirit-color-primary);',
},
{
tokens: exampleColorsTokens,
groupName: '',
hasJsOutput: true,
hasMixin: true,
hasParentPrefix: false,
hasTokenPrefix: false,
description: 'should generate styles from tokens with colors with js output',
Expand All @@ -188,6 +210,7 @@ describe('stylesGenerator', () => {
tokens: exampleShadowTokens,
groupName: '',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: false,
hasTokenPrefix: true,
description: 'should generate styles from tokens with shadows',
Expand All @@ -197,6 +220,7 @@ describe('stylesGenerator', () => {
tokens: exampleShadowTokens,
groupName: '',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: false,
hasTokenPrefix: false,
description: 'should generate styles from tokens with shadows',
Expand All @@ -206,6 +230,7 @@ describe('stylesGenerator', () => {
tokens: exampleGradientTokens,
groupName: '',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: false,
hasTokenPrefix: true,
description: 'should generate styles from tokens with gradients',
Expand All @@ -216,6 +241,7 @@ describe('stylesGenerator', () => {
tokens: exampleGradientTokens,
groupName: '',
hasJsOutput: false,
hasMixin: false,
hasParentPrefix: false,
hasTokenPrefix: false,
description: 'should generate styles from tokens with gradients',
Expand All @@ -226,7 +252,7 @@ describe('stylesGenerator', () => {

it.each(dataProvider)(
'$description',
({ tokens, groupName, hasJsOutput, hasParentPrefix, hasTokenPrefix, expectedStyles }) => {
({ tokens, groupName, hasJsOutput, hasMixin, hasParentPrefix, hasTokenPrefix, expectedStyles }) => {
const prefixTokens = Array.from(examplePrefixToken.values());
const tokenPrefix = hasTokenPrefix ? findTokenPrefix(prefixTokens) : '';
const styles = generateStylesFromTokens(
Expand All @@ -235,6 +261,7 @@ describe('stylesGenerator', () => {
tokenGroups,
tokenPrefix,
groupName,
hasMixin,
hasParentPrefix,
false,
hasJsOutput,
Expand Down
Loading

0 comments on commit 6b79489

Please sign in to comment.