From 9a8feb49422600b9741684e64e067803eb542790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20S=C3=A1ros?= Date: Mon, 2 Oct 2023 19:21:38 +0200 Subject: [PATCH] chore(ui-icons,ui-scripts): move icon generation to ui-scipts (except icon fonts) --- package-lock.json | 250 ++++++++++++++++++ packages/ui-icons/gulpfile.js | 19 +- packages/ui-icons/package.json | 5 +- packages/ui-icons/svgo.config.js | 6 + packages/ui-scripts/lib/commands/index.js | 4 +- packages/ui-scripts/lib/icons/build-icons.js | 83 ++++++ .../lib/icons/generate-react-components.js | 145 ++++++++++ .../lib/icons/generate-svg-index.js | 72 +++++ .../ui-scripts/lib/icons/get-glyph-data.js | 62 +++++ packages/ui-scripts/package.json | 4 + 10 files changed, 630 insertions(+), 20 deletions(-) create mode 100644 packages/ui-scripts/lib/icons/build-icons.js create mode 100644 packages/ui-scripts/lib/icons/generate-react-components.js create mode 100644 packages/ui-scripts/lib/icons/generate-svg-index.js create mode 100644 packages/ui-scripts/lib/icons/get-glyph-data.js diff --git a/package-lock.json b/package-lock.json index f09e9f122f..bc25989f39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55303,6 +55303,10 @@ "bin": { "ui-scripts": "lib/index.js" }, + "devDependencies": { + "svg-to-jsx": "^1.0.4", + "svgo": "^3.0.2" + }, "optionalDependencies": { "@storybook/react": "^6.5.16" }, @@ -55312,6 +55316,113 @@ "webpack": "^5" } }, + "packages/ui-scripts/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "packages/ui-scripts/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "packages/ui-scripts/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "packages/ui-scripts/node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "packages/ui-scripts/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "packages/ui-scripts/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "packages/ui-scripts/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "packages/ui-scripts/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "packages/ui-scripts/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -55323,6 +55434,18 @@ "node": ">=10" } }, + "packages/ui-scripts/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, + "packages/ui-scripts/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "packages/ui-scripts/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -55337,6 +55460,30 @@ "node": ">=10" } }, + "packages/ui-scripts/node_modules/svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "packages/ui-scripts/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -60513,11 +60660,88 @@ "semver": "^7.5.4", "style-dictionary": "2.10.3", "stylelint": "^15.10.3", + "svg-to-jsx": "^1.0.4", + "svgo": "^3.0.2", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1", "yargs": "^17.7.2" }, "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "requires": { + "css-tree": "~2.2.0" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -60526,6 +60750,18 @@ "yallist": "^4.0.0" } }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -60534,6 +60770,20 @@ "lru-cache": "^6.0.0" } }, + "svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + } + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/packages/ui-icons/gulpfile.js b/packages/ui-icons/gulpfile.js index 8e0b36a9ac..e37b4e3271 100644 --- a/packages/ui-icons/gulpfile.js +++ b/packages/ui-icons/gulpfile.js @@ -23,21 +23,6 @@ */ const gulp = require('gulp') +const { buildFonts } = require('@instructure/ui-icons-build') -const { - optimizeSVGs, - buildSVGs, - buildFonts, - buildReact, - buildAll, - clean -} = require('@instructure/ui-icons-build') - -gulp.task('clean', clean) -gulp.task('optimize', optimizeSVGs) -gulp.task('build:svgs', buildSVGs) -gulp.task('build:fonts', buildFonts) -gulp.task('build:react', buildReact) -gulp.task('build', buildAll) - -gulp.task('default', buildAll) +gulp.task('default', buildFonts) diff --git a/packages/ui-icons/package.json b/packages/ui-icons/package.json index d926abdf68..ea397221e5 100644 --- a/packages/ui-icons/package.json +++ b/packages/ui-icons/package.json @@ -14,9 +14,10 @@ "bugs": "https://github.com/instructure/instructure-ui/issues", "scripts": { "clean": "gulp clean && ui-scripts clean", - "prepare-build": "gulp", + "prepare-build": "npm run build-icons", + "build-icons": "ui-scripts build-icons --svgoConfig svgo.config.js --config icons.config.js && gulp", "build": "ui-scripts build __build__ --copy-files --modules es,cjs", - "export": "gulp clean && gulp && npm run build:types", + "export": "npm run build-icons && npm run build:types", "build:types": "tsc -p tsconfig.build.json", "ts:check": "tsc -p tsconfig.build.json --noEmit --emitDeclarationOnly false" }, diff --git a/packages/ui-icons/svgo.config.js b/packages/ui-icons/svgo.config.js index b41c82e746..1f0191adad 100644 --- a/packages/ui-icons/svgo.config.js +++ b/packages/ui-icons/svgo.config.js @@ -52,6 +52,12 @@ module.exports = { name: 'convertPathData', active: false }, + { + name: 'removeAttrs', + params: { + attrs: '(fill)' + } + }, { // Custom plugin for wrapping multiple path svg-s into a `` tag. // This is needed because all svg files are supposed to have diff --git a/packages/ui-scripts/lib/commands/index.js b/packages/ui-scripts/lib/commands/index.js index 16bd5eb903..2212058ec2 100644 --- a/packages/ui-scripts/lib/commands/index.js +++ b/packages/ui-scripts/lib/commands/index.js @@ -34,6 +34,7 @@ import bundle from '../build/webpack.js' import clean from '../build/clean.js' import build from '../build/babel.js' import generateAllTokens from '../build/generate-all-tokens.js' +import buildIcons from '../icons/build-icons.js' export const yargCommands = [ bump, @@ -47,5 +48,6 @@ export const yargCommands = [ bundle, clean, build, - generateAllTokens + generateAllTokens, + buildIcons ] diff --git a/packages/ui-scripts/lib/icons/build-icons.js b/packages/ui-scripts/lib/icons/build-icons.js new file mode 100644 index 0000000000..cbb8b5d14f --- /dev/null +++ b/packages/ui-scripts/lib/icons/build-icons.js @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import { runCommandSync } from '@instructure/command-utils' +import path from 'path' +import fs from 'fs' +import process from 'process' +import getGlyphData from './get-glyph-data.js' +import generateReactComponents from './generate-react-components.js' +import generateSvgIndex from './generate-svg-index.js' + +export default { + command: 'build-icons', + desc: 'build icons', + builder: (yargs) => { + yargs.option('config', { + string: true, + desc: 'icon config file', + demandOption: true + }) + yargs.option('svgoConfig', { string: true, desc: 'svgo config file' }) + }, + handler: async (argv) => { + const configFile = path.join(process.cwd(), argv.config) + const config = await import(configFile) + const svgSourceDir = path.join(process.cwd(), config.source) + + const svgoConfigFile = + argv.svgoConfig && path.join(process.cwd(), argv.svgoConfig) + const svgoConfigOption = svgoConfigFile ? ['--config', svgoConfigFile] : [] + + // optimize svgs in place + runCommandSync('svgo', [ + '-r', + '-f', + svgSourceDir, + '-o', + svgSourceDir, + ...svgoConfigOption + ]) + + // cleanup before generating output + if (fs.existsSync(config.destination)) { + fs.rmSync(config.destination, { recursive: true, force: true }) + } + fs.mkdirSync(config.destination) + + const glyphs = getGlyphData( + svgSourceDir, + config.deprecated, + config.bidirectional, + config.react.componentBaseName + ) + + // generate svg index + generateSvgIndex(glyphs, config.destination) + // - output: ui-icons/__build__/svg/index.js + // - fields: variant, glyphName, src (svg), deprecated + + // generate react components + generateReactComponents(glyphs, config.destination) + } +} diff --git a/packages/ui-scripts/lib/icons/generate-react-components.js b/packages/ui-scripts/lib/icons/generate-react-components.js new file mode 100644 index 0000000000..62ce4c2346 --- /dev/null +++ b/packages/ui-scripts/lib/icons/generate-react-components.js @@ -0,0 +1,145 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import fs from 'fs' +import svg2jsx from 'svg-to-jsx' + +const NOTICE_HEADER = `/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +` + +async function generateIconComponent(glyph) { + const { name, variant, glyphName, deprecated, bidirectional, src } = glyph + + const source = src.match( + /]*?(?:viewBox="(\b[^"]*)")?>([\s\S]*?)<\/svg>/ + )[2] + const jsxSource = await svg2jsx(source) + const viewBox = src.match(/viewBox="(.*?)"/)[1] + + const content = `import React, { Component } from 'react' +import { SVGIcon } from '@instructure/ui-svg-images' +import type { SVGIconProps } from '@instructure/ui-svg-images' + +class ${name}${variant} extends Component { + static glyphName = '${glyphName}' + static variant = '${variant}' + static displayName = '${name}${variant}' + ${deprecated ? `static deprecated = true` : ''} + // eslint-disable-next-line react/forbid-foreign-prop-types + static propTypes = { ...SVGIcon.propTypes } + static allowedProps = [ ...SVGIcon.allowedProps ] + + ref: Element | null = null + + handleRef = (el: Element | null) => { + const { elementRef } = this.props + + this.ref = el + + if (typeof elementRef === 'function') { + elementRef(el) + } + } + + render () { + ${ + deprecated + ? `if (process.env.NODE_ENV !== 'production') { + console.warn('<${name}${variant} /> is deprecated. Please use <${deprecated}${variant} /> instead.') + }` + : '' + } + return ( + + ${jsxSource} + + ) + } +} + +export default ${name}${variant} +export { ${name}${variant} } +` + return NOTICE_HEADER + content +} + +function generateIconIndex(glyphs) { + const content = glyphs + .map((glyph) => { + return `export { ${glyph.name}${glyph.variant} } from './${glyph.name}${glyph.variant}'` + }) + .join('\n\n') + + return NOTICE_HEADER + content +} + +export default function generateReactComponents(glyphs, destination) { + glyphs.forEach(async (glyph) => { + const fileName = `${destination}${glyph.name}${glyph.variant}.tsx` + const componentContent = await generateIconComponent(glyph) + + fs.writeFile(fileName, componentContent, (err) => { + if (err) { + console.error(err) + } + // file written successfully + }) + }) + + const indexFilePath = `${destination}index.ts` + const indexContent = generateIconIndex(glyphs) + fs.writeFile(indexFilePath, indexContent, (err) => { + if (err) { + console.error(err) + } + // file written successfully + }) +} diff --git a/packages/ui-scripts/lib/icons/generate-svg-index.js b/packages/ui-scripts/lib/icons/generate-svg-index.js new file mode 100644 index 0000000000..85ff58cd6e --- /dev/null +++ b/packages/ui-scripts/lib/icons/generate-svg-index.js @@ -0,0 +1,72 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import fs from 'fs' + +const NOTICE_HEADER = `/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +` + +export default function generateSvgIndex(glyphs, destination) { + const content = glyphs + .map((glyph) => { + return `export const ${glyph.name}${glyph.variant} = { + variant: "${glyph.variant}", + glyphName: "${glyph.glyphName}", + src: \`${glyph.src}\`, + deprecated: ${!!glyph.deprecated} +}` + }) + .join('\n\n') + + fs.mkdirSync(`${destination}svg`) + const indexFilePath = `${destination}svg/index.js` + const indexContent = NOTICE_HEADER + content + fs.writeFile(indexFilePath, indexContent, (err) => { + if (err) { + console.error(err) + } + // file written successfully + }) +} diff --git a/packages/ui-scripts/lib/icons/get-glyph-data.js b/packages/ui-scripts/lib/icons/get-glyph-data.js new file mode 100644 index 0000000000..de139eb806 --- /dev/null +++ b/packages/ui-scripts/lib/icons/get-glyph-data.js @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import fs from 'fs' +import path from 'path' + +function toPascalCase(text) { + return text.replace(/(^\w|-\w)/g, (text) => + text.replace(/-/, '').toUpperCase() + ) +} + +export default function getGlyphData( + svgSourceDir, + deprecatedMap, + bidirectionalList, + prefix +) { + const glyphs = [] + // variants are in different sub directories + const subdirs = fs.readdirSync(svgSourceDir) + + subdirs.forEach((subdir) => { + const fileNames = fs.readdirSync(svgSourceDir + subdir) + fileNames.forEach((fileName) => { + const { name, ext } = path.parse(fileName) + if (ext !== '.svg') return + + const filepath = path.resolve(svgSourceDir + subdir, fileName) + const fileContent = fs.readFileSync(filepath, { encoding: 'utf8' }) + glyphs.push({ + name: prefix + toPascalCase(name), + glyphName: name, + variant: subdir, + src: fileContent, + bidirectional: bidirectionalList.includes(name), + deprecated: deprecatedMap[name] ?? false + }) + }) + }) + return glyphs +} diff --git a/packages/ui-scripts/package.json b/packages/ui-scripts/package.json index ca89ac32e8..f95235d754 100644 --- a/packages/ui-scripts/package.json +++ b/packages/ui-scripts/package.json @@ -50,5 +50,9 @@ }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "svg-to-jsx": "^1.0.4", + "svgo": "^3.0.2" } }