Author: Mirco Veltri [indaco@users.noreply.github.com]
Committer: GitHub [noreply@github.com] Tue, 24 Oct 2023 19:37:06 +0000
Hash: 982f431dd7dddacbb828394754b8b73ce908af61
Timestamp: Tue, 24 Oct 2023 19:37:06 +0000 (11 months ago)

+169 -2 +/-4 browse
Style dictionary tokens (#433)
Style dictionary tokens (#433)

* feat: generate json for style dictionary

* chore: update package-lock.json

* refactor(build/to-style-dictionary.js): maintains code consistency with others

* fix(build/to-style-dictionary.js): references to existing tokens

* chore: add export entry for `open-props.style-dictionary-tokens.json`

* feat(build/to-style-dictionary.js): nested tokens for 'other' meta types

* refactor(build/to-style-dictionary.js): replace the forEach loop with reduce

* style(build/to-style-dictionary.js): adhere to the formatting style of the code

* fix(build/to-style-dictionary.js): references to existing tokens when nested
1diff --git a/build/props.js b/build/props.js
2index fc8a4f0..c8f74e2 100644
3--- a/build/props.js
4+++ b/build/props.js
5 @@ -22,6 +22,7 @@ import {buildPropsStylesheet} from './to-stylesheet.js'
6 import {toTokens} from './to-tokens.js'
7 import {toObject} from './to-object.js'
8 import {toFigmaTokens} from './to-figmatokens.js'
9+ import {toStyleDictionary} from './to-style-dictionary.js'
10
11 const [,,prefix='',useWhere,customSubject='',filePrefix=''] = process.argv
12
13 @@ -77,6 +78,11 @@ const designtokens = toTokens(jsonbundle)
14 const JSONtokens = fs.createWriteStream('../open-props.tokens.json')
15 JSONtokens.end(JSON.stringify(Object.fromEntries(designtokens), null, 2))
16
17+ // gen style-dictionary tokens
18+ const styledictionarytokens = toStyleDictionary(jsonbundle)
19+ const StyleDictionaryTokens = fs.createWriteStream('../open-props.style-dictionary-tokens.json')
20+ StyleDictionaryTokens.end(JSON.stringify(styledictionarytokens, null, 2))
21+
22 // gen figma tokens
23 const figmatokens = toFigmaTokens(jsonbundle)
24 const FigmaTokens = fs.createWriteStream('../open-props.figma-tokens.json')
25 diff --git a/build/to-style-dictionary.js b/build/to-style-dictionary.js
26new file mode 100644
27index 0000000..875aa31
28--- /dev/null
29+++ b/build/to-style-dictionary.js
30 @@ -0,0 +1,160 @@
31+ import * as Colors from '../src/props.colors.js'
32+
33+ // Mapping of CSS variable names to dictionary keys
34+ const dictionaryMap = {
35+ "size-relative": "relative",
36+ "size-fluid": "fluid",
37+ "size-header": "header",
38+ "size-content": "content",
39+ "border-size": "size",
40+ "radius-conditional": "conditional",
41+ "radius-blob": "blob"
42+ }
43+
44+ // Map a value to a dictionary key using the dictionaryMap
45+ const mapToDictionaryKey = (value) => dictionaryMap[value] || value
46+
47+ // Determine the type key based on the metaType
48+ const getTypeKey = (metaType) => {
49+ if (metaType === "size" || metaType === "border-radius") {
50+ return metaType === "size" ? "size" : "radius"
51+ } else if (metaType === "border-width") {
52+ return "border"
53+ }
54+ return metaType
55+ }
56+
57+ // Count the occurrences of a character in a string
58+ const countOccurrences = (str, letter) => (str.match(new RegExp(letter, 'g')) || []).length
59+
60+ // Regular expression to match CSS variable usages
61+ const cssVarUsageRegex = /var\(--([a-zA-Z0-9-]+)\)/g
62+
63+ // Replace the last occurrence of a pattern with a replacement
64+ /* https://www.30secondsofcode.org/js/s/replace-last-occurrence/ */
65+ const replaceLast = (str, pattern, replacement) => {
66+ const match =
67+ typeof pattern === 'string'
68+ ? pattern
69+ : (str.match(new RegExp(pattern.source, 'g')) || []).slice(-1)[0]
70+ if (!match) return str
71+ const last = str.lastIndexOf(match)
72+ return last !== -1
73+ ? `${str.slice(0, last)}${replacement}${str.slice(last + match.length)}`
74+ : str
75+ }
76+
77+ // Helper function to convert CSS variable name to token reference
78+ const tokenizeCSSVar = (variableName, metaType) => {
79+ const tokenName = replaceLast(variableName, '-', '.')
80+ const hyphenCount = countOccurrences(variableName, '-')
81+
82+ if (hyphenCount > 2 && metaType === "other") {
83+ const [firstPart, ...restParts] = tokenName.split('-')
84+ return `{${metaType}.${firstPart}.${restParts.join('-')}.value}`
85+ }
86+
87+ return `{${tokenName}.value}`
88+ };
89+
90+ // Convert CSS variable usages to token references
91+ const cssVarToToken = (input, metaType) => {
92+ if (!input.toString().includes("var")) {
93+ return input
94+ }
95+
96+ return input.replace(cssVarUsageRegex, (match, variableName) => {
97+ return tokenizeCSSVar(variableName, metaType)
98+ })
99+ };
100+
101+ // Create a token object based on metaType and dictionary key
102+ const createTokenObject = ({
103+ baseObj,
104+ mainKey,
105+ metaType,
106+ dictionarykey,
107+ index,
108+ token
109+ }) => {
110+ const typeKey = getTypeKey(metaType)
111+ const targetObj = baseObj[typeKey] = baseObj[typeKey] || {}
112+
113+ if (typeKey === "size" || typeKey === "radius") {
114+ const shouldReplace = mainKey !== dictionarykey
115+ handleKey(targetObj, dictionarykey, index, token, metaType, shouldReplace)
116+ } else if (typeKey !== "other") {
117+ handleKey(targetObj, dictionarykey, index, token, metaType, true)
118+ } else {
119+ handleOtherTypes(targetObj, dictionarykey, index, token, metaType)
120+ }
121+
122+ return baseObj
123+ }
124+
125+ // Handle cases where meta.type != "other"
126+ function handleKey(targetObj, dictionarykey, index, token, metaType, shouldReplace) {
127+ if (shouldReplace) {
128+ targetObj[dictionarykey] = targetObj[dictionarykey] || {}
129+ targetObj[dictionarykey][index] = { value: token, type: metaType }
130+ } else {
131+ targetObj[index] = { value: token, type: metaType }
132+ }
133+ }
134+
135+ // Handle cases where meta.type = "other"
136+ function handleOtherTypes(targetObj, dictionarykey, index, token, metaType) {
137+ const keyParts = dictionarykey.split("-")
138+
139+ if (keyParts.length > 1) {
140+ const groupName = keyParts[0]
141+ targetObj[groupName] = targetObj[groupName] || {}
142+ targetObj[groupName][index] = { value: token, type: metaType }
143+
144+ const rest = keyParts.slice(1)
145+ const subKey = rest.join("-")
146+
147+ targetObj[groupName][subKey] = targetObj[groupName][subKey] || {}
148+ targetObj[groupName][subKey][index] = { value: token, type: metaType }
149+ }
150+ }
151+
152+ // Generate a style dictionary
153+ export const toStyleDictionary = props => {
154+ const colors = Object.keys(Colors)
155+ .filter(exportName => exportName !== "default")
156+ .map(hueName => hueName.toLowerCase())
157+
158+ return props.reduce((styledictionarytokens, [key, token]) => {
159+ const meta = {}
160+
161+ const isLength = key.includes('size') && !key.includes('border-size')
162+ const isBorder = key.includes('border-size')
163+ const isRadius = key.includes('radius')
164+ const isShadow = key.includes('shadow')
165+ const isColor = colors.some(color => key.includes(color))
166+
167+ if (isLength) meta.type = 'size'
168+ else if (isBorder) meta.type = 'border-width'
169+ else if (isRadius) meta.type = 'border-radius'
170+ else if (isShadow) meta.type = 'box-shadow'
171+ else if (isColor) meta.type = 'color'
172+ else meta.type = 'other'
173+
174+ const keyWithoutPrefix = key.replace('--', '')
175+ const keyParts = keyWithoutPrefix.split('-')
176+ const mainKey = keyParts.length > 1 ? keyParts.slice(0, -1).join('-') : keyParts[0]
177+ const index = keyParts.length > 1 ? keyParts[keyParts.length - 1] : 0
178+
179+ const dictionarykey = mapToDictionaryKey(mainKey)
180+
181+ return createTokenObject({
182+ baseObj: styledictionarytokens,
183+ mainKey,
184+ metaType: meta.type,
185+ dictionarykey,
186+ index,
187+ token: cssVarToToken(token, meta.type)
188+ })
189+ }, {})
190+ }
191 diff --git a/package-lock.json b/package-lock.json
192index 4e0659a..1efa6ab 100644
193--- a/package-lock.json
194+++ b/package-lock.json
195 @@ -1,12 +1,12 @@
196 {
197 "name": "open-props",
198- "version": "1.5.16",
199+ "version": "1.6.8",
200 "lockfileVersion": 2,
201 "requires": true,
202 "packages": {
203 "": {
204 "name": "open-props",
205- "version": "1.5.16",
206+ "version": "1.6.8",
207 "license": "MIT",
208 "devDependencies": {
209 "ava": "^3.15.0",
210 diff --git a/package.json b/package.json
211index b711098..709f2ef 100644
212--- a/package.json
213+++ b/package.json
214 @@ -171,6 +171,7 @@
215 "./json": "./open-props.tokens.json",
216 "./tokens": "./open-props.tokens.json",
217 "./design-tokens": "./open-props.tokens.json",
218+ "./style-dictionary-tokens": "./open-props.style-dictionary-tokens.json",
219 "./postcss/brand": "./src/extra/brand.css",
220 "./postcss/theme": "./src/extra/theme.css",
221 "./postcss/theme-dark": "./src/extra/theme-dark.css",