├── CODEOWNERS ├── variant-switcher ├── src │ ├── ui.html │ ├── icons │ │ ├── index.tsx │ │ ├── Dropdown.tsx │ │ ├── Exchange.tsx │ │ └── Help.tsx │ ├── shared.ts │ ├── utils.ts │ ├── ui.css │ ├── code.ts │ └── ui.tsx ├── _assets_ │ ├── example.png │ ├── plugin-icon.png │ ├── deep-switch-diagram.png │ ├── plugin-icon.svg │ └── cover-art.svg ├── tsconfig.json ├── manifest.json ├── package.json ├── webpack.config.js ├── LICENSES.json ├── CHANGELOG.md └── README.md ├── _maintenance_ ├── icon-description │ ├── manifest.json │ ├── utils │ │ ├── concat-files.js │ │ └── build-material.js │ ├── tsconfig.json │ ├── package.json │ ├── LICENSES.json │ ├── ui.html │ ├── README.md │ ├── code.ts │ └── yarn.lock └── README.md ├── .gitignore ├── README.md ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md ├── workflows │ └── blui-pr-actions.yml └── PULL_REQUEST_TEMPLATE.md └── LICENSE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jeffGreiner-eaton @surajeaton @ektaghag-eaton 2 | -------------------------------------------------------------------------------- /variant-switcher/src/ui.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /variant-switcher/_assets_/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etn-ccis/blui-figma-plugins/HEAD/variant-switcher/_assets_/example.png -------------------------------------------------------------------------------- /variant-switcher/_assets_/plugin-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etn-ccis/blui-figma-plugins/HEAD/variant-switcher/_assets_/plugin-icon.png -------------------------------------------------------------------------------- /variant-switcher/_assets_/deep-switch-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etn-ccis/blui-figma-plugins/HEAD/variant-switcher/_assets_/deep-switch-diagram.png -------------------------------------------------------------------------------- /variant-switcher/src/icons/index.tsx: -------------------------------------------------------------------------------- 1 | import Exchange from './Exchange'; 2 | import Help from './Help'; 3 | import Dropdown from './Dropdown'; 4 | 5 | export { Exchange, Help, Dropdown }; 6 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Icon Description", 3 | "id": "994359716504097058", 4 | "api": "1.0.0", 5 | "main": "build/code.js", 6 | "ui": "ui.html" 7 | } 8 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/utils/concat-files.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | fs.writeFileSync('./build/code.js', fs.readFileSync('./build/matMeta.js') + ';\n' + fs.readFileSync('./build/code.js')); 4 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "typeRoots": ["./node_modules/@types", "./node_modules/@figma"], 5 | "outDir": "build" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /variant-switcher/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "jsx": "react", 5 | "skipLibCheck": true, 6 | "typeRoots": ["./node_modules/@types", "./node_modules/@figma"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/build 4 | 5 | .DS_Store 6 | .env.local 7 | .env.development.local 8 | .env.test.local 9 | .env.production.local 10 | .idea/ 11 | .editorconfig 12 | .angulardoc.json 13 | 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Figma Plugins 2 | 3 | This library contains various plugins developed by the Brightlayer UI team for use with [Figma](https://www.figma.com/). 4 | 5 | The following plugins are avalable: 6 | 7 | - [Variant Switcher](./variant-switcher/README.md): recursively swaps component variants based on a specified property value. 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/variant-switcher' 5 | schedule: 6 | interval: 'monthly' 7 | day: 'monday' 8 | open-pull-requests-limit: 1 9 | target-branch: 'dev' 10 | labels: 11 | - 'external-dependency' 12 | -------------------------------------------------------------------------------- /variant-switcher/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Variant Switcher", 3 | "id": "971482182464094790", 4 | "api": "1.0.0", 5 | "main": "dist/code.js", 6 | "ui": "dist/ui.html", 7 | "editorType": ["figma", "figjam"], 8 | "documentAccess": "dynamic-page", 9 | "networkAccess": { "allowedDomains": ["none"] } 10 | } 11 | -------------------------------------------------------------------------------- /_maintenance_/README.md: -------------------------------------------------------------------------------- 1 | This folder contains scripts helpful to manage Brightlayer UI resources. They are not published as Figma plugins, but rather meant to be run in the local dev environment. 2 | 3 | - icon-description: 4 | 5 | Add descriptions to [Component Sticker Sheet](https://www.figma.com/community/file/1024360297793425107) icons so that they are easier to search. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this resource 4 | title: '' 5 | labels: 'enhancement, needs-review, brightlayer-ui' 6 | assignees: '' 7 | --- 8 | 9 | #### Describe the desired feature/functionality 10 | 11 | #### Additional Context (where / how would this be used) 12 | 13 | #### Is this request related to a current issue? 14 | 15 | #### Suggested implementation details 16 | -------------------------------------------------------------------------------- /.github/workflows/blui-pr-actions.yml: -------------------------------------------------------------------------------- 1 | name: blui-pr-actions 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: read 10 | 11 | jobs: 12 | pr-labels: 13 | uses: etn-ccis/blui-automation/.github/workflows/blui-labels.yml@dev 14 | secrets: inherit 15 | 16 | pr-comment: 17 | uses: etn-ccis/blui-automation/.github/workflows/blui-comment.yml@dev 18 | secrets: inherit -------------------------------------------------------------------------------- /variant-switcher/src/icons/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const DropdownIcon: React.FC = () => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default DropdownIcon; 13 | -------------------------------------------------------------------------------- /variant-switcher/src/icons/Exchange.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const ExchangeIcon: React.FC = () => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default ExchangeIcon; 13 | -------------------------------------------------------------------------------- /variant-switcher/src/shared.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2021-present, Eaton 3 | All rights reserved. 4 | This code is licensed under the BSD-3 license found in the LICENSE file in the root directory of this source tree and at https://opensource.org/licenses/BSD-3-Clause. 5 | **/ 6 | 7 | export const KEYS = { 8 | PROPERTY_NAME: 'property-name', 9 | FROM_VARIANT: 'from-variant', 10 | TO_VARIANT: 'to-variant', 11 | DEEP_SWITCH: 'deep-switch', 12 | FULL_DOCUMENT: 'full-document', 13 | EXACT_MATCH: 'exact-match', 14 | PLUGIN_STAY_OPEN: 'plugin-stay-open', 15 | MAIN_COMPONENT_NAME: 'main-component-name', 16 | SHOW_ADVANCED_OPTIONS: 'show-advanced-options', 17 | }; 18 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icon-description", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "build/code.js", 6 | "scripts": { 7 | "build": "mkdir -p build && node ./utils/build-material.js && tsc -p tsconfig.json && node ./utils/concat-files.js", 8 | "generate:licenses": "npm-license-crawler -onlyDirectDependencies -json LICENSES.json" 9 | }, 10 | "author": "Brightlayer UI ", 11 | "license": "BSD-3-Clause", 12 | "dependencies": { 13 | "@types/figma": "^1.0.3", 14 | "node-fetch": "^2.6.7", 15 | "typescript": "^4.3.5" 16 | }, 17 | "devDependencies": { 18 | "npm-license-crawler": "^0.2.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /variant-switcher/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeText = (str: string): string => { 2 | return str.toLowerCase().replace(/[ \.,]/g, ''); 3 | }; 4 | 5 | /** 6 | * @param givenProperty "Property Name" as specified by the user 7 | * @param givenVariant "From Variant" or "To Variant" as specified by the user 8 | * @param givenPair The property-variant pair, as appears in the main component layer name 9 | */ 10 | export const fuzzyMatch = (givenProperty: string, givenVariant: string, givenPair: string) => { 11 | const newProperty = sanitizeText(givenProperty); 12 | const newVariant = sanitizeText(givenVariant); 13 | const instanceArray = givenPair.split('='); 14 | return sanitizeText(instanceArray[0]).includes(newProperty) && sanitizeText(instanceArray[1]).includes(newVariant); 15 | }; 16 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/utils/build-material.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const fetch = require('node-fetch'); 3 | 4 | // everything works fine on the BLUI side, which uses GitHub's API. 5 | // But Material Design's meta json file has CORS restrictions on it, therefore we have to 6 | // download it on build. 7 | async function main() { 8 | fetch('https://fonts.google.com/metadata/icons') 9 | .then((response) => response.text()) 10 | .then((text) => { 11 | const data = text.replace(")]}'\n", ''); 12 | const iconSet = {}; 13 | JSON.parse(data).icons.forEach((icon) => { 14 | iconSet[icon.name] = icon.tags; 15 | }); 16 | fs.writeFileSync('./build/matMeta.js', 'const matIconSet = ' + JSON.stringify(iconSet)); 17 | }); 18 | } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/LICENSES.json: -------------------------------------------------------------------------------- 1 | { 2 | "@types/figma@1.0.3": { 3 | "licenses": "MIT", 4 | "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped", 5 | "licenseUrl": "https://github.com/DefinitelyTyped/DefinitelyTyped/raw/master/LICENSE", 6 | "parents": "icon-description" 7 | }, 8 | "node-fetch@2.6.1": { 9 | "licenses": "MIT", 10 | "repository": "https://github.com/bitinn/node-fetch", 11 | "licenseUrl": "https://github.com/bitinn/node-fetch/raw/master/LICENSE.md", 12 | "parents": "icon-description" 13 | }, 14 | "typescript@4.3.5": { 15 | "licenses": "Apache-2.0", 16 | "repository": "https://github.com/Microsoft/TypeScript", 17 | "licenseUrl": "https://github.com/Microsoft/TypeScript/raw/master/LICENSE.txt", 18 | "parents": "icon-description" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/ui.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug with a Brightlayer UI resource 4 | title: '' 5 | labels: 'bug, needs-review, brightlayer-ui' 6 | assignees: '' 7 | --- 8 | 9 | #### Describe the bug / expected behavior 10 | 11 | #### What are the steps to reproduce? 12 | 13 | 1. Go to... 14 | 2. Click on... 15 | 16 | #### Screenshots / Screen recording 17 | 18 | #### Code snippet / Link to minimum reproduction example 19 | 20 | 21 | 22 | ``` 23 | CODE HERE 24 | ``` 25 | 26 | #### Your environment information 27 | 28 | 29 | 30 | #### Suggested fix 31 | 32 | 33 | 34 | #### Anything else to add? 35 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fixes # . 4 | 5 | 6 | 7 | #### Changes proposed in this Pull Request: 8 | 9 | - 10 | - 11 | - 12 | 13 | 14 | 15 | #### Screenshots / Screen Recording (if applicable) 16 | 17 | - 18 | 19 | 20 | 21 | #### To Test: 22 | 23 | - 24 | 25 | 26 | 27 | #### Any specific feedback you are looking for? 28 | 29 | - 30 | 31 | #### PR Readiness Checklist 32 | 33 | Please confirm all items below have been addressed prior to requesting review: 34 | 35 | - [ ] Code has been formatted with Prettier 36 | - [ ] Code passes all linting checks 37 | - [ ] All tests pass 38 | - [ ] Code builds successfully 39 | - [ ] Translations have been updated (if applicable) 40 | - [ ] Documentation has been updated (if applicable) 41 | - [ ] Changelog has been updated (if applicable) 42 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/README.md: -------------------------------------------------------------------------------- 1 | # Icon Description 2 | 3 | By default, this maintenance plugin will update the "description" field for each selected icon using the array of tags supplied in meta files. It will also output some useful information in Figma's developer console. 4 | 5 | ## For Maintainers 6 | 7 | The developer functionality only works through Figma desktop app. 8 | 9 | To sync up to Material Design and Brightlayer UI's latest icon meta file, open Component Stickersheet's icon page (I suggest doing this from a [Figma branch](https://help.figma.com/hc/en-us/articles/360063144053-Create-branches-and-merge-changes)), and link to this plugin's `manifest.json`. Observe that the plugin shows up under "Plugin > Development > Icon Description" in Figma. 10 | 11 | **To update Material Design icons:** You will need to do `yarn && yarn build`, select those Material icon components you would like to update, and run the plugin. 12 | 13 | **To update Brightlayer UI icons:** Make sure [the meta data at Brightlayer UI master branch](https://github.com/etn-ccis/blui-icons/blob/master/svg/index.json) is up-to-date. 14 | Then change line 5 of `code.ts` to `false` and run `yarn && yarn build`. After that, select those Brightlayer UI icon components you would want to update, and run the plugin. 15 | 16 | Both Material and Brightlayer UI's meta data is updated every time you run `yarn build`. 17 | -------------------------------------------------------------------------------- /variant-switcher/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-plugin-variant-switcher", 3 | "version": "0.0.0", 4 | "description": "Figma plugin to switch instances into a designated variant.", 5 | "main": "code.js", 6 | "scripts": { 7 | "prettier": "prettier \"src/**/**.{ts,tsx,js,jsx,json,css,scss,html,md}\" --write", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "npx webpack", 10 | "build:production": "npx webpack --mode production", 11 | "generate:licenses": "npm-license-crawler -onlyDirectDependencies -json LICENSES.json" 12 | }, 13 | "private": true, 14 | "prettier": "@brightlayer-ui/prettier-config", 15 | "author": "Brightlayer UI ", 16 | "license": "BSD-3-Clause", 17 | "dependencies": { 18 | "css-loader": "^3.1.0", 19 | "html-webpack-inline-source-plugin": "0.0.10", 20 | "html-webpack-plugin": "^3.2.0", 21 | "react": "^18.3.1", 22 | "react-dom": "^18.3.1", 23 | "style-loader": "^2.0.0", 24 | "ts-loader": "^6.0.4", 25 | "url-loader": "^4.1.1", 26 | "webpack": "^4.47.0", 27 | "webpack-cli": "^4.10.0" 28 | }, 29 | "devDependencies": { 30 | "@brightlayer-ui/prettier-config": "^1.0.3", 31 | "@figma/plugin-typings": "^1.117.0", 32 | "@types/react": "^18.3.12", 33 | "@types/react-dom": "^17.0.4", 34 | "npm-license-crawler": "^0.2.1", 35 | "prettier": "^3.6.2", 36 | "typescript": "^5.8.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020 - present, Eaton 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /variant-switcher/src/icons/Help.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const HelpIcon: React.FC = () => ( 4 | 5 | 9 | 13 | 19 | 20 | ); 21 | 22 | export default HelpIcon; 23 | -------------------------------------------------------------------------------- /variant-switcher/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => ({ 6 | mode: argv.mode === 'production' ? 'production' : 'development', 7 | 8 | // This is necessary because Figma's 'eval' works differently than normal eval 9 | devtool: argv.mode === 'production' ? false : 'inline-source-map', 10 | 11 | entry: { 12 | ui: './src/ui.tsx', // The entry point for your UI code 13 | code: './src/code.ts', // The entry point for your plugin code 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | // Converts TypeScript code to JavaScript 19 | { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }, 20 | 21 | // Enables including CSS by doing "import './file.css'" in your TypeScript code 22 | { test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] }, 23 | 24 | // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI 25 | { test: /\.(png|jpg|gif|webp|svg|zip)$/, loader: [{ loader: 'url-loader' }] }, 26 | ], 27 | }, 28 | 29 | // Webpack tries these extensions for you if you omit the extension like "import './file'" 30 | resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'] }, 31 | 32 | output: { 33 | filename: '[name].js', 34 | path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist" 35 | }, 36 | 37 | watch: true, 38 | 39 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it 40 | plugins: [ 41 | new HtmlWebpackPlugin({ 42 | template: './src/ui.html', 43 | filename: 'ui.html', 44 | inlineSource: '.(js)$', 45 | chunks: ['ui'], 46 | }), 47 | new HtmlWebpackInlineSourcePlugin(), 48 | ], 49 | }); 50 | -------------------------------------------------------------------------------- /variant-switcher/_assets_/plugin-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /variant-switcher/LICENSES.json: -------------------------------------------------------------------------------- 1 | { 2 | "css-loader@3.6.0": { 3 | "licenses": "MIT", 4 | "repository": "https://github.com/webpack-contrib/css-loader", 5 | "licenseUrl": "https://github.com/webpack-contrib/css-loader/raw/master/LICENSE", 6 | "parents": "figma-plugin-variant-switcher" 7 | }, 8 | "html-webpack-inline-source-plugin@0.0.10": { 9 | "licenses": "MIT", 10 | "repository": "https://github.com/dustinjackson/html-webpack-inline-source-plugin", 11 | "licenseUrl": "https://github.com/dustinjackson/html-webpack-inline-source-plugin/raw/master/LICENSE", 12 | "parents": "figma-plugin-variant-switcher" 13 | }, 14 | "html-webpack-plugin@3.2.0": { 15 | "licenses": "MIT", 16 | "repository": "https://github.com/jantimon/html-webpack-plugin", 17 | "licenseUrl": "https://github.com/jantimon/html-webpack-plugin/raw/master/LICENSE", 18 | "parents": "figma-plugin-variant-switcher" 19 | }, 20 | "react-dom@17.0.2": { 21 | "licenses": "MIT", 22 | "repository": "https://github.com/facebook/react", 23 | "licenseUrl": "https://github.com/facebook/react/raw/master/LICENSE", 24 | "parents": "figma-plugin-variant-switcher" 25 | }, 26 | "react@17.0.2": { 27 | "licenses": "MIT", 28 | "repository": "https://github.com/facebook/react", 29 | "licenseUrl": "https://github.com/facebook/react/raw/master/LICENSE", 30 | "parents": "figma-plugin-variant-switcher" 31 | }, 32 | "style-loader@2.0.0": { 33 | "licenses": "MIT", 34 | "repository": "https://github.com/webpack-contrib/style-loader", 35 | "licenseUrl": "https://github.com/webpack-contrib/style-loader/raw/master/LICENSE", 36 | "parents": "figma-plugin-variant-switcher" 37 | }, 38 | "ts-loader@6.2.2": { 39 | "licenses": "MIT", 40 | "repository": "https://github.com/TypeStrong/ts-loader", 41 | "licenseUrl": "https://github.com/TypeStrong/ts-loader/raw/master/LICENSE", 42 | "parents": "figma-plugin-variant-switcher" 43 | }, 44 | "url-loader@4.1.1": { 45 | "licenses": "MIT", 46 | "repository": "https://github.com/webpack-contrib/url-loader", 47 | "licenseUrl": "https://github.com/webpack-contrib/url-loader/raw/master/LICENSE", 48 | "parents": "figma-plugin-variant-switcher" 49 | }, 50 | "webpack-cli@3.3.12": { 51 | "licenses": "MIT", 52 | "repository": "https://github.com/webpack/webpack-cli", 53 | "licenseUrl": "https://github.com/webpack/webpack-cli/raw/master/LICENSE", 54 | "parents": "figma-plugin-variant-switcher" 55 | }, 56 | "webpack@4.46.0": { 57 | "licenses": "MIT", 58 | "repository": "https://github.com/webpack/webpack", 59 | "licenseUrl": "https://github.com/webpack/webpack/raw/master/LICENSE", 60 | "parents": "figma-plugin-variant-switcher" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /variant-switcher/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v9 (Feb 27, 2025) 4 | 5 | ### Added 6 | 7 | - Added Dynamic Loading to help the plugin load faster. ([#97](https://github.com/etn-ccis/blui-figma-plugins/pull/97); thanks, @andrewlevada!) 8 | - Declared in manifest file that no network access is required for the plugin. 9 | 10 | ## v8 (May 8, 2023) 11 | 12 | ### Fixed 13 | 14 | - Fixed an issue that causes the "Plugin Stays Open" checkbox to not work properly. 15 | 16 | ## v7 (May 8, 2023) 17 | 18 | ### Added 19 | 20 | - Support for an advanced option "Plugin Stays Open". ([#46](https://github.com/etn-ccis/blui-figma-plugins/issues/46)) 21 | 22 | ## v6 (Sept 26, 2022) 23 | 24 | ### Added 25 | 26 | - Support for dark theme. ([#35](https://github.com/etn-ccis/blui-figma-plugins/issues/35); thanks, @Jerryjappinen!) 27 | - Support for FigJam. ([#23](https://github.com/etn-ccis/blui-figma-plugins/issues/23)) 28 | 29 | ## v5 (Mar 9, 2022) 30 | 31 | ### Fixed 32 | 33 | - Fixed an issue that caused the plugin to crash when the instance has no properties on it. ([#27](https://github.com/etn-ccis/blui-figma-plugins/issues/27)) 34 | 35 | ### Changed 36 | 37 | - Changed the way this changlog is managed to better match with Figma's versioning convention. 38 | 39 | ## v3, v4 (Feb 23, 2022) 40 | 41 | ### Added 42 | 43 | - Optional field "Main Component Name": Only check for the components with the matching main component name. 44 | - Option to scan and switch the whole document, rather than just the current page / selection. ([#19](https://github.com/etn-ccis/blui-figma-plugins/issues/19)) 45 | - Option to toggle on / off "exact match" to allow users to do a blurred search (case-insensitive, substring search, ignore white spaces). ([#21](https://github.com/etn-ccis/blui-figma-plugins/issues/21)) 46 | 47 | ### Changed 48 | 49 | - Grouped advanced options behind an "Advanced Options" dropdown. 50 | - Allowed white space / no white space around the delimiter `,` between different instance names. ([#16](https://github.com/etn-ccis/blui-figma-plugins/issues/16)) 51 | 52 | ## v2 (June 30, 2021) 53 | 54 | ### Added 55 | 56 | - When an instance is switched to the specified "To Variant", you can now choose to disable the "deep switch". Deep switch will look into all child layers and switch everything that matches the "From Variant". When this is disabled, the children of a switched element will not be switched. This is helpful when you built dark-themed components using light-themed components, and don't want the plugin to "over-switch". ([See a diagram explanation](./_assets_/deep-switch-diagram.png)) 57 | - Ability to switch variants by pressing the "Enter" key. 58 | 59 | ### Changed 60 | 61 | - Added `editorType` to `manifest.json` to limit the plugin to be used on Figma Design only. 62 | 63 | ### Changed 64 | 65 | - When an instance already has the specified "To Variant" property value, no variant switching will be performed. 66 | 67 | ## v1 (May 12, 2021) 68 | 69 | ### Added 70 | 71 | - Initial Release 72 | -------------------------------------------------------------------------------- /variant-switcher/src/ui.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/figma/plugin-samples/blob/master/react/src/ui.css */ 2 | body { 3 | font: 12px sans-serif; 4 | color: var(--figma-color-text); 5 | } 6 | button { 7 | border-radius: 5px; 8 | background: var(--figma-color-bg); 9 | color: var(--figma-color-text); 10 | border: none; 11 | padding: 8px 15px; 12 | box-shadow: inset 0 0 0 1px var(--figma-color-text); 13 | outline: none; 14 | margin-left: 10px; 15 | } 16 | button.primary { 17 | box-shadow: none; 18 | background: var(--figma-color-bg-brand); 19 | color: var(--figma-color-text-onbrand); 20 | } 21 | button.primary:active { 22 | background: var(--figma-color-bg-brand-pressed); 23 | } 24 | button.primary:disabled { 25 | background: var(--figma-color-bg-disabled); 26 | } 27 | button:focus, 28 | #exchange-icon:active { 29 | box-shadow: inset 0 0 0 2px var(--figma-color-border-selected); 30 | } 31 | button.primary:focus { 32 | box-shadow: inset 0 0 0 2px var(--figma-color-border-selected); 33 | } 34 | input:not([type="checkbox"]) { 35 | border: none; 36 | border-radius: 2px; 37 | box-shadow: inset 0 0 0 1px var(--figma-color-border); 38 | outline: none; 39 | padding: 8px; 40 | 41 | font-size: inherit; 42 | color: inherit; 43 | background-color: transparent; 44 | } 45 | /* input:not([type="checkbox"]):hover { 46 | box-shadow: inset 0 0 0 1px var(--figma-color-border); 47 | } */ 48 | input:not([type="checkbox"]):focus { 49 | box-shadow: inset 0 0 0 2px var(--figma-color-border-selected); 50 | } 51 | .input-row, 52 | .button-row, 53 | .checkbox-row { 54 | padding: 8px; 55 | display: flex; 56 | flex-direction: row; 57 | align-items: center; 58 | } 59 | .input-row label { 60 | width: 10em; 61 | margin-right: 1em; 62 | } 63 | .input-row input { 64 | flex: 1; 65 | } 66 | .checkbox-row { 67 | display: flex; 68 | align-items: flex-start; 69 | cursor: default; 70 | } 71 | .checkbox-row > div { 72 | margin-left: 0.75em; 73 | margin-right: 0.5em; 74 | text-align: left; 75 | height: 44px; 76 | } 77 | 78 | .checkbox-row label { 79 | margin-bottom: 2px; 80 | display: inline-block; 81 | } 82 | 83 | .button-row { 84 | justify-content: flex-end; 85 | } 86 | 87 | #exchange-icon-container { 88 | position: absolute; 89 | top: 85px; 90 | left: 101px; 91 | } 92 | 93 | #exchange-icon { 94 | opacity: 0.3; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | border-radius: 2px; 99 | padding: 0; 100 | box-shadow: none; 101 | width: 30px; 102 | height: 30px; 103 | } 104 | 105 | #exchange-icon:hover, 106 | #exchange-icon:focus { 107 | opacity: 1; 108 | background-color: rgba(0, 0, 0, 0.05); 109 | } 110 | 111 | .figma-dark #exchange-icon:hover, 112 | .figma-dark #exchange-icon:focus { 113 | background-color: rgba(255, 255, 255, 0.05); 114 | } 115 | 116 | 117 | .hint-text { 118 | color: var(--figma-color-text-secondary); 119 | } 120 | 121 | #dropdown-ui { 122 | display: flex; 123 | justify-content: flex-start; 124 | align-items: center; 125 | padding: 4px 8px; 126 | cursor: pointer; 127 | } 128 | 129 | #dropdown-ui:hover #dropdown-icon { 130 | opacity: 1; 131 | } 132 | 133 | #dropdown-icon { 134 | opacity: 0.3; 135 | display: inline-block; 136 | margin-left: 4px; 137 | } 138 | #dropdown-icon.flipped { 139 | transform: rotate(180deg); 140 | } 141 | 142 | label.required::after { 143 | content: '*'; 144 | color: var(--figma-color-icon-brand); 145 | margin-left: 0.2em; 146 | } 147 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/code.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Choose to update material icons or brightlayer-ui icons. 3 | * Updating both at the same time would result in name conflict. 4 | */ 5 | const UPDATE_MATERIAL = true; 6 | 7 | const BRIGHTLAYER_UI_META = 'https://raw.githubusercontent.com/etn-ccis/blui-icons/master/svg/index.json'; 8 | 9 | type IconSet = { [name: string]: string[] }; 10 | 11 | // count the icon description status 12 | let updateCount = 0; 13 | const iconsWithoutTags: string[] = []; 14 | const iconsNotInMeta: string[] = []; 15 | 16 | if (!figma.currentPage.selection.length) { 17 | figma.notify(`Select the icons before you run the plugin.`); 18 | figma.closePlugin(); 19 | } else if (figma.currentPage.selection[0].type !== 'COMPONENT') { 20 | figma.notify(`Do not select anything else other than the icons.`); 21 | figma.closePlugin(); 22 | } else { 23 | // create a fake UI and send the network request 24 | figma.showUI(__html__, { visible: false }); 25 | if (UPDATE_MATERIAL) { 26 | // couldn't find a proper way to import the json 27 | // @ts-ignore 28 | const iconSet = matIconSet; 29 | updateIcons(iconSet); 30 | } else { 31 | figma.ui.postMessage({ url: BRIGHTLAYER_UI_META }); 32 | } 33 | } 34 | 35 | function addDescriptionToIconNode(node, icons: IconSet): void { 36 | if (node && node.type === 'COMPONENT') { 37 | // find a node, let's see if it is an icon 38 | if (icons[node.name] !== undefined) { 39 | // bingo, it is a recognized icon. 40 | 41 | // the tags are "[]" in the meta file 42 | if (icons[node.name].length === 0) { 43 | iconsWithoutTags.push(node.name); 44 | } else { 45 | // there are some tags in the meta file, add them to the description 46 | const newDescription = icons[node.name].join(', '); 47 | 48 | if (newDescription !== node.description) { 49 | node.description = newDescription; 50 | updateCount++; 51 | } 52 | // remove it from the set to speed up the search, and also to avoid fights between dup names in brightlayer-ui and mat icons 53 | icons[node.name] = undefined; 54 | } 55 | } else { 56 | // user selected something whose name cannot be recognized by the plugin 57 | iconsNotInMeta.push(node.name); 58 | } 59 | } 60 | } 61 | 62 | function updateIcons(iconSet: IconSet) { 63 | if (figma.currentPage.selection.length) { 64 | for (const node of figma.currentPage.selection) { 65 | addDescriptionToIconNode(node, iconSet); 66 | } 67 | } 68 | 69 | // snackbar feedback 70 | if (updateCount === 0) { 71 | figma.notify(`😕 I couldn't find any icons to update.`); 72 | } else if (updateCount === 1) { 73 | figma.notify(`Changed 1 icon's description.`); 74 | } else { 75 | figma.notify(`Changed ${updateCount} icons' description.`); 76 | } 77 | 78 | if (iconsWithoutTags.length !== 0) { 79 | console.log( 80 | "These icons showed up in meta files, but their 'tags' field are empty, and therefore the plugin didn't touch anything to them:" 81 | ); 82 | console.log(iconsWithoutTags); 83 | } 84 | if (iconsNotInMeta.length !== 0) { 85 | console.log( 86 | `[!!] These icons exist in your selection, but are not in the meta files, and therefore the plugin didn't touch anything to them:` 87 | ); 88 | console.log(iconsNotInMeta); 89 | } 90 | figma.closePlugin(); 91 | } 92 | 93 | figma.ui.onmessage = (msg: IconSet) => { 94 | updateIcons(msg); 95 | }; 96 | -------------------------------------------------------------------------------- /variant-switcher/README.md: -------------------------------------------------------------------------------- 1 | # Variant Switcher 2 | 3 | ![Banner](./_assets_/cover-art.svg) 4 | 5 | The Variant Switcher plugin takes all of your selected component instances and recursively changes them to a different variant based on the specified property. 6 | 7 | [Install it for your Figma workspace](https://www.figma.com/community/plugin/971482182464094790/Variant-Switcher) 8 | 9 | ## Usage 10 | 11 | The Variant Switcher plugin has several user controls: 12 | 13 | | Input Field | Description | Required? | 14 | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | 15 | | Property Name | the property you want to change | yes | 16 | | From Variant | the current value you want to target (leave blank to select all instances with the specified property name regardless of the current value) | no | 17 | | To Variant | the new value you want to set the property to | yes | 18 | | Deep Switch | When unchecked, the plugin will not switch children after switching their parent instances. ([Diagram explanation](./_assets_/deep-switch-diagram.png)) | (yes) | 19 | | Switch Full Document | When checked, the plugin will traverse through the entire document. When unchecked, the plugin will only switch the current selection, or the current page if nothing is selected. | (yes) | 20 | | Exact Match | Whether to check for the exact property name and variant name or do a fuzzy search instead. | (yes) | 21 | | Plugin Stays Open | Whether the plugin will stay open after clicking "Switch Variants" | (no) | 22 | | Main Component Name | Change only instances with the specified main component name | no | 23 | 24 | ## Example 25 | 26 | ![Example](./_assets_/example.png) 27 | 28 | Consider the example above. In this case, all of the components have a `Theme` property (all of them have a "Light" and "Dark" variant and the Star has an additional "Blue" variant). When using the plugin, we set the `Property Name` field to "Theme", `From Variant` to "Light", and `To Variant` to "Dark". The plugin traverses through all selected nodes finding any instances whose `Theme` properties are set to "Light", and changes them to "Dark". 29 | 30 | > Notice that the Star component was unchanged because the current value of its `Theme` property was "Blue", not "Light". If we had left the "From Variant" field blank, then the Star would have also been changed because the plugin would select all nodes with the `Theme` property regardless of the current value. 31 | 32 | ## Running Plugin Locally (For Developers) 33 | 34 | To run the plugin locally, first clone the repository: 35 | 36 | ```sh 37 | git clone https://github.com/etn-ccis/blui-figma-plugins 38 | ``` 39 | 40 | Then, link the plugin to Figma: 41 | 42 | - Open the Figma desktop app and in the toolbar go to `Plugins > Development > Import Plugin from Manifest...`. 43 | - Select the `manifest.json` file from the repo you just cloned. 44 | 45 | Finally, build the plugin: 46 | 47 | ```sh 48 | cd path/to/figma-plugins/variant-switcher 49 | yarn && yarn build 50 | ``` 51 | 52 | The plugin should now be running happily. 53 | 54 | > To build in production mode, run `yarn && yarn build:production`. See results in `/dist` folder. 55 | 56 | Now you can run the dev version via `Plugins > Development > Variant Switcher`. 57 | -------------------------------------------------------------------------------- /variant-switcher/src/code.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2021-present, Eaton 3 | All rights reserved. 4 | This code is licensed under the BSD-3 license found in the LICENSE file in the root directory of this source tree and at https://opensource.org/licenses/BSD-3-Clause. 5 | **/ 6 | 7 | import { KEYS } from './shared'; 8 | import { fuzzyMatch } from './utils'; 9 | 10 | const DELIMITER = ','; 11 | const EXPANDED_HEIGHT = 460; 12 | const COLLAPSED_HEIGHT = 224; 13 | const DEFAULT_WIDTH = 300; 14 | 15 | figma.showUI(__html__, { themeColors: true, visible: false, width: DEFAULT_WIDTH, height: COLLAPSED_HEIGHT }); 16 | 17 | figma.clientStorage.getAsync(KEYS.PROPERTY_NAME).then((val) => { 18 | if (val) figma.ui.postMessage({ param: KEYS.PROPERTY_NAME, val }); 19 | }); 20 | figma.clientStorage.getAsync(KEYS.FROM_VARIANT).then((val) => { 21 | if (val !== undefined) figma.ui.postMessage({ param: KEYS.FROM_VARIANT, val }); 22 | }); 23 | figma.clientStorage.getAsync(KEYS.TO_VARIANT).then((val) => { 24 | if (val) figma.ui.postMessage({ param: KEYS.TO_VARIANT, val }); 25 | }); 26 | figma.clientStorage.getAsync(KEYS.DEEP_SWITCH).then((val) => { 27 | if (val) figma.ui.postMessage({ param: KEYS.DEEP_SWITCH, val }); 28 | }); 29 | figma.clientStorage.getAsync(KEYS.FULL_DOCUMENT).then((val) => { 30 | if (val) figma.ui.postMessage({ param: KEYS.FULL_DOCUMENT, val }); 31 | }); 32 | figma.clientStorage.getAsync(KEYS.EXACT_MATCH).then((val) => { 33 | if (val) figma.ui.postMessage({ param: KEYS.EXACT_MATCH, val }); 34 | }); 35 | figma.clientStorage.getAsync(KEYS.PLUGIN_STAY_OPEN).then((val) => { 36 | if (val) figma.ui.postMessage({ param: KEYS.PLUGIN_STAY_OPEN, val }); 37 | }); 38 | figma.clientStorage.getAsync(KEYS.MAIN_COMPONENT_NAME).then((val) => { 39 | if (val) figma.ui.postMessage({ param: KEYS.MAIN_COMPONENT_NAME, val }); 40 | }); 41 | figma.clientStorage.getAsync(KEYS.SHOW_ADVANCED_OPTIONS).then((val) => { 42 | if (val !== undefined) { 43 | figma.ui.postMessage({ param: KEYS.SHOW_ADVANCED_OPTIONS, val }); 44 | if (val) figma.ui.resize(DEFAULT_WIDTH, EXPANDED_HEIGHT); 45 | } 46 | }); 47 | 48 | let switchCount = 0; 49 | 50 | // show the UI when we finish initializing clientStorage 51 | setTimeout(() => { 52 | figma.ui.show(); 53 | }, 250); 54 | 55 | // to account for white space around the delimiter in figma 56 | function trimPropertyWhiteSpace(str: string): string[] { 57 | return str.split(DELIMITER).map((prop) => prop.trim()); 58 | } 59 | 60 | async function traverse( 61 | node: any, 62 | propertyName: string, 63 | fromVariant: string, 64 | toVariant: string, 65 | deepSwitch: boolean, 66 | exactMatch: boolean, 67 | mainComponentName: string 68 | ) { 69 | // find an instance 70 | // the instance need to come from some kind of component set (i.e., has a parent) 71 | 72 | let parentSwapped = false; 73 | 74 | if (node && node.type == 'INSTANCE' && node.variantProperties) { 75 | const mainComponent = await node.getMainComponentAsync(); 76 | if (!mainComponent || !mainComponent.parent) return; 77 | 78 | let nodeProperties = trimPropertyWhiteSpace(mainComponent.name); 79 | 80 | // the instance comes from a component with variances set in them 81 | // and there is the variant we are looking for 82 | 83 | let propertyIndex = -1; 84 | if (fromVariant !== '') { 85 | if (exactMatch) { 86 | propertyIndex = nodeProperties.indexOf(`${propertyName}=${fromVariant}`); 87 | } else { 88 | propertyIndex = nodeProperties.findIndex((property: string): boolean => 89 | fuzzyMatch(propertyName, fromVariant, property) 90 | ); 91 | } 92 | } else { 93 | // the user didn't provide any fromVariant 94 | // changing all instances with the "propertyName" property to "toVariant" 95 | if (exactMatch) { 96 | propertyIndex = nodeProperties.findIndex((property: string): boolean => 97 | property.startsWith(`${propertyName}=`) 98 | ); 99 | } else { 100 | propertyIndex = nodeProperties.findIndex((property: string): boolean => 101 | fuzzyMatch(propertyName, '', property) 102 | ); 103 | } 104 | } 105 | 106 | // do not swap if somehow the instance is already on the "toVariant" 107 | let isOnToVariant = false; 108 | if (exactMatch) { 109 | isOnToVariant = node.variantProperties[propertyName] === toVariant; 110 | } else { 111 | isOnToVariant = 112 | nodeProperties.findIndex((property: string): boolean => 113 | fuzzyMatch(propertyName, toVariant, property) 114 | ) !== -1; 115 | } 116 | 117 | // do the swapping 118 | if ( 119 | (mainComponentName !== '' && mainComponentName === mainComponent.parent.name) || 120 | mainComponentName === '' 121 | ) { 122 | if (mainComponent.parent.type === 'COMPONENT_SET' && propertyIndex !== -1 && !isOnToVariant) { 123 | let changeToSibling: ComponentNode; 124 | if (exactMatch) { 125 | nodeProperties[propertyIndex] = `${propertyName}=${toVariant}`; 126 | changeToSibling = mainComponent.parent.findChild( 127 | (sibling: ComponentNode) => 128 | trimPropertyWhiteSpace(sibling.name).join(DELIMITER) === nodeProperties.join(DELIMITER) 129 | ); 130 | } else { 131 | const nodePropertiesJSON = { ...node.variantProperties }; 132 | changeToSibling = mainComponent.parent.findChild((sibling: ComponentNode): boolean => { 133 | const siblingNodeProperties = trimPropertyWhiteSpace(sibling.name); 134 | if (fuzzyMatch(propertyName, toVariant, siblingNodeProperties[propertyIndex])) { 135 | const propertyToChange = siblingNodeProperties[propertyIndex].split('=')[0].trim(); 136 | nodePropertiesJSON[propertyToChange] = sibling.variantProperties[propertyToChange]; 137 | return JSON.stringify(nodePropertiesJSON) === JSON.stringify(sibling.variantProperties); 138 | } else { 139 | return false; 140 | } 141 | }); 142 | } 143 | // we found a sibling with the property swapped out 144 | if (changeToSibling) { 145 | node.swapComponent(changeToSibling); 146 | parentSwapped = true; 147 | switchCount++; 148 | } 149 | // we couldn't find a good sibling with the exact property, 150 | // but try again to find at least one with the property we care about 151 | else { 152 | changeToSibling = mainComponent.parent.findChild( 153 | (sibling: ComponentNode) => sibling.variantProperties[propertyName] === toVariant 154 | ); 155 | 156 | if (changeToSibling) { 157 | node.swapComponent(changeToSibling); 158 | parentSwapped = true; 159 | switchCount++; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | // now that we are done swapping, look to see if any child component is swappable 166 | // if deepSwitch is checked and parent is swapped, we don't want to look further in this layer tree 167 | if ('children' in node && (deepSwitch || !parentSwapped)) { 168 | for (const child of node.children) { 169 | await traverse(child, propertyName, fromVariant, toVariant, deepSwitch, exactMatch, mainComponentName); 170 | } 171 | } 172 | } 173 | 174 | figma.ui.onmessage = async (msg) => { 175 | if (msg.action === 'submit') { 176 | // remember these params and save to client storage 177 | figma.clientStorage.setAsync(KEYS.PROPERTY_NAME, msg.propertyName); 178 | figma.clientStorage.setAsync(KEYS.FROM_VARIANT, msg.fromVariant); 179 | figma.clientStorage.setAsync(KEYS.TO_VARIANT, msg.toVariant); 180 | figma.clientStorage.setAsync(KEYS.DEEP_SWITCH, msg.deepSwitch); 181 | figma.clientStorage.setAsync(KEYS.FULL_DOCUMENT, msg.fullDocument); 182 | figma.clientStorage.setAsync(KEYS.EXACT_MATCH, msg.exactMatch); 183 | figma.clientStorage.setAsync(KEYS.PLUGIN_STAY_OPEN, msg.pluginStayOpen); 184 | figma.clientStorage.setAsync(KEYS.MAIN_COMPONENT_NAME, msg.mainComponentName); 185 | figma.clientStorage.setAsync(KEYS.SHOW_ADVANCED_OPTIONS, msg.showAdvancedOptions); 186 | 187 | // the user want to switch the whole document 188 | if (msg.fullDocument === 'true') { 189 | await figma.loadAllPagesAsync(); 190 | for (const pageNode of figma.root.children) { 191 | await traverse( 192 | pageNode, 193 | msg.propertyName, 194 | msg.fromVariant, 195 | msg.toVariant, 196 | msg.deepSwitch === 'true', 197 | msg.exactMatch === 'true', 198 | msg.mainComponentName 199 | ); 200 | } 201 | } 202 | // the user selected something, then we look at the selection 203 | else if (figma.currentPage.selection.length) { 204 | for (const node of figma.currentPage.selection) { 205 | await traverse( 206 | node, 207 | msg.propertyName, 208 | msg.fromVariant, 209 | msg.toVariant, 210 | msg.deepSwitch === 'true', 211 | msg.exactMatch === 'true', 212 | msg.mainComponentName 213 | ); 214 | } 215 | } 216 | // the user didn't select anything, then let's change the entire page 217 | else { 218 | await figma.currentPage.loadAsync(); 219 | await traverse( 220 | figma.currentPage, 221 | msg.propertyName, 222 | msg.fromVariant, 223 | msg.toVariant, 224 | msg.deepSwitch === 'true', 225 | msg.exactMatch === 'true', 226 | msg.mainComponentName 227 | ); 228 | } 229 | 230 | // display snackbar message with ellipsis, so that it looks less awkward when exact match is off 231 | const notifyPropertyName = msg.exactMatch === 'true' ? msg.propertyName : `...${msg.propertyName}...`; 232 | const notifyToVariant = msg.exactMatch === 'true' ? msg.toVariant : `...${msg.toVariant}...`; 233 | 234 | // snackbar feedback 235 | if (switchCount === 0) { 236 | if (msg.mainComponentName !== '') { 237 | figma.notify( 238 | `😕 Variant Switcher couldn't find any "${msg.mainComponentName}" to switch to "${notifyPropertyName}=${notifyToVariant}".` 239 | ); 240 | } else { 241 | figma.notify( 242 | `😕 Variant Switcher couldn't find anything to switch to "${notifyPropertyName}=${notifyToVariant}".` 243 | ); 244 | } 245 | } else if (switchCount === 1) { 246 | figma.notify(`Changed 1 instance's "${notifyPropertyName}" to "${notifyToVariant}".`); 247 | } else { 248 | figma.notify(`Changed ${switchCount} instances' "${notifyPropertyName}" to "${notifyToVariant}".`); 249 | } 250 | 251 | if (msg.pluginStayOpen === 'false') { 252 | figma.closePlugin(); 253 | } 254 | 255 | switchCount = 0; 256 | } else if (msg.action === 'resize') { 257 | if (msg.showAdvancedOptions) { 258 | figma.ui.resize(DEFAULT_WIDTH, EXPANDED_HEIGHT); 259 | } else { 260 | figma.ui.resize(DEFAULT_WIDTH, COLLAPSED_HEIGHT); 261 | } 262 | } 263 | }; 264 | -------------------------------------------------------------------------------- /variant-switcher/src/ui.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2021-present, Eaton 3 | All rights reserved. 4 | This code is licensed under the BSD-3 license found in the LICENSE file in the root directory of this source tree and at https://opensource.org/licenses/BSD-3-Clause. 5 | **/ 6 | import * as React from 'react'; 7 | import * as ReactDOM from 'react-dom'; 8 | import { KEYS } from './shared'; 9 | import { Exchange, Dropdown } from './icons/index'; 10 | import './ui.css'; 11 | 12 | declare function require(path: string): any; 13 | 14 | const LOCAL_STORAGE_DATA = {}; 15 | onmessage = (event) => { 16 | LOCAL_STORAGE_DATA[event.data.pluginMessage.param] = event.data.pluginMessage.val; 17 | }; 18 | 19 | const App: React.FC = () => { 20 | // the states set here will be the default state a first time plugin user gets 21 | // basic options 22 | const [propertyName, setPropertyName] = React.useState('Theme'); 23 | const [fromVariant, setFromVariant] = React.useState('Light'); 24 | const [toVariant, setToVariant] = React.useState('Dark'); 25 | 26 | // advanced options 27 | const [deepSwitch, setDeepSwitch] = React.useState<'true' | 'false'>('false'); 28 | const [fullDocument, setFullDocument] = React.useState<'true' | 'false'>('false'); 29 | const [exactMatch, setExactMatch] = React.useState<'true' | 'false'>('true'); 30 | const [pluginStayOpen, setPluginStayOpen] = React.useState<'true' | 'false'>('false'); 31 | const [mainComponentName, setMainComponentName] = React.useState(''); 32 | 33 | const [showAdvancedOptions, setShowAdvancedOptions] = React.useState(false); 34 | 35 | // load stored parameters from history 36 | // use a timer to help picking up the async update from onmessage 37 | React.useEffect(() => { 38 | const timerPropertyName = setInterval(() => { 39 | if (LOCAL_STORAGE_DATA[KEYS.PROPERTY_NAME]) { 40 | setPropertyName(LOCAL_STORAGE_DATA[KEYS.PROPERTY_NAME]); 41 | clearInterval(timerPropertyName); 42 | } 43 | }, 50); 44 | const timerFromVariant = setInterval(() => { 45 | if (LOCAL_STORAGE_DATA[KEYS.FROM_VARIANT] !== undefined) { 46 | setFromVariant(LOCAL_STORAGE_DATA[KEYS.FROM_VARIANT]); 47 | clearInterval(timerFromVariant); 48 | } 49 | }, 50); 50 | const timerToVariant = setInterval(() => { 51 | if (LOCAL_STORAGE_DATA[KEYS.TO_VARIANT]) { 52 | setToVariant(LOCAL_STORAGE_DATA[KEYS.TO_VARIANT]); 53 | clearInterval(timerToVariant); 54 | } 55 | }, 50); 56 | const timerDeepSwitch = setInterval(() => { 57 | if (LOCAL_STORAGE_DATA[KEYS.DEEP_SWITCH]) { 58 | setDeepSwitch(LOCAL_STORAGE_DATA[KEYS.DEEP_SWITCH]); 59 | clearInterval(timerDeepSwitch); 60 | } 61 | }, 50); 62 | const timerFullDocument = setInterval(() => { 63 | if (LOCAL_STORAGE_DATA[KEYS.FULL_DOCUMENT]) { 64 | setFullDocument(LOCAL_STORAGE_DATA[KEYS.FULL_DOCUMENT]); 65 | clearInterval(timerFullDocument); 66 | } 67 | }, 50); 68 | const timerExactMatch = setInterval(() => { 69 | if (LOCAL_STORAGE_DATA[KEYS.EXACT_MATCH]) { 70 | setExactMatch(LOCAL_STORAGE_DATA[KEYS.EXACT_MATCH]); 71 | clearInterval(timerExactMatch); 72 | } 73 | }, 50); 74 | const timerPluginStayOpen = setInterval(() => { 75 | if (LOCAL_STORAGE_DATA[KEYS.PLUGIN_STAY_OPEN]) { 76 | setExactMatch(LOCAL_STORAGE_DATA[KEYS.PLUGIN_STAY_OPEN]); 77 | clearInterval(timerPluginStayOpen); 78 | } 79 | }, 50); 80 | const timerMainComponentName = setInterval(() => { 81 | if (LOCAL_STORAGE_DATA[KEYS.MAIN_COMPONENT_NAME]) { 82 | setMainComponentName(LOCAL_STORAGE_DATA[KEYS.MAIN_COMPONENT_NAME]); 83 | clearInterval(timerMainComponentName); 84 | } 85 | }, 50); 86 | const timerShowAdvancedOptions = setInterval(() => { 87 | if (LOCAL_STORAGE_DATA[KEYS.SHOW_ADVANCED_OPTIONS]) { 88 | setShowAdvancedOptions(LOCAL_STORAGE_DATA[KEYS.SHOW_ADVANCED_OPTIONS]); 89 | clearInterval(timerShowAdvancedOptions); 90 | } 91 | }, 50); 92 | return () => { 93 | clearInterval(timerPropertyName); 94 | clearInterval(timerFromVariant); 95 | clearInterval(timerToVariant); 96 | clearInterval(timerDeepSwitch); 97 | clearInterval(timerFullDocument); 98 | clearInterval(timerExactMatch); 99 | clearInterval(timerPluginStayOpen); 100 | clearInterval(timerMainComponentName); 101 | clearInterval(timerShowAdvancedOptions); 102 | }; 103 | }, []); 104 | 105 | // submit iff input fields are valid 106 | const submit = React.useCallback(() => { 107 | if (propertyName !== '' && toVariant !== '') { 108 | parent.postMessage( 109 | { 110 | pluginMessage: { 111 | action: 'submit', 112 | propertyName, 113 | fromVariant, 114 | toVariant, 115 | deepSwitch, 116 | fullDocument, 117 | exactMatch, 118 | pluginStayOpen, 119 | mainComponentName, 120 | showAdvancedOptions, 121 | }, 122 | }, 123 | '*' 124 | ); 125 | } 126 | }, [ 127 | propertyName, 128 | fromVariant, 129 | toVariant, 130 | deepSwitch, 131 | fullDocument, 132 | exactMatch, 133 | pluginStayOpen, 134 | mainComponentName, 135 | showAdvancedOptions, 136 | ]); 137 | 138 | return ( 139 |
140 |
141 | 142 | setPropertyName(e.target.value)} 144 | name={'Name of the property shared by instances'} 145 | value={propertyName} 146 | onKeyDown={(e) => { 147 | if (e.code === 'Enter') { 148 | submit(); 149 | } 150 | }} 151 | /> 152 |
153 |
154 | {' '} 155 | setFromVariant(e.target.value)} 157 | name={'The variant name to change from'} 158 | value={fromVariant} 159 | onKeyDown={(e) => { 160 | if (e.code === 'Enter') { 161 | submit(); 162 | } 163 | }} 164 | /> 165 |
166 |
167 | 168 | setToVariant(e.target.value)} 170 | name={'The variant name to change into'} 171 | value={toVariant} 172 | onKeyDown={(e) => { 173 | if (e.code === 'Enter') { 174 | submit(); 175 | } 176 | }} 177 | /> 178 |
179 |
{ 182 | setShowAdvancedOptions(!showAdvancedOptions); 183 | parent.postMessage( 184 | { 185 | pluginMessage: { 186 | action: 'resize', 187 | showAdvancedOptions: !showAdvancedOptions, 188 | }, 189 | }, 190 | '*' 191 | ); 192 | }} 193 | > 194 | Advanced options 195 |
196 | 197 |
198 |
199 | {showAdvancedOptions && ( 200 | <> 201 |
202 | { 205 | setDeepSwitch(e.target.value === 'true' ? 'false' : 'true'); 206 | }} 207 | name={'swapChild'} 208 | value={deepSwitch} 209 | checked={deepSwitch === 'true'} 210 | /> 211 |
{ 213 | setDeepSwitch(deepSwitch === 'true' ? 'false' : 'true'); 214 | }} 215 | > 216 | 222 |
223 | {deepSwitch === 'true' ? ( 224 | Plugin will switch all instances in the selected tree 225 | ) : ( 226 | Plugin will not switch children after switching parent instance 227 | )} 228 |
229 |
230 |
231 |
232 | { 235 | setFullDocument(e.target.value === 'true' ? 'false' : 'true'); 236 | }} 237 | name={'fullDocumentSwitch'} 238 | value={fullDocument} 239 | checked={fullDocument === 'true'} 240 | /> 241 |
{ 243 | setFullDocument(fullDocument === 'true' ? 'false' : 'true'); 244 | }} 245 | > 246 | 249 |
250 | {fullDocument === 'true' ? ( 251 | Plugin will switch the entire document 252 | ) : ( 253 | Plugin will only switch the current selection or the current page 254 | )} 255 |
256 |
257 |
258 |
259 | { 262 | setExactMatch(e.target.value === 'true' ? 'false' : 'true'); 263 | }} 264 | name={'exactMatch'} 265 | value={exactMatch} 266 | checked={exactMatch === 'true'} 267 | /> 268 |
{ 270 | setExactMatch(exactMatch === 'true' ? 'false' : 'true'); 271 | }} 272 | style={{ height: 'unset' }} 273 | > 274 | 277 |
278 |
279 |
280 | { 283 | setPluginStayOpen(e.target.value === 'true' ? 'false' : 'true'); 284 | }} 285 | name={'pluginStayOpen'} 286 | value={pluginStayOpen} 287 | checked={pluginStayOpen === 'true'} 288 | /> 289 |
{ 291 | setPluginStayOpen(pluginStayOpen === 'true' ? 'false' : 'true'); 292 | }} 293 | style={{ height: 'unset' }} 294 | > 295 | 301 |
302 |
303 |
304 | 305 | setMainComponentName(e.target.value)} 307 | name={'Main component name'} 308 | value={mainComponentName} 309 | onKeyDown={(e) => { 310 | if (e.code === 'Enter') { 311 | submit(); 312 | } 313 | }} 314 | /> 315 |
316 | 317 | )} 318 | 319 |
320 | 330 |
331 | 332 |
333 | 336 |
337 |
338 | ); 339 | }; 340 | 341 | ReactDOM.render(, document.getElementById('react-page')); 342 | -------------------------------------------------------------------------------- /_maintenance_/icon-description/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/figma@^1.0.3": 6 | version "1.0.3" 7 | resolved "https://registry.yarnpkg.com/@types/figma/-/figma-1.0.3.tgz#961a613c4de0627d520a16b7f82fe9627481e27f" 8 | integrity sha512-2jDGdTYyLabbRxAKLceNS7mfo06bwknvI27CGLbl7UKobHmIZ+HW/1dWEsLE0SC4yPf3/sL1l5atw3YCUXhJHQ== 9 | 10 | abbrev@1: 11 | version "1.1.1" 12 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 13 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 14 | 15 | ansi-regex@^0.2.0, ansi-regex@^0.2.1: 16 | version "0.2.1" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" 18 | integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= 19 | 20 | ansi-styles@^1.1.0: 21 | version "1.1.0" 22 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" 23 | integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= 24 | 25 | ansi-styles@^3.2.1: 26 | version "3.2.1" 27 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 28 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 29 | dependencies: 30 | color-convert "^1.9.0" 31 | 32 | asap@^2.0.0: 33 | version "2.0.6" 34 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 35 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 36 | 37 | async@^2.6.1: 38 | version "2.6.4" 39 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" 40 | integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== 41 | dependencies: 42 | lodash "^4.17.14" 43 | 44 | balanced-match@^1.0.0: 45 | version "1.0.2" 46 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 47 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 48 | 49 | brace-expansion@^1.1.7: 50 | version "1.1.12" 51 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" 52 | integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== 53 | dependencies: 54 | balanced-match "^1.0.0" 55 | concat-map "0.0.1" 56 | 57 | chalk@^2.4.2: 58 | version "2.4.2" 59 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 60 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 61 | dependencies: 62 | ansi-styles "^3.2.1" 63 | escape-string-regexp "^1.0.5" 64 | supports-color "^5.3.0" 65 | 66 | chalk@~0.5.1: 67 | version "0.5.1" 68 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" 69 | integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= 70 | dependencies: 71 | ansi-styles "^1.1.0" 72 | escape-string-regexp "^1.0.0" 73 | has-ansi "^0.1.0" 74 | strip-ansi "^0.3.0" 75 | supports-color "^0.2.0" 76 | 77 | color-convert@^1.9.0: 78 | version "1.9.3" 79 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 80 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 81 | dependencies: 82 | color-name "1.1.3" 83 | 84 | color-name@1.1.3: 85 | version "1.1.3" 86 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 87 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 88 | 89 | concat-map@0.0.1: 90 | version "0.0.1" 91 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 92 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 93 | 94 | debuglog@^1.0.1: 95 | version "1.0.1" 96 | resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" 97 | integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= 98 | 99 | dezalgo@^1.0.0: 100 | version "1.0.3" 101 | resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" 102 | integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= 103 | dependencies: 104 | asap "^2.0.0" 105 | wrappy "1" 106 | 107 | escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.5: 108 | version "1.0.5" 109 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 110 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 111 | 112 | github-url-from-git@^1.3.0: 113 | version "1.5.0" 114 | resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.5.0.tgz#f985fedcc0a9aa579dc88d7aff068d55cc6251a0" 115 | integrity sha1-+YX+3MCpqledyI16/waNVcxiUaA= 116 | 117 | github-url-from-username-repo@^1.0.0: 118 | version "1.0.2" 119 | resolved "https://registry.yarnpkg.com/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz#7dd79330d2abe69c10c2cef79714c97215791dfa" 120 | integrity sha1-fdeTMNKr5pwQws73lxTJchV5Hfo= 121 | 122 | glob@^5.0.3: 123 | version "5.0.15" 124 | resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" 125 | integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= 126 | dependencies: 127 | inflight "^1.0.4" 128 | inherits "2" 129 | minimatch "2 || 3" 130 | once "^1.3.0" 131 | path-is-absolute "^1.0.0" 132 | 133 | "graceful-fs@2 || 3": 134 | version "3.0.12" 135 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.12.tgz#0034947ce9ed695ec8ab0b854bc919e82b1ffaef" 136 | integrity sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg== 137 | dependencies: 138 | natives "^1.1.3" 139 | 140 | graceful-fs@^4.1.2: 141 | version "4.2.9" 142 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" 143 | integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== 144 | 145 | has-ansi@^0.1.0: 146 | version "0.1.0" 147 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" 148 | integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= 149 | dependencies: 150 | ansi-regex "^0.2.0" 151 | 152 | has-flag@^3.0.0: 153 | version "3.0.0" 154 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 155 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 156 | 157 | inflight@^1.0.4: 158 | version "1.0.6" 159 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 160 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 161 | dependencies: 162 | once "^1.3.0" 163 | wrappy "1" 164 | 165 | inherits@2: 166 | version "2.0.4" 167 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 168 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 169 | 170 | jju@^1.1.0: 171 | version "1.4.0" 172 | resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" 173 | integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= 174 | 175 | jquery-extend@~2.0.3: 176 | version "2.0.3" 177 | resolved "https://registry.yarnpkg.com/jquery-extend/-/jquery-extend-2.0.3.tgz#6815cdb01a866ddba30e6f4d0fc5fb6679272735" 178 | integrity sha1-aBXNsBqGbdujDm9ND8X7ZnknJzU= 179 | 180 | json-parse-helpfulerror@^1.0.2: 181 | version "1.0.3" 182 | resolved "https://registry.yarnpkg.com/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz#13f14ce02eed4e981297b64eb9e3b932e2dd13dc" 183 | integrity sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w= 184 | dependencies: 185 | jju "^1.1.0" 186 | 187 | "license-checker@git+https://github.com/mwittig/license-checker#d546e3f738e14c62e732346fa355162d46700893": 188 | version "1.0.0" 189 | uid d546e3f738e14c62e732346fa355162d46700893 190 | resolved "git+https://github.com/mwittig/license-checker#d546e3f738e14c62e732346fa355162d46700893" 191 | dependencies: 192 | chalk "~0.5.1" 193 | mkdirp "^0.3.5" 194 | nopt "^2.2.0" 195 | read-installed "~3.1.3" 196 | treeify "^1.0.1" 197 | 198 | lodash@^4.17.14: 199 | version "4.17.21" 200 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 201 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 202 | 203 | "minimatch@2 || 3": 204 | version "3.1.2" 205 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 206 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 207 | dependencies: 208 | brace-expansion "^1.1.7" 209 | 210 | minimist@^1.2.5: 211 | version "1.2.6" 212 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 213 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 214 | 215 | mkdirp@^0.3.5: 216 | version "0.3.5" 217 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" 218 | integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= 219 | 220 | mkdirp@^0.5.1: 221 | version "0.5.5" 222 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 223 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 224 | dependencies: 225 | minimist "^1.2.5" 226 | 227 | natives@^1.1.3: 228 | version "1.1.6" 229 | resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" 230 | integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== 231 | 232 | node-fetch@^2.6.7: 233 | version "2.6.7" 234 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 235 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 236 | dependencies: 237 | whatwg-url "^5.0.0" 238 | 239 | nopt-defaults@^0.0.1: 240 | version "0.0.1" 241 | resolved "https://registry.yarnpkg.com/nopt-defaults/-/nopt-defaults-0.0.1.tgz#f150fcc8882309cbfb76187e12e9bcb20694558b" 242 | integrity sha1-8VD8yIgjCcv7dhh+Eum8sgaUVYs= 243 | 244 | nopt-usage@^0.1.0: 245 | version "0.1.0" 246 | resolved "https://registry.yarnpkg.com/nopt-usage/-/nopt-usage-0.1.0.tgz#b18b8c183e181047ca9e63b7cde7cfc702cca579" 247 | integrity sha1-sYuMGD4YEEfKnmO3zefPxwLMpXk= 248 | 249 | nopt@^2.2.0: 250 | version "2.2.1" 251 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-2.2.1.tgz#2aa09b7d1768487b3b89a9c5aa52335bff0baea7" 252 | integrity sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc= 253 | dependencies: 254 | abbrev "1" 255 | 256 | nopt@^3.0.6: 257 | version "3.0.6" 258 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 259 | integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= 260 | dependencies: 261 | abbrev "1" 262 | 263 | normalize-package-data@^1.0.0: 264 | version "1.0.3" 265 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-1.0.3.tgz#8be955b8907af975f1a4584ea8bb9b41492312f5" 266 | integrity sha1-i+lVuJB6+XXxpFhOqLubQUkjEvU= 267 | dependencies: 268 | github-url-from-git "^1.3.0" 269 | github-url-from-username-repo "^1.0.0" 270 | semver "2 || 3 || 4" 271 | 272 | npm-license-crawler@^0.2.1: 273 | version "0.2.1" 274 | resolved "https://registry.yarnpkg.com/npm-license-crawler/-/npm-license-crawler-0.2.1.tgz#a76a82e0a0407e2032c03dc5b1d518cf9eac9e1d" 275 | integrity sha512-CRchloUjZk/ZSAkb5JbCKNFojLWtbjxwsB7w48kauHXK+5bjby2HXFvGvicVx7uNBY6HBWEPw20qKc/4jlL+1Q== 276 | dependencies: 277 | async "^2.6.1" 278 | chalk "^2.4.2" 279 | jquery-extend "~2.0.3" 280 | license-checker "git+https://github.com/mwittig/license-checker#d546e3f738e14c62e732346fa355162d46700893" 281 | mkdirp "^0.5.1" 282 | nopt "^3.0.6" 283 | nopt-defaults "^0.0.1" 284 | nopt-usage "^0.1.0" 285 | treeify "^1.1.0" 286 | 287 | once@^1.3.0: 288 | version "1.4.0" 289 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 290 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 291 | dependencies: 292 | wrappy "1" 293 | 294 | path-is-absolute@^1.0.0: 295 | version "1.0.1" 296 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 297 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 298 | 299 | read-installed@~3.1.3: 300 | version "3.1.5" 301 | resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-3.1.5.tgz#4ae36081afd3e2204dc2e279807aaa52c30c8c0c" 302 | integrity sha1-SuNgga/T4iBNwuJ5gHqqUsMMjAw= 303 | dependencies: 304 | debuglog "^1.0.1" 305 | read-package-json "1" 306 | readdir-scoped-modules "^1.0.0" 307 | semver "2 || 3 || 4" 308 | slide "~1.1.3" 309 | util-extend "^1.0.1" 310 | optionalDependencies: 311 | graceful-fs "2 || 3" 312 | 313 | read-package-json@1: 314 | version "1.3.3" 315 | resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-1.3.3.tgz#ef79dfda46e165376ee8a57efbfedd4d1b029ba4" 316 | integrity sha1-73nf2kbhZTdu6KV++/7dTRsCm6Q= 317 | dependencies: 318 | glob "^5.0.3" 319 | json-parse-helpfulerror "^1.0.2" 320 | normalize-package-data "^1.0.0" 321 | optionalDependencies: 322 | graceful-fs "2 || 3" 323 | 324 | readdir-scoped-modules@^1.0.0: 325 | version "1.1.0" 326 | resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" 327 | integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== 328 | dependencies: 329 | debuglog "^1.0.1" 330 | dezalgo "^1.0.0" 331 | graceful-fs "^4.1.2" 332 | once "^1.3.0" 333 | 334 | "semver@2 || 3 || 4": 335 | version "4.3.6" 336 | resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" 337 | integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= 338 | 339 | slide@~1.1.3: 340 | version "1.1.6" 341 | resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" 342 | integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= 343 | 344 | strip-ansi@^0.3.0: 345 | version "0.3.0" 346 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" 347 | integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= 348 | dependencies: 349 | ansi-regex "^0.2.1" 350 | 351 | supports-color@^0.2.0: 352 | version "0.2.0" 353 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" 354 | integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= 355 | 356 | supports-color@^5.3.0: 357 | version "5.5.0" 358 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 359 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 360 | dependencies: 361 | has-flag "^3.0.0" 362 | 363 | tr46@~0.0.3: 364 | version "0.0.3" 365 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 366 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 367 | 368 | treeify@^1.0.1, treeify@^1.1.0: 369 | version "1.1.0" 370 | resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" 371 | integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== 372 | 373 | typescript@^4.3.5: 374 | version "4.3.5" 375 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" 376 | integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== 377 | 378 | util-extend@^1.0.1: 379 | version "1.0.3" 380 | resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" 381 | integrity sha1-p8IW0mdUUWljeztu3GypEZ4v+T8= 382 | 383 | webidl-conversions@^3.0.0: 384 | version "3.0.1" 385 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 386 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 387 | 388 | whatwg-url@^5.0.0: 389 | version "5.0.0" 390 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 391 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 392 | dependencies: 393 | tr46 "~0.0.3" 394 | webidl-conversions "^3.0.0" 395 | 396 | wrappy@1: 397 | version "1.0.2" 398 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 399 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 400 | -------------------------------------------------------------------------------- /variant-switcher/_assets_/cover-art.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | --------------------------------------------------------------------------------