Skip to content

Commit

Permalink
Feature/dark mode (#54)
Browse files Browse the repository at this point in the history
* Move the generate style variables to StyleDictionary

* Update README

* Handle no style dictionary config

* Changeset

* Improve colors json struct

* Fix tests

* Up changeset to major

* Add support for CSS dark mode

* Update Readme and template

* Refactor

* Changeset
  • Loading branch information
Imar Abreu authored Dec 20, 2023
1 parent 646a330 commit 1b7ed3b
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 64 deletions.
8 changes: 8 additions & 0 deletions .changeset/honest-bats-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@runroom/design-tokens': major
---

- Move the `StyleDictionary` config to a `styleDictionary` entry in the design tokens config
- Add support for dark mode in the CSS variables with the `darkMode` and the `darkModeStyleDictionary` entries in the
design tokens config
- Remove `figmaThemes`
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ array of the figma frames that contains the tokens

`outputDir`: The source where will be stored the package's output

`figmaThemes`: An array of your's figma project themes. If you aren't usign themes, just remove the key (first element of the
array, will be setted as default)
`darkMode`: Boolean to enable/disable the dark mode support


### Optional config fields for [Style Dictionary](https://amzn.github.io/style-dictionary/#/config)

`styleDictionary`: The style dictionary config

`darkModeStyleDictionary`: The style dictionary config for dark mode files **only works with CSS variables**

### Execution

Then execute:
Expand Down Expand Up @@ -77,6 +81,9 @@ Expanding the design tokens in your project is a straightforward process. Here's
- In the `src/types/figma` directory, define types that reflect the structure of the new tokens in Figma.
- In the `src/types/designTokens` directory, define types specific to how the tokens will be used.

6. **Add Token to Style Dictionary:**
- If the token needs a specific parser, add it to the `src/styleDictionary/styleDictionary.ts` file.

## License

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Expand Down
4 changes: 2 additions & 2 deletions src/api/figmaApiConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const figmaApiConnection = async ({
figmaApiKey,
figmaProjectId,
figmaPages,
figmaThemes
darkMode
}: Config): Promise<DesignTokensGenerator[]> => {
log('Connecting with Figma...', EMOJIS.workingInProgress);

Expand All @@ -46,7 +46,7 @@ const figmaApiConnection = async ({
throw new Error(`No styles found`);
}

const parsedTokens = parseFigma(responseJson, figmaPages, figmaThemes);
const parsedTokens = parseFigma(responseJson, figmaPages, darkMode);

if (!parsedTokens || !parsedTokens.length) {
throw new Error(`No styles found`);
Expand Down
4 changes: 2 additions & 2 deletions src/api/parseFigma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FigmaResponse } from '@/types/figma';
import { generateDesignTokens } from '@/functions/getTokens.ts';
import { DesignTokensGenerator, FigmaPages } from '@/types/designTokens';

const parseFigma = (response: FigmaResponse, FIGMA_PAGES: FigmaPages, themes?: string[]) => {
const parseFigma = (response: FigmaResponse, FIGMA_PAGES: FigmaPages, darkMode?: boolean) => {
if (!response) {
throw new Error(`\x1b[31m\n\n${EMOJIS.error} No styles found\n`);
}
Expand All @@ -30,7 +30,7 @@ const parseFigma = (response: FigmaResponse, FIGMA_PAGES: FigmaPages, themes?: s
}

const figmaDesignTokensFrames = figmaPage.children;
const generateTokens = generateDesignTokens(frames, figmaDesignTokensFrames, themes);
const generateTokens = generateDesignTokens(frames, figmaDesignTokensFrames, darkMode);
tokens.push(...generateTokens);
}

Expand Down
4 changes: 2 additions & 2 deletions src/bin/designTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import parserRuntime from 'yargs-parser';
import designTokens from '@/index.ts';
import { logError, configFileParser } from '@/functions';
import { configFileParser, logError } from '@/functions';
import fs from 'fs';

const args = parserRuntime(process.argv.slice(2));

configFileParser(args, fs)
.then(config => designTokens(args, config))
.then(config => designTokens(config))
.catch(err => logError(err));
10 changes: 5 additions & 5 deletions src/functions/getTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,20 @@ const designTokensBuilder = <T>(
name: DesignPages,
pages: string[],
frame: FigmaFrame,
designToken: ({ frame, themes }: TokenPayload) => T,
themes?: string[]
designToken: ({ frame, darkMode }: TokenPayload) => T,
darkMode?: boolean
): T | undefined => {
if (!pages.includes(name)) {
return;
}

return designToken({ frame, themes });
return designToken({ frame, darkMode });
};

const generateDesignTokens = (
pages: string[],
figmaDesignTokensFrames: FigmaFrame[],
themes?: string[]
darkMode?: boolean
) => {
const writeFilePromises: DesignTokensGenerator[] = figmaDesignTokensFrames
.map(frame => {
Expand All @@ -120,7 +120,7 @@ const generateDesignTokens = (
pages,
frame,
tokenGenerator,
themes
darkMode
);
})
.filter(truthy);
Expand Down
2 changes: 1 addition & 1 deletion src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export * from './fileIO.ts';
export * from './getTokens.ts';
export * from './logger.ts';
export * from './stringManipulation.ts';
export * from '../styleDictionary/StyleDictionary.ts';
export * from '../styleDictionary/styleDictionary.ts';
export * from './unitsConvert.ts';
export * from './ensureType.ts';
13 changes: 4 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { buildStyleDictionary } from '@/styleDictionary/StyleDictionary.ts';
import { Arguments } from 'yargs-parser';
import { buildStyleDictionary } from '@/styleDictionary/styleDictionary.ts';
import { ParseConfig } from '@/types/designTokens';
import { figmaApiConnection } from '@/api';
import { createJsonTokenFiles, EMOJIS, log } from '@/functions';

const designTokens = (args: Arguments, config: ParseConfig) => {
const { settings, configFile } = config;
const designTokens = (config: ParseConfig) => {
const { settings } = config;

figmaApiConnection(settings).then(async generatedTokens => {
log('Generating design tokens...', EMOJIS.workingInProgress);

await createJsonTokenFiles(generatedTokens, settings);

if (settings.platforms) {
buildStyleDictionary(configFile);
} else {
log('No Style Dictionary config found', EMOJIS.warning);
}
buildStyleDictionary(settings);
});
};

Expand Down
21 changes: 0 additions & 21 deletions src/styleDictionary/StyleDictionary.ts

This file was deleted.

110 changes: 110 additions & 0 deletions src/styleDictionary/styleDictionary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import StyleDictionary, { Config as StyleDictionaryConfig } from 'style-dictionary';
import { EMOJIS, log } from '@/functions';
import { getGradientsParser, getShadowsParser, getTypographiesParser } from '@/tokens';
import { Config } from '@/types/designTokens';

const registerParsers = () => {
StyleDictionary.registerParser(getTypographiesParser());
StyleDictionary.registerParser(getShadowsParser());
StyleDictionary.registerParser(getGradientsParser());
};

const getDarkModeSource = (source?: string[], outputDir?: string) => {
const defaultJsonPath = `${outputDir}/**/*.dark.json`;

if (!source) {
return [defaultJsonPath];
}

return source.map(path => {
if (path.indexOf(`.dark.json`) > -1) {
return path;
}

return `${path.replace(`.json`, `.dark.json`)}`;
});
};

const getDarkModeStyleDictionaryDefaultConfig = (settings: Config) => {
const source = getDarkModeSource(settings.styleDictionary?.source, settings.outputDir);
const buildPath =
settings.styleDictionary!.platforms.css.buildPath ?? `${settings.outputDir}/tokens/`;

return {
include: source,
source,
filter: {
dark: ({ filePath }: { filePath: string }) => filePath.indexOf(`.dark`) > -1
},
platforms: {
css: {
transformGroup: `css`,
buildPath,
files: [
{
destination: `variables-dark.css`,
format: `css/variables`
}
]
}
}
};
};

const buildDarkModeStyles = (settings: Config) => {
const darkModeConfig =
settings.darkModeStyleDictionary ?? getDarkModeStyleDictionaryDefaultConfig(settings);

const extendedDictionary = StyleDictionary.extend(darkModeConfig);

log('Compiling dark mode styles...', EMOJIS.workingInProgress);
extendedDictionary.buildAllPlatforms();
log('Dark mode styles compiled', EMOJIS.success);
};

const buildStyles = (styleDictionary: StyleDictionaryConfig) => {
const extendedDictionary = StyleDictionary.extend(styleDictionary);

log('Compiling styles...', EMOJIS.workingInProgress);
extendedDictionary.buildAllPlatforms();
log('Styles compiled', EMOJIS.success);
};

const getStyleDictionaryWithoutDarkFiles = (
styleDictionary: StyleDictionaryConfig
): StyleDictionaryConfig => {
const source = styleDictionary.source
? styleDictionary.source.map(sourcePath => sourcePath.replace('*.json', '!(*.dark).json'))
: [];

return {
...styleDictionary,
source,
filter: {
...styleDictionary.filter,
dark: ({ filePath }: { filePath: string }) => filePath.indexOf(`.dark`) === -1
}
};
};

const buildStyleDictionary = (settings: Config) => {
if (!settings.styleDictionary) {
log('No Style Dictionary config found', EMOJIS.warning);
return;
}

registerParsers();

const { darkMode, styleDictionary } = settings;

if (darkMode) {
const styleDictionaryWithoutDarkFiles = getStyleDictionaryWithoutDarkFiles(styleDictionary);
buildStyles(styleDictionaryWithoutDarkFiles);
buildDarkModeStyles(settings);
return;
}

buildStyles(styleDictionary);
};

export { buildStyleDictionary };
36 changes: 33 additions & 3 deletions src/tokens/Colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,43 @@ const getColors = (component: FigmaColorComponent): ColorToken | false => {
return buildColorToken(component, tokenValue);
};

const buildColorTokensWithThemes = (tokens: ColorCollection) => {
const tokensWithThemes: { [key: string]: ColorCollection } = {};

tokensWithThemes['dark'] = {
colors: tokens.colors['dark'] as any
} as ColorCollection;

delete tokens.colors['dark'];

tokensWithThemes.default = tokens;

return tokensWithThemes;
};

const getExt = (theme: string) => {
if (theme === 'default') {
return 'json';
}

return `${theme}.json`;
};

const writeColorTokens =
(tokens: ColorCollection) =>
(tokens: ColorCollection, darkMode?: boolean) =>
(createFile: CreateFile, outputDir: string, name = 'colors') => {
if (darkMode) {
const tokensWithThemes = buildColorTokensWithThemes(tokens);

return Object.keys(tokensWithThemes).map(theme =>
createFile(name, tokensWithThemes[theme], outputDir, getExt(theme))
);
}

return [createFile(name, tokens, outputDir, 'json')];
};

const Colors = ({ frame }: TokenPayload): DesignTokensGenerator => {
const Colors = ({ frame, darkMode }: TokenPayload): DesignTokensGenerator => {
const tokens = getTokens<FigmaColorComponent, ColorCollection, ColorToken>(
'Colors',
frame,
Expand All @@ -60,7 +90,7 @@ const Colors = ({ frame }: TokenPayload): DesignTokensGenerator => {
return {
name: 'Colors',
tokens,
writeTokens: writeColorTokens(tokens)
writeTokens: writeColorTokens(tokens, darkMode)
};
};

Expand Down
6 changes: 4 additions & 2 deletions src/types/designTokens/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ export type Config = {
figmaProjectId: string;
figmaPages: FigmaPages;
outputDir: string;
figmaThemes?: string[];
} & StyleDictionaryConfig;
darkMode?: boolean;
darkModeStyleDictionary?: StyleDictionaryConfig;
styleDictionary?: StyleDictionaryConfig;
};

export type ParseConfig = {
settings: Config;
Expand Down
2 changes: 1 addition & 1 deletion src/types/designTokens/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export interface TokenCollection {

export type TokenPayload = {
frame: FigmaFrame;
themes?: string[];
darkMode?: boolean;
};
Loading

3 comments on commit 1b7ed3b

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.❔
Category Percentage Covered / Total
🟑 Statements 73.44% 481/655
🟑 Branches 60.27% 135/224
🟑 Functions 73.8% 138/187
🟑 Lines 72.62% 443/610

Test suite run success

82 tests passing in 4 suites.

Report generated by πŸ§ͺjest coverage report action from 1b7ed3b

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.❔
Category Percentage Covered / Total
🟑 Statements 73.44% 481/655
🟑 Branches 60.27% 135/224
🟑 Functions 73.8% 138/187
🟑 Lines 72.62% 443/610

Test suite run success

82 tests passing in 4 suites.

Report generated by πŸ§ͺjest coverage report action from 1b7ed3b

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.❔
Category Percentage Covered / Total
🟑 Statements 73.44% 481/655
🟑 Branches 60.27% 135/224
🟑 Functions 73.8% 138/187
🟑 Lines 72.62% 443/610

Test suite run success

82 tests passing in 4 suites.

Report generated by πŸ§ͺjest coverage report action from 1b7ed3b

Please sign in to comment.