Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter out all design variables and tokens not in use #215

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/light-pandas-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@propeldata/ui-kit': minor
---

Filter out all design variables and tokens not in use
25 changes: 5 additions & 20 deletions app/storybook/documentation/ui-kit/Theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import LinkTo from '@storybook/addon-links/react'
import { prettier } from '../../src/utils'
import { SourceCode } from '../../.storybook/blocks/SourceCode'
import * as ThemeProviderStories from '../../../../packages/ui-kit/src/components/ThemeProvider/ThemeProvider.stories'
import rawThemesScss from '!!raw-loader!../../../../packages/ui-kit/src/themes/themes.module.scss'
import rawCommonScss from '!!raw-loader!../../../../packages/ui-kit/src/themes/generated/_tokens.scss'
import rawLightThemeScss from '!!raw-loader!../../../../packages/ui-kit/src/themes/generated/_lightTheme.scss'
import rawDarkThemeScss from '!!raw-loader!../../../../packages/ui-kit/src/themes/generated/_darkTheme.scss'
import rawThemesScss from '!!raw-loader!../../../../packages/ui-kit/src/generated/themes/themes.module.css'

<Meta title="Customization/Theming" />

Expand Down Expand Up @@ -41,25 +38,13 @@ Further details are available in the <LinkTo kind="API/ThemeTokenProps" story="o

### Default values

The content in `themes.module.scss` serves as the foundational entry point for theming within the UI Kit.
The content in `themes.module.scss` defines design tokens for a UI Kit, supporting both light and dark themes. It
includes typography styles for various text sizes and weights, spacing units, and border radii. A comprehensive colour
palette covers text, borders, and backgrounds for both themes. Deprecated variables are also listed for phasing out
older styles.

<Source language="css" format="dedent" dark code={rawThemesScss} />

The `common.scss` file contains the **common** theme configuration, defining a series of CSS custom properties that are
shared by both the light and dark themes.

<Source language="css" format="dedent" dark code={rawCommonScss} />

The `lightTheme.scss` file sets up the **light theme** for the UI Kit, defining a series of CSS custom properties
specific to this theme.

<Source language="css" format="dedent" dark code={rawLightThemeScss} />

The `darkTheme.scss` file sets up the **dark theme** for the UI Kit, defining a series of CSS custom properties specific
to this theme.

<Source language="css" format="dedent" dark code={rawDarkThemeScss} />

### Customizing the Theme

The `ThemeProvider` component allows theme customization through its `theme` prop.
Expand Down
4 changes: 2 additions & 2 deletions packages/ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"test:coverage": "jest --ci --coverage --json --outputFile=coverage/coverage.json",
"design-tokens:sync": "yarn design-tokens:clean && tsc -p ./scripts/tsconfig.json && yarn design-tokens:parse && yarn design-tokens:validate",
"design-tokens:clean": "rm -rf ./scripts/dist && rm -rf ./src/generated && rm -rf ./src/themes/generated",
"design-tokens:parse": "node ./scripts/dist/parse-design-tokens.mjs",
"design-tokens:validate": "node ./scripts/dist/validate-design-tokens.mjs"
"design-tokens:parse": "node ./scripts/dist/design-tokens/parse.mjs",
"design-tokens:validate": "node ./scripts/dist/design-tokens/validate.mjs"
},
"publishConfig": {
"access": "public",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import fs, { promises as fsPromises } from 'fs'
import path from 'path'
import slugify from 'slugify'
import ora from 'ora'
import path from 'path'
import { buildSASSFiles, camelCaseToKebabCase, getCSSVariables, kebabCaseToCamelCase, slugifyStr } from './shared.mjs'

const env = process.env.NODE_ENV || 'development'

const GENERATED_WARNING =
'This file is generated automatically by scripts/parse-design-tokens.js. Do not edit manually.'
Expand Down Expand Up @@ -60,20 +62,6 @@ type TypographyClassProps = {
props: { prop: string; value: string }[]
}

// Slugify string with strict mode
export const slugifyStr = (str: string): string => slugify(str.replaceAll('/', '-'), { lower: true, strict: true })

// Convert kebab-case to camelCase
export const kebabCaseToCamelCase = (kebabStr: string): string =>
kebabStr
.split('-')
.map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
.join('')

// Convert camelCase to kebab-case
export const camelCaseToKebabCase = (camelStr: string): string =>
camelStr.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()

// Parse Figma variable value
export const parseValue = (variable: VariableProps): string => {
const { type, value } = variable
Expand Down Expand Up @@ -156,8 +144,13 @@ export const getThemeTokens = ({
)
)

const main = async () => {
const spinner = ora({ text: 'Parsing design variables and tokens...', color: 'yellow' }).start()
const main = async (isProdEnv = false) => {
let succeedMessage = 'Parse design variables and tokens'

const spinner = ora({
text: `Parsing design variables and tokens...`,
color: 'yellow'
}).start()

try {
const variablesJSON = await getJSONFromFile()
Expand All @@ -167,8 +160,8 @@ const main = async () => {
throw new Error('Failed to parse variables.json')
}

const variables: TokenDataProps[] = []
const tokens: TokenDataProps[] = []
let variables: TokenDataProps[] = []
let tokens: TokenDataProps[] = []

// Parse variables
variablesJSON.collections
Expand Down Expand Up @@ -240,15 +233,32 @@ const main = async () => {
const themes = variablesJSON.collections.find(({ name }) => name === '1. Color Modes')?.modes

// Parse theme tokens
const lightTheme = getThemeTokens({ name: 'light', themes, variables, tokens })
const darkTheme = getThemeTokens({ name: 'dark', themes, variables, tokens })
let lightTheme = getThemeTokens({ name: 'light', themes, variables, tokens })
let darkTheme = getThemeTokens({ name: 'dark', themes, variables, tokens })

if (isProdEnv) {
await buildSASSFiles(spinner)

const currentVariablesList = getCSSVariables('./src', 'current')
const statsBefore = {
variables: variables.length,
tokens: tokens.length
}

variables = variables.filter((variable) => currentVariablesList.includes(variable.cssName))
tokens = tokens.filter((token) => currentVariablesList.includes(token.cssName))
lightTheme = lightTheme?.filter((token) => currentVariablesList.includes(token.cssName))
darkTheme = darkTheme?.filter((token) => currentVariablesList.includes(token.cssName))

succeedMessage = `Filter out all design variables (${variables.length} from ${statsBefore.variables}) and tokens (${tokens.length} from ${statsBefore.tokens}) not in use`
}

// Generate _variables.scss
writeToFileSync(
'_variables.scss',
[
`// ${GENERATED_WARNING}\n`,
'.variables {',
'%variables {',
variables.map(({ cssName, value }) => ` ${cssName}: ${value};`).join('\n'),
'}'
].join('\n')
Expand All @@ -260,15 +270,14 @@ const main = async () => {
[
`// ${GENERATED_WARNING}\n`,
"@use './variables';\n",
'.tokens {',
' @extend .variables;\n',
'%tokens {',
' @extend %variables;\n',
tokens.map(({ cssName, value }) => ` ${cssName}: ${value};`).join('\n'),
'}\n',
typographyClasses
.map((typographyClass) =>
[
`.${typographyClass.className} {`,
' @extend .variables;\n',
`%${typographyClass.className} {`,
typographyClass.props.map(({ prop, value }) => ` ${prop}: ${value};`).join('\n'),
'}\n'
].join('\n')
Expand All @@ -287,9 +296,7 @@ const main = async () => {
`_${name}.scss`,
[
`// ${GENERATED_WARNING}\n`,
"@use './tokens';\n",
`.${name} {`,
' @extend .tokens;\n',
`%${name} {`,
theme?.map(({ cssName, value }) => ` ${cssName}: ${value};`).join('\n'),
'}'
].join('\n')
Expand Down Expand Up @@ -338,11 +345,15 @@ const main = async () => {
].join('\n')
)

spinner.succeed('Parse design variables and tokens')
spinner.succeed(succeedMessage)
} catch (err) {
console.error(err)
spinner.fail('Failed to parse design variables and tokens')
}
}

main()

if (env === 'production') {
main(true)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { exec } from 'child_process'
import fs from 'fs'
import { Ora } from 'ora'
import path from 'path'
import ora, { Ora } from 'ora'
import { exec } from 'child_process'
import slugify from 'slugify'
import { promisify } from 'util'

const FAILED_MESSAGE = 'Design tokens validation failed'
// Slugify string with strict mode
export const slugifyStr = (str: string): string => slugify(str.replaceAll('/', '-'), { lower: true, strict: true })

// Convert kebab-case to camelCase
export const kebabCaseToCamelCase = (kebabStr: string): string =>
kebabStr
.split('-')
.map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
.join('')

// Convert camelCase to kebab-case
export const camelCaseToKebabCase = (camelStr: string): string =>
camelStr.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()

const execAsync = promisify(exec)

Expand Down Expand Up @@ -68,39 +81,3 @@ export const buildSASSFiles = async (spinner: Ora): Promise<void> => {
throw error
}
}

// Compare current and new CSS variables, and log any missing variables
export const validateDesignTokens = (spinner: Ora): void => {
spinner.start('Validating design tokens...')
let validationFailed = false

const currentVariablesList = getCSSVariables('./src', 'current')
const newVariablesList = getCSSVariables('./src/themes', 'new')

currentVariablesList.forEach((variable) => {
if (!newVariablesList.includes(variable)) {
spinner.warn(`Design token '${variable}' is not defined`)
validationFailed = true
}
})

if (validationFailed) {
throw new Error(FAILED_MESSAGE)
}

spinner.succeed('Validate design tokens')
}

async function main() {
const spinner = ora({ text: 'Building SASS files...', color: 'yellow' }).start()

try {
await buildSASSFiles(spinner)
validateDesignTokens(spinner)
} catch (error) {
spinner.fail(FAILED_MESSAGE)
console.error(error)
}
}

main()
40 changes: 40 additions & 0 deletions packages/ui-kit/scripts/design-tokens/validate.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import ora, { Ora } from 'ora'
import { getCSSVariables, buildSASSFiles } from './shared.mjs'

const FAILED_MESSAGE = 'Design tokens validation failed'

// Compare current and new CSS variables, and log any missing variables
export const validateDesignTokens = (spinner: Ora): void => {
spinner.start('Validating design tokens...')
let validationFailed = false

const currentVariablesList = getCSSVariables('./src', 'current')
const newVariablesList = getCSSVariables('./src/themes', 'new')

currentVariablesList.forEach((variable) => {
if (!newVariablesList.includes(variable)) {
spinner.warn(`Design token '${variable}' is not defined`)
validationFailed = true
}
})

if (validationFailed) {
throw new Error(FAILED_MESSAGE)
}

spinner.succeed('Validate design tokens')
}

async function main() {
const spinner = ora({ text: 'Building SASS files...', color: 'yellow' }).start()

try {
await buildSASSFiles(spinner)
validateDesignTokens(spinner)
} catch (error) {
spinner.fail(FAILED_MESSAGE)
console.error(error)
}
}

main()
1 change: 1 addition & 0 deletions packages/ui-kit/scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"target": "ESNext",
"strict": true,
"outDir": "dist",
"rootDir": ".",
"lib": ["ESNext", "DOM"]
},
"include": ["./**/*.mts"],
Expand Down
4 changes: 2 additions & 2 deletions packages/ui-kit/src/components/Button/Button.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use '../../themes/generated/tokens';

.rootButton {
@extend .textMdRegular;
@extend %textMdRegular;

padding: var(--propel-spacing-lg) var(--propel-spacing-4xl);
gap: var(--propel-spacing-lg);
Expand Down Expand Up @@ -71,7 +71,7 @@
}

&.small {
@extend .textSmSemibold;
@extend %textSmSemibold;

padding: var(--propel-spacing-md) var(--propel-spacing-4xl);

Expand Down
6 changes: 3 additions & 3 deletions packages/ui-kit/src/components/Counter/Counter.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use '../../themes/common';
@use '../../themes/generated/tokens';

.rootCounter {
@extend .displayLgBold;
@extend %displayLgBold;
color: var(--propel-text-primary);
white-space: nowrap;
transition: opacity 0.2s ease-in-out;
Expand All @@ -13,6 +13,6 @@
}

.loader {
@extend .displayLgBold;
@extend %displayLgBold;
color: var(--propel-text-secondary);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
@use '../../themes/common';
// @use '../../themes/common';
@use '../../themes/generated/tokens';

.rootErrorFallback {
@extend .textMdRegular;
@extend %textMdRegular;
color: var(--propel-text-secondary);
max-width: 263px;
height: var(--propel-component-height);
Expand Down
8 changes: 4 additions & 4 deletions packages/ui-kit/src/components/Input/Input.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use '../../themes/generated/tokens';

.rootInput {
@extend .textMdRegular;
@extend %textMdRegular;

padding: var(--propel-spacing-lg) var(--propel-spacing-xl);
gap: var(--propel-spacing-lg);
Expand All @@ -21,7 +21,7 @@
transition: border-color var(--propel-transition-ease-time) ease, box-shadow var(--propel-transition-ease-time) ease;

& input {
@extend .textMdRegular;
@extend %textMdRegular;

border: none;
padding: 0;
Expand Down Expand Up @@ -67,12 +67,12 @@
}

.small {
@extend .textSmRegular;
@extend %textSmRegular;

padding: var(--propel-spacing-md) var(--propel-spacing-lg);
gap: var(--propel-spacing-md);

& input {
@extend .textSmRegular;
@extend %textSmRegular;
}
}
Loading
Loading