diff --git a/docs/src/components/sd-playground.ts b/docs/src/components/sd-playground.ts index c33fbae5a..1dbf2f606 100644 --- a/docs/src/components/sd-playground.ts +++ b/docs/src/components/sd-playground.ts @@ -10,6 +10,9 @@ import '@shoelace-style/shoelace/dist/components/select/select.js'; import '@shoelace-style/shoelace/dist/components/option/option.js'; import { bundle } from '../utils/rollup-bundle.ts'; import { changeLang, init } from '../monaco/monaco.ts'; +import { analyzeDependencies } from '../utils/analyzeDependencies.ts'; +import type SlRadioGroup from '@shoelace-style/shoelace/dist/components/radio-group/radio-group.js'; +import { downloadZIP } from '../utils/downloadZIP.ts'; const { Volume } = memfs; @@ -35,6 +38,7 @@ const defaults = { }, }, }, + script: "import StyleDictionary from 'style-dictionary';", }; const getLang = (lang: string) => { @@ -78,6 +82,29 @@ class SdPlayground extends LitElement { width: 1px !important; white-space: nowrap !important; } + + @media (max-width: 550px) { + ::part(button-group) { + display: block; + } + ::part(button-group__base) { + flex-direction: column; + } + + sl-radio-button[data-sl-button-group__button--first]::part(button) { + border-top-left-radius: var(--sl-input-border-radius-medium); + border-top-right-radius: var(--sl-input-border-radius-medium); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + sl-radio-button[data-sl-button-group__button--last]::part(button) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: var(--sl-input-border-radius-medium); + border-bottom-right-radius: var(--sl-input-border-radius-medium); + } + } `; } @@ -150,6 +177,9 @@ class SdPlayground extends LitElement { return html` { + if ((ev.target as SlRadioGroup).value === 'eject') { + return; + } this.currentFile = (ev.target as HTMLInputElement).value as Files; }} name="file-switch" @@ -179,6 +209,30 @@ class SdPlayground extends LitElement { `, )} + + + + + + `; @@ -266,18 +320,19 @@ class SdPlayground extends LitElement { return sdConfig as Config; } - async fileSwitch(val: 'tokens' | 'config' | 'script' | 'output') { - await this.hasInitialized; - - const data = { - lang: JSON.parse(this[val as keyof SdPlayground]).lang ?? 'json', + getFileData(file: 'tokens' | 'config' | 'script' | 'output') { + const def = defaults[file as keyof typeof defaults]; + return { + lang: JSON.parse(this[file]).lang ?? (file === 'script' ? 'js' : 'json'), value: - JSON.parse(this[val]).value ?? - (val === 'script' ? '' : JSON.stringify(defaults[val as keyof typeof defaults], null, 2)), + JSON.parse(this[file]).value ?? (file === 'script' ? def : JSON.stringify(def, null, 2)), }; + } + async fileSwitch(val: 'tokens' | 'config' | 'script' | 'output') { + await this.hasInitialized; + const data = this.getFileData(val); this.editor.setValue(data.value); - await changeLang(getLang(data.lang), this.editor); } @@ -321,10 +376,63 @@ class SdPlayground extends LitElement { async saveFile() { this[this.currentFile] = JSON.stringify({ value: this.editor.getValue(), - lang: JSON.parse(this[this.currentFile]).lang ?? 'json', + lang: this.getFileData(this.currentFile).lang, }); await this.initData(); } + + async ejectHandler() { + const tokens = this.getFileData('tokens'); + const script = this.getFileData('script'); + const config = this.getFileData('config'); + const dependencies = await analyzeDependencies(script.value); + const sdDep = dependencies.find((dep) => dep.package === 'style-dictionary'); + if (sdDep) { + sdDep.package = 'style-dictionary@4.0.0-prerelease.28'; + } + const files: Record = {}; + files[`tokens.${tokens.lang}`] = tokens.value; + + const scriptLang = script.lang === 'js' ? 'mjs' : script.lang; + const configLang = config.lang === 'js' ? 'mjs' : config.lang; + + if (configLang === 'json') { + const parsed = JSON.parse(config.value); + parsed.source = [`tokens.${tokens.lang}`]; + config.value = JSON.stringify(parsed, null, 2); + } else if (config.lang === 'js') { + // this is a bit brittle, to add the "source" into a JS file like that.. + config.value = config.value.replace( + /export( *)default( *){/, + `export default {\n source: ['tokens.${tokens.lang}'],`, + ); + } + + files[`config.${configLang}`] = config.value; + files[`build-tokens.${scriptLang}`] = `${script.value} + +const sd = new StyleDictionary('config.${configLang}'); + +await sd.cleanAllPlatforms(); +await sd.buildAllPlatforms(); +`; + files['README.md'] = `# Style Dictionary Eject + +Install your dependencies with [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm): + +\`\`\`sh +npm init -y && npm install ${dependencies.map((dep) => dep.package).join(' ')} +\`\`\` + +Then run + +\`\`\`sh +node build-tokens.${scriptLang} +\`\`\` +`; + + await downloadZIP(files); + } } customElements.define('sd-playground', SdPlayground); diff --git a/docs/src/styles.css b/docs/src/styles.css index 6e6de2de1..e76ec5dea 100644 --- a/docs/src/styles.css +++ b/docs/src/styles.css @@ -39,6 +39,10 @@ table code { max-width: 900px; } +a { + display: inline-block; +} + @media (min-width: 77rem) { [data-has-sidebar][data-has-toc] .main-pane { width: auto; diff --git a/docs/src/utils/analyzeDependencies.ts b/docs/src/utils/analyzeDependencies.ts new file mode 100644 index 000000000..2a284420d --- /dev/null +++ b/docs/src/utils/analyzeDependencies.ts @@ -0,0 +1,36 @@ +import { parse } from 'acorn'; +import { asyncWalk } from 'estree-walker'; + +import type { Node } from 'estree-walker'; + +export async function analyzeDependencies(code: string) { + let dependencies: Array<{ + source: string; + specifiers: Array<{ name: string; default: boolean }>; + package: string; + }> = []; + const ast = parse(code, { + allowImportExportEverywhere: true, + ecmaVersion: 'latest', + }) as Node; + + await asyncWalk(ast, { + enter: async (node) => { + if (node.type === 'ImportDeclaration') { + const source = `${node.source.value}`; + dependencies.push({ + source: source, + specifiers: node.specifiers.map((spec) => ({ + name: spec.local.name, + default: spec.type === 'ImportDefaultSpecifier', + })), + package: source + .split('/') + .slice(0, source.startsWith('@') ? 2 : 1) + .join('/'), + }); + } + }, + }); + return dependencies; +} diff --git a/docs/src/utils/downloadZIP.ts b/docs/src/utils/downloadZIP.ts new file mode 100644 index 000000000..2c18146c6 --- /dev/null +++ b/docs/src/utils/downloadZIP.ts @@ -0,0 +1,23 @@ +import * as zip from '@zip.js/zip.js'; + +export async function downloadZIP(files: Record) { + const zipWriter = new zip.ZipWriter(new zip.BlobWriter('application/zip')); + + await Promise.all( + Object.entries(files).map(([key, value]) => zipWriter.add(key, new zip.TextReader(value))), + ); + + // Close zip and make into URL + const dataURI = await zipWriter.close(); + const url = URL.createObjectURL(dataURI); + + // Auto-download the ZIP through anchor + const anchor = document.createElement('a'); + anchor.href = url; + const today = new Date(); + anchor.download = `sd-output_${today.getFullYear()}-${today.getMonth()}-${( + '0' + today.getDate() + ).slice(-2)}.zip`; + anchor.click(); + URL.revokeObjectURL(url); +} diff --git a/package-lock.json b/package-lock.json index c42a36f28..f4909d581 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "style-dictionary", - "version": "4.0.0-prerelease.26", + "version": "4.0.0-prerelease.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "style-dictionary", - "version": "4.0.0-prerelease.26", + "version": "4.0.0-prerelease.28", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -42,11 +42,14 @@ "@web/test-runner": "^0.18.0", "@web/test-runner-commands": "^0.9.0", "@web/test-runner-playwright": "^0.11.0", + "@zip.js/zip.js": "^2.7.44", + "acorn": "^8.11.3", "astro": "^4.3.5", "chai": "^5.0.0-alpha.2", "eslint": "^8.7.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-mocha": "^10.2.0", + "estree-walker": "^3.0.3", "fs-extra": "^10.0.0", "hanbi": "^1.0.1", "husky": "^8.0.3", @@ -5428,15 +5431,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/@mdx-js/mdx/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -5634,6 +5628,12 @@ } } }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", @@ -7417,6 +7417,17 @@ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.44", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.44.tgz", + "integrity": "sha512-ZzMhAcAyRAYi1FZELsvKaw8I4ADxNTqbiVIjyo/syBe4HGWop9+OADnuBnHpm2TxgXPogxxhhPffOhDD40jUdA==", + "dev": true, + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -8296,15 +8307,6 @@ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, - "node_modules/astro/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/astro/node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -11474,15 +11476,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/estree-util-build-jsx/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", @@ -11523,10 +11516,13 @@ } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", @@ -17871,15 +17867,6 @@ "is-reference": "^3.0.0" } }, - "node_modules/periscopic/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", diff --git a/package.json b/package.json index 82e20b70b..fd9a57b74 100644 --- a/package.json +++ b/package.json @@ -129,11 +129,14 @@ "@web/test-runner": "^0.18.0", "@web/test-runner-commands": "^0.9.0", "@web/test-runner-playwright": "^0.11.0", + "@zip.js/zip.js": "^2.7.44", + "acorn": "^8.11.3", "astro": "^4.3.5", "chai": "^5.0.0-alpha.2", "eslint": "^8.7.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-mocha": "^10.2.0", + "estree-walker": "^3.0.3", "fs-extra": "^10.0.0", "hanbi": "^1.0.1", "husky": "^8.0.3",