-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement exporting to tailwind theme
- Loading branch information
Showing
18 changed files
with
778 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
lib | ||
theme | ||
tokens.* | ||
tailwind.figma2theme.js | ||
|
||
# Storybook output | ||
storybook-static | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import path from 'path'; | ||
|
||
import { version } from '../../package.json'; | ||
import { renderTemplate } from '../utils/file'; | ||
import { convertShadowsDesignTokenToCss } from '../utils/convertDesignTokenToCss'; | ||
import type { Dictionary, NestedDictionary, Tokens } from '../utils/types'; | ||
|
||
const templateDir = path.resolve(__dirname, '../../templates/tailwind'); | ||
|
||
// Convert a design token dictionary into a simpler format that can be used in the Tailwind config | ||
// e.g. convert `{ "sm": { "$type": "dimension", "$value": "30em" } }` to `{ "sm": "30em" }` | ||
const processTokens = <Type, Token>( | ||
dictionary: NestedDictionary<{ $type: Type; $value: Token }>, | ||
transformKey: (key: string) => string = (key) => key, | ||
transformValue: (value: Token) => unknown = (value) => value | ||
): Dictionary<Token> => | ||
Object.entries(dictionary).reduce((obj, [key, dictionaryOrToken]) => { | ||
return { | ||
...obj, | ||
[transformKey(key)]: dictionaryOrToken.$value | ||
? transformValue(dictionaryOrToken.$value as Token) | ||
: processTokens( | ||
dictionaryOrToken as typeof dictionary, | ||
transformKey, | ||
transformValue | ||
), | ||
}; | ||
}, {}); | ||
|
||
// Convert the text styles dictionary into a format that can be used in a Tailwind plugin | ||
// e.g. convert `{ "fontSize": { "sm": "1rem", "xl": "1.125rem" }` to `{ "fontSize: "1rem", '@media (min-width: 80em)': { fontSize: "1.125rem" } }` | ||
const processTextStyles = ( | ||
textStyles: Tokens['textStyles'], | ||
breakpoints: Tokens['breakpoints'] | ||
) => { | ||
// Get the breakpoints in order (e.g. `["sm", "md", "lg", "xl"]`) | ||
const breakpointsInOrder = Object.entries(breakpoints) | ||
.sort(([, a], [, b]) => parseInt(a.$value) - parseInt(b.$value)) | ||
.map(([name]) => name); | ||
|
||
// Get the base value for a given token (e.g. `{ "sm": "1rem", "xl": "1.125rem" }` will return `"1rem"`) | ||
const getBaseValue = (value: string | Dictionary<string>) => { | ||
if (!value) return undefined; | ||
if (typeof value === 'string') return value; | ||
if (value.base) return value.base; | ||
|
||
const smallestBreakpoint = breakpointsInOrder.find((name) => value[name]); | ||
if (!smallestBreakpoint) return undefined; | ||
|
||
return value[smallestBreakpoint]; | ||
}; | ||
|
||
// Get the value for a given breakpoint, return undefined if it doesn't exist | ||
const getBreakpointValue = ( | ||
value: string | Dictionary<string>, | ||
breakpoint: string | ||
) => { | ||
if (!value) return undefined; | ||
if (typeof value === 'string') return undefined; | ||
if (value[breakpoint]) return value[breakpoint]; | ||
|
||
return undefined; | ||
}; | ||
|
||
return Object.entries(processTokens(textStyles)).reduce( | ||
(obj, [name, style]) => { | ||
const className = `.typography-${name}`; | ||
const baseStyle = { | ||
fontFamily: getBaseValue(style.fontFamily), | ||
fontSize: getBaseValue(style.fontSize), | ||
fontStyle: getBaseValue(style.fontStyle), | ||
fontWeight: getBaseValue(style.fontWeight), | ||
letterSpacing: getBaseValue(style.letterSpacing), | ||
lineHeight: getBaseValue(style.lineHeight), | ||
textDecorationLine: getBaseValue(style.textDecorationLine), | ||
textTransform: getBaseValue(style.textTransform), | ||
}; | ||
|
||
// Iterate over each breakpoint and create a media query if the value is different from the base | ||
const mediaQueries = breakpointsInOrder.reduce((acc, breakpoint) => { | ||
const breakpointStyle = { | ||
fontFamily: getBreakpointValue(style.fontFamily, breakpoint), | ||
fontSize: getBreakpointValue(style.fontSize, breakpoint), | ||
fontStyle: getBreakpointValue(style.fontStyle, breakpoint), | ||
fontWeight: getBreakpointValue(style.fontWeight, breakpoint), | ||
letterSpacing: getBreakpointValue(style.letterSpacing, breakpoint), | ||
lineHeight: getBreakpointValue(style.lineHeight, breakpoint), | ||
textDecorationLine: getBreakpointValue( | ||
style.textDecorationLine, | ||
breakpoint | ||
), | ||
textTransform: getBreakpointValue(style.textTransform, breakpoint), | ||
}; | ||
|
||
// Skip the breakpoint if the style is the same as the base | ||
if ( | ||
Object.entries(breakpointStyle).every( | ||
([key, value]) => | ||
value === undefined || | ||
value === baseStyle[key as keyof typeof baseStyle] | ||
) | ||
) { | ||
return acc; | ||
} | ||
|
||
return { | ||
...acc, | ||
[`@media (min-width: ${breakpoints[breakpoint].$value})`]: | ||
breakpointStyle, | ||
}; | ||
}, {}); | ||
|
||
return { ...obj, [className]: { ...baseStyle, ...mediaQueries } }; | ||
}, | ||
{} | ||
); | ||
}; | ||
|
||
export default async function exportTailwindFromTokens( | ||
tokens: Tokens, | ||
outputDir: string, | ||
figmaFileKey: string, | ||
versionDescription: string, | ||
fontFallbacks?: { [token: string]: string } | ||
) { | ||
const screens = processTokens(tokens.breakpoints); | ||
|
||
const colors = processTokens(tokens.colours, (key) => { | ||
// Convert any "default" keys to "DEFAULT" which is what Tailwind uses | ||
if (key === 'default') return 'DEFAULT'; | ||
|
||
return key; | ||
}); | ||
|
||
const borderRadius = processTokens(tokens.radii); | ||
|
||
const boxShadow = processTokens( | ||
tokens.shadows, | ||
undefined, | ||
convertShadowsDesignTokenToCss | ||
); | ||
|
||
const fonts = processTokens(tokens.typography.fonts); | ||
const fontFamily = Object.keys(fonts).reduce<Dictionary<string>>( | ||
(obj, name) => { | ||
// Add font fallbacks if they exist | ||
const font = fonts[name]; | ||
const fallback = fontFallbacks?.[name] ?? 'sans-serif'; | ||
|
||
return { ...obj, [name]: `${font}, ${fallback}` }; | ||
}, | ||
{} | ||
); | ||
// Add "sans" and "serif" to the font list if they don't exist (Tailwind uses these) | ||
if (!fontFamily.sans) fontFamily.sans = fontFamily.body; | ||
if (!fontFamily.serif) fontFamily.serif = fontFamily.heading; | ||
|
||
const fontSize = processTokens(tokens.typography.fontSizes); | ||
|
||
const lineHeight = processTokens(tokens.typography.lineHeights); | ||
|
||
const letterSpacing = processTokens(tokens.typography.letterSpacing); | ||
|
||
const textStyles = processTextStyles(tokens.textStyles, tokens.breakpoints); | ||
|
||
const tailwind = { | ||
screens, | ||
colors, | ||
borderRadius, | ||
boxShadow, | ||
fontFamily, | ||
fontSize, | ||
lineHeight, | ||
letterSpacing, | ||
textStyles, | ||
urls: tokens.urls, | ||
}; | ||
|
||
await renderTemplate( | ||
`${templateDir}/tailwind.figma2theme.js.ejs`, | ||
`${outputDir}/tailwind.figma2theme.js`, | ||
{ tailwind, version, figmaFileKey, versionDescription } | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export { default as exportChakra } from './export-chakra'; | ||
export { default as exportJson } from './export-json'; | ||
export { default as exportCss } from './export-css'; | ||
export { default as exportTailwind } from './export-tailwind'; |
Oops, something went wrong.