-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Tailwind-email]: Add new config for tailwind-email
This is a greatly pared down version of the Tailwind config with the constraints of HTML email development in mind.
- Loading branch information
Showing
8 changed files
with
332 additions
and
0 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
23 changes: 23 additions & 0 deletions
23
src/tokens/transformation/tailwind-email/copyStaticFiles.js
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,23 @@ | ||
const { join } = require('path') | ||
const { readdirSync, unlink, cpSync, rmdir, statSync } = require('fs') | ||
|
||
const staticFilesPath = join(__dirname, './static') | ||
const staticFiles = readdirSync(staticFilesPath) | ||
|
||
module.exports = { | ||
do: function (dictionary, config) { | ||
const targetDir = join(config.buildPath, config.preset) | ||
cpSync(staticFilesPath, targetDir, { recursive: true }) | ||
}, | ||
undo: function (dictionary, config) { | ||
staticFiles.forEach((file) => { | ||
const target = join(config.buildPath, config.preset, file) | ||
|
||
if (statSync(target).isDirectory()) { | ||
rmdir(target) | ||
} else { | ||
unlink(target) | ||
} | ||
}) | ||
} | ||
} |
159 changes: 159 additions & 0 deletions
159
src/tokens/transformation/tailwind-email/formatTokens.ts
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,159 @@ | ||
import merge from 'lodash.merge' | ||
import { Formatter } from 'style-dictionary' | ||
|
||
const themes = ['light', 'dark'] | ||
|
||
const kebabCase = (str: string) => str && str.toLowerCase().replaceAll(' ', '-') | ||
|
||
/** | ||
* This function transforms tokens into a nested object | ||
* structure ready for Tailwind. The conditional statements | ||
* are largely to handle creating both static and dynamic | ||
* tokens. E.g. A given token needs three variants: | ||
* dynamic, static light, and static dark. This function gets | ||
* run twice: once to create all static tokens, and another | ||
* time to create the dynamic tokens which places values on | ||
* the parent (e.g. the root object, or 'legacy') and uses the | ||
* appropriate color variable. | ||
*/ | ||
function createColorTokensFromGroup(tokens, staticTheme = true) { | ||
const colorTokens = {} | ||
tokens.forEach(({ type, name, ...t }) => { | ||
if (type === 'color') { | ||
/** | ||
* The following conditions are in order to properly group | ||
* color tokens and format into a nested object structure | ||
* for use in Tailwind. | ||
*/ | ||
let colorGroup = colorTokens[t.attributes.type] ?? {} | ||
|
||
const tItem = kebabCase(t.attributes.item) | ||
const tSubItem = kebabCase(t.attributes.subitem) | ||
|
||
/** | ||
* `state` is for the deepest level on a token. | ||
* E.g. `icon` in colors.systemfeedback.success.icon | ||
*/ | ||
if (t.attributes.state) { | ||
if (!staticTheme) { | ||
// If not on a static theme, do not place within `dark` or `light` groups | ||
colorTokens[tItem] = colorTokens[tItem] || {} | ||
const tokenGroup = colorTokens[tItem][tSubItem] ?? {} | ||
colorTokens[tItem][tSubItem] = merge(tokenGroup, { | ||
[t.attributes.state]: t.value | ||
}) | ||
} else { | ||
// If on a static theme, place within `dark` or `light` groups | ||
const tokenGroup = colorGroup[tItem] | ||
colorGroup[tItem] = merge(tokenGroup, { | ||
[tSubItem]: t.value | ||
}) | ||
} | ||
} else if (tSubItem) { | ||
/** | ||
* If not on a static theme AND theme is determined by `type` | ||
* property do not place within `dark` or `light` groups | ||
*/ | ||
if (themes.includes(t.attributes.type) && !staticTheme) { | ||
const tokenGroup = colorTokens[tItem] ?? {} | ||
colorTokens[tItem] = merge(tokenGroup, { | ||
[tSubItem]: t.value | ||
}) | ||
|
||
/** | ||
* If not on a static theme AND theme is determined by `item` | ||
* property (e.g. legacy tokens) do not place within `dark` | ||
* or `light` groups | ||
*/ | ||
} else if (themes.includes(t.attributes.item) && !staticTheme) { | ||
const tokenGroup = colorTokens[t.attributes.type] ?? {} | ||
colorTokens[t.attributes.type] = merge(tokenGroup, { | ||
[tSubItem]: t.value | ||
}) | ||
} else { | ||
// If on a static theme, place within `dark` or `light` groups | ||
const tokenGroup = colorGroup[tItem] | ||
colorGroup[tItem] = merge(tokenGroup, { | ||
[tSubItem]: t.value | ||
}) | ||
} | ||
|
||
/** | ||
* If `item` property is the token name, don't nest inside object | ||
*/ | ||
} else if (t.attributes.item) { | ||
colorGroup[tItem] = t.value | ||
|
||
/** | ||
* If `item` property is the token name, set directly on colorGroup | ||
*/ | ||
} else if (t.attributes.type) { | ||
colorGroup = t.value | ||
} | ||
|
||
if (Object.keys(colorGroup).length > 0) { | ||
colorTokens[t.attributes.type] = colorGroup | ||
} | ||
} | ||
}) | ||
return colorTokens | ||
} | ||
|
||
export default (({ dictionary }) => { | ||
const colorTokens = createColorTokensFromGroup(dictionary.allTokens) | ||
|
||
const borderRadii = new Map([['none', '0']]) | ||
const spacing = new Map<string | number, string | number>([[0, 0]]) // Initialize with option for 0 spacing | ||
const gradients = new Map() | ||
const boxShadows = new Map([['none', 'none']]) | ||
const dropShadows = new Map<string, string | string[]>([ | ||
['none', '0 0 #0000'] | ||
]) | ||
|
||
// Format all other tokens | ||
dictionary.allTokens.forEach(({ type, name, ...t }) => { | ||
const attributes = t.attributes! | ||
if (attributes.category === 'radius') { | ||
if (attributes.type === 'full') { | ||
borderRadii.set(attributes.type, '9999px') | ||
} else { | ||
borderRadii.set(attributes.type!, t.value) | ||
} | ||
} else if (attributes.category === 'spacing') { | ||
spacing.set(attributes.type!, t.value) | ||
} else if (type === 'custom-gradient') { | ||
const [, ...pathParts] = t.path | ||
gradients.set(pathParts.join('-'), t.value) | ||
} else if (type === 'custom-shadow') { | ||
const [, ...pathParts] = t.path | ||
boxShadows.set( | ||
pathParts | ||
.filter((v) => !['elevation', 'light', 'dark'].includes(v)) | ||
.join('-') | ||
.replaceAll(' ', '-'), | ||
t.value.boxShadow | ||
) | ||
dropShadows.set( | ||
pathParts | ||
.filter((v) => !['elevation', 'light', 'dark'].includes(v)) | ||
.join('-') | ||
.replaceAll(' ', '-'), | ||
t.value.dropShadow | ||
) | ||
} | ||
}) | ||
|
||
// Note: replace strips out 'light-mode' and 'dark-mode' inside media queries | ||
return `module.exports = ${JSON.stringify( | ||
{ | ||
colors: colorTokens, | ||
spacing: Object.fromEntries(spacing), | ||
borderRadius: Object.fromEntries(borderRadii), | ||
boxShadow: Object.fromEntries(boxShadows), | ||
dropShadow: Object.fromEntries(dropShadows), | ||
gradients: Object.fromEntries(gradients) | ||
}, | ||
null, | ||
' '.repeat(2) | ||
)}` | ||
}) as Formatter |
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,87 @@ | ||
import StyleDictionary from 'style-dictionary' | ||
|
||
import twFilterTokens from '../tailwind/twFilterTokens' | ||
import twFilterFonts from '../tailwind/twFilterFonts' | ||
|
||
import sizePx from '../web/sizePx' | ||
import twShadows from '../tailwind/twShadows' | ||
import webRadius from '../web/webRadius' | ||
import webSize from '../web/webSize' | ||
import webPadding from '../web/webPadding' | ||
import twFont from '../tailwind/twFont' | ||
import webGradient from '../web/webGradient' | ||
import formatFonts from '../tailwind/formatFonts' | ||
|
||
import formatTokens from './formatTokens' | ||
import copyStaticFiles from './copyStaticFiles' | ||
|
||
// Filters | ||
StyleDictionary.registerFilter({ | ||
name: 'tw/filterTokens', | ||
matcher: twFilterTokens | ||
}) | ||
|
||
StyleDictionary.registerFilter({ | ||
name: 'tw/filterFonts', | ||
matcher: twFilterFonts | ||
}) | ||
|
||
// Transforms | ||
StyleDictionary.registerTransform({ | ||
name: 'size/px', | ||
...sizePx | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'tw/shadow', | ||
...twShadows | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'web/radius', | ||
...webRadius | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'web/size', | ||
...webSize | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'web/padding', | ||
...webPadding | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'tw/font', | ||
...twFont | ||
}) | ||
StyleDictionary.registerTransform({ | ||
name: 'web/gradient', | ||
...webGradient | ||
}) | ||
|
||
StyleDictionary.registerTransformGroup({ | ||
name: 'tailwind/css', | ||
transforms: StyleDictionary.transformGroup.css.concat([ | ||
'size/px', | ||
'tw/shadow', | ||
'web/radius', | ||
'web/size', | ||
'web/padding', | ||
'tw/font', | ||
'web/gradient' | ||
]) | ||
}) | ||
|
||
StyleDictionary.registerFormat({ | ||
name: 'tailwind/tokens', | ||
formatter: formatTokens | ||
}) | ||
|
||
StyleDictionary.registerFormat({ | ||
name: 'tailwind/fonts', | ||
formatter: formatFonts | ||
}) | ||
|
||
// Actions | ||
StyleDictionary.registerAction({ | ||
name: 'tailwind/copy_static_files', | ||
do: copyStaticFiles.do, | ||
undo: copyStaticFiles.undo | ||
}) |
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,29 @@ | ||
const { | ||
colors, | ||
boxShadow, | ||
dropShadow, | ||
gradients, | ||
borderRadius, | ||
spacing | ||
} = require('./tokens') | ||
|
||
/** @type {import('tailwindcss').Config} */ | ||
module.exports = { | ||
theme: { | ||
boxShadow: {}, | ||
borderRadius: {}, | ||
spacing: {}, | ||
dropShadow: {}, | ||
colors: {}, | ||
extend: { | ||
boxShadow, | ||
borderRadius, | ||
spacing, | ||
dropShadow, | ||
colors: colors, | ||
backgroundImage: { | ||
...gradients | ||
} | ||
} | ||
} | ||
} |
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,3 @@ | ||
{ | ||
"type": "commonjs" | ||
} |
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