├── src
├── app
│ ├── index.html
│ ├── styles
│ │ ├── controls.css
│ │ ├── empty-state.css
│ │ ├── nav.css
│ │ └── ui.css
│ ├── assets
│ │ ├── placeholder-image.png
│ │ ├── line.svg
│ │ ├── arrow.svg
│ │ ├── caret.svg
│ │ ├── logo.svg
│ │ ├── stroke.svg
│ │ ├── polygon.svg
│ │ ├── text.svg
│ │ ├── rectangle.svg
│ │ ├── check.svg
│ │ ├── star.svg
│ │ ├── instance.svg
│ │ ├── radius.svg
│ │ ├── frame.svg
│ │ ├── group.svg
│ │ ├── slice.svg
│ │ ├── ellipse.svg
│ │ ├── component.svg
│ │ ├── component_set.svg
│ │ ├── context.svg
│ │ ├── boolean_operation.svg
│ │ ├── layers.svg
│ │ ├── settings.svg
│ │ ├── fill.svg
│ │ ├── refresh.svg
│ │ ├── smile.svg
│ │ ├── effects.svg
│ │ ├── vector.svg
│ │ ├── placeholder.svg
│ │ └── confetti.svg
│ ├── index.tsx
│ └── components
│ │ ├── ListItem.tsx
│ │ └── App.tsx
└── plugin
│ ├── example-theme.ts
│ ├── controller.ts
│ ├── light-to-dark-theme.ts
│ └── dark-to-light-theme.ts
├── assets
├── Auto Theme Art.png
├── Auto Theme Logo.png
└── auto-theme-example.gif
├── manifest.json
├── tsconfig.json
├── tslint.json
├── LICENSE
├── package.json
├── webpack.config.js
├── README.md
└── figma.d.ts
/src/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/styles/controls.css:
--------------------------------------------------------------------------------
1 | .button-margin-bottom {
2 | margin-bottom: 12px;
3 | }
4 |
--------------------------------------------------------------------------------
/assets/Auto Theme Art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/destefanis/auto-theme/HEAD/assets/Auto Theme Art.png
--------------------------------------------------------------------------------
/assets/Auto Theme Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/destefanis/auto-theme/HEAD/assets/Auto Theme Logo.png
--------------------------------------------------------------------------------
/assets/auto-theme-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/destefanis/auto-theme/HEAD/assets/auto-theme-example.gif
--------------------------------------------------------------------------------
/src/app/assets/placeholder-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/destefanis/auto-theme/HEAD/src/app/assets/placeholder-image.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Auto Theme",
3 | "id": "816463256448925575",
4 | "api": "1.0.0",
5 | "main": "dist/code.js",
6 | "ui": "dist/ui.html",
7 | "editorType": ["figma"]
8 | }
--------------------------------------------------------------------------------
/src/app/assets/line.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as ReactDOM from "react-dom";
3 | import App from "./components/App";
4 |
5 | ReactDOM.render(, document.getElementById("react-page"));
6 |
--------------------------------------------------------------------------------
/src/app/assets/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/assets/caret.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/assets/stroke.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/polygon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/assets/text.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/rectangle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/star.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/instance.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/radius.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/frame.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/assets/group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/assets/slice.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "outDir": "dist",
5 | "jsx": "react",
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "experimentalDecorators": true,
9 | "removeComments": true,
10 | "noImplicitAny": false,
11 | "moduleResolution": "node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/assets/ellipse.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/assets/component.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/component_set.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/context.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/boolean_operation.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/layers.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended",
5 | "tslint-react"
6 | ],
7 | "jsRules": {},
8 | "rules": {
9 | "ordered-imports": false,
10 | "object-literal-sort-keys": false,
11 | "member-ordering": false,
12 | "await-promise": false,
13 | "interface-name": false,
14 | "quotemark": false,
15 | "curly": false,
16 | "member-access": [true, "no-public"],
17 | "semicolon": false,
18 | "no-trailing-whitespace": false,
19 | "no-console": false,
20 | "jsx-wrap-multiline": false,
21 | "jsx-no-lambda": false
22 | },
23 | "rulesDirectory": []
24 | }
--------------------------------------------------------------------------------
/src/app/assets/settings.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/app/assets/fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/assets/refresh.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/app/assets/smile.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/app/components/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import classNames from "classnames";
3 |
4 | function ListItem(props) {
5 | const node = props.node;
6 |
7 | const handleClick = id => {
8 | props.onClick(id);
9 | };
10 |
11 | return (
12 | handleClick(node.id)}
19 | >
20 |
21 |
22 |
23 |
24 |
25 |
26 | {node.name}
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default ListItem;
34 |
--------------------------------------------------------------------------------
/src/app/assets/effects.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/app/assets/vector.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Daniel Destefanis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/styles/empty-state.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | padding: 0;
3 | }
4 |
5 | .active-state {
6 | display: flex;
7 | flex-flow: column;
8 | align-content: center;
9 | justify-content: center;
10 | padding: 16px;
11 | }
12 |
13 | .active-state-title {
14 | width: 100%;
15 | padding: 84px 0;
16 | text-align: center;
17 | flex: 1 1 100%;
18 | border-radius: 4px;
19 | border: 1px solid #e5e5e5;
20 | margin-bottom: 16px;
21 | }
22 |
23 | .layer-empty-title {
24 | padding: 130px 0;
25 | }
26 |
27 | .empty-state-wrapper {
28 | margin: 16px;
29 | }
30 |
31 | .empty-state {
32 | height: calc(100% - 32px);
33 | width: 100%;
34 | border: 1px solid #e5e5e5;
35 | text-align: center;
36 | margin-bottom: 16px;
37 | padding: 40px 0;
38 | border-radius: 6px;
39 | display: flex;
40 | align-content: center;
41 | justify-content: center;
42 | flex-direction: column;
43 | }
44 |
45 | .empty-state__image {
46 | height: 64px;
47 | width: 64px;
48 | background-color: #18a0fb;
49 | border-radius: 100px;
50 | margin-bottom: 24px;
51 | margin-left: auto;
52 | margin-right: auto;
53 | display: flex;
54 | justify-content: center;
55 | align-content: center;
56 | }
57 |
58 | .empty-state__title {
59 | font-size: 13px;
60 | line-height: 1.3;
61 | color: #333333;
62 | margin: auto;
63 | padding-bottom: 16px;
64 | }
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "figma-plugin-react-template",
3 | "version": "1.0.0",
4 | "description": "This plugin template uses Typescript. If you are familiar with Javascript, Typescript will look very familiar. In fact, valid Javascript code is already valid Typescript code.",
5 | "license": "ISC",
6 | "scripts": {
7 | "build": "/usr/local/bin/node node_modules/.bin/webpack --mode=production",
8 | "build:watch": "/usr/local/bin/node node_modules/.bin/webpack --mode=development --watch",
9 | "prettier:format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,json}' "
10 | },
11 | "dependencies": {
12 | "classnames": "^2.2.6",
13 | "react": "^16.8.6",
14 | "react-dom": "^16.8.6"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^16.8.24",
18 | "@types/react-dom": "^16.8.5",
19 | "css-loader": "^3.1.0",
20 | "html-webpack-inline-source-plugin": "^0.0.10",
21 | "html-webpack-plugin": "^3.2.0",
22 | "husky": "^3.0.2",
23 | "lint-staged": "^9.2.1",
24 | "prettier": "^1.18.2",
25 | "style-loader": "^0.23.1",
26 | "ts-loader": "^6.0.4",
27 | "tslint": "^5.18.0",
28 | "tslint-react": "^4.0.0",
29 | "typescript": "^3.5.3",
30 | "url-loader": "^2.1.0",
31 | "webpack": "^4.39.1",
32 | "webpack-cli": "^3.3.6"
33 | },
34 | "husky": {
35 | "hooks": {
36 | "pre-commit": "lint-staged"
37 | }
38 | },
39 | "lint-staged": {
40 | "src/**/*.{js,jsx,ts,tsx,css,json}": [
41 | "prettier --write",
42 | "git add"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/plugin/example-theme.ts:
--------------------------------------------------------------------------------
1 | // Themes are objects.
2 | // Using the "Inspector" plugin we've made pubic makes this process easier!
3 |
4 | const sampleTheme = {
5 | // Within the object, we check for a *style key*,
6 | // Figma uses this key to refer to a specific style in your library
7 | "5c1691cbeaaf4270107d34f1a12f02fdd04afa02": {
8 | // Name isn't used, but is nice for reference.
9 | name: "Dark / Header / Primary (White)",
10 | // Within the object we check for the mapsToKey key value.
11 | // This is the style we'll swap the original style with.
12 | mapsToKey: "b19a14675b8adeb1528ab5f84e57b2eeed10d46c",
13 | mapsToName: "Light / Header / Primary (900)"
14 | },
15 | style_key_goes_here: {
16 | name: "",
17 | mapsToKey: "style_key_to_switch_with_goes_here",
18 | mapsToName: ""
19 | },
20 | // If you have two instances of a component in your library
21 | // ex: (Header/Dark and Header/Light) you can swap those instances
22 | // rather than simply retheming them. By adding a component key to your theme.
23 | f0d5aa5e63fff4392e3b3c22884523369f5d0424: {
24 | componentName: "iPhone X Status Bar / Dark",
25 | mapsToComponentName: "iPhone X Status Bar / Light",
26 | // This is key of the component I want to switch with.
27 | mapsToKey: "33425bd93c1b8cea071df9b5297f0b19583a643b"
28 | },
29 | component_key_goes_here: {
30 | name: "",
31 | mapsToKey: "component_key_to_switch_with_goes_here",
32 | mapsToName: ""
33 | }
34 | };
35 |
36 | // Don't know how to find a styles key? Use our inspector plugin
37 | // https://www.figma.com/community/plugin/760351147138040099
38 |
39 | export { sampleTheme };
40 |
--------------------------------------------------------------------------------
/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/app/index.tsx', // The entry point for your UI code
13 | code: './src/plugin/controller.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)$/, 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 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
38 | plugins: [
39 | new HtmlWebpackPlugin({
40 | template: './src/app/index.html',
41 | filename: 'ui.html',
42 | inlineSource: '.(js)$',
43 | chunks: ['ui'],
44 | }),
45 | new HtmlWebpackInlineSourcePlugin(),
46 | ],
47 | })
--------------------------------------------------------------------------------
/src/app/assets/placeholder.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/styles/nav.css:
--------------------------------------------------------------------------------
1 | .nav {
2 | display: flex;
3 | flex-flow: row;
4 | border-bottom: 1px solid #e5e5e5;
5 | padding: 4px 8px;
6 | }
7 |
8 | .nav .section-title {
9 | cursor: pointer;
10 | }
11 |
12 | .active {
13 | color: #333;
14 | }
15 |
16 | .disabled {
17 | color: #b3b3b3;
18 | cursor: pointer;
19 | }
20 |
21 | .disabled:hover {
22 | color: #333;
23 | }
24 |
25 | .list {
26 | flex: 1;
27 | margin: 0;
28 | padding: 0;
29 | overflow-y: auto;
30 | overflow-x: hidden;
31 | position: relative;
32 | }
33 |
34 | .list-arrow {
35 | height: 32px;
36 | min-width: 16px;
37 | display: flex;
38 | align-content: center;
39 | justify-content: center;
40 | flex-flow: column;
41 | }
42 |
43 | .list-arrow-icon {
44 | opacity: 0;
45 | transition: opacity 50ms ease;
46 | transform: rotate(-90deg);
47 | }
48 |
49 | .list-item {
50 | width: 100%;
51 | list-style: none;
52 | padding: 0;
53 | font-family: "Inter";
54 | }
55 |
56 | .list-item ul {
57 | display: none;
58 | }
59 |
60 | .list-item--selected > .list-flex-row {
61 | background: #daebf7;
62 | }
63 |
64 | .list-item--selected ul {
65 | background: #edf5fa;
66 | }
67 |
68 | .list-item--active > ul {
69 | display: block;
70 | }
71 |
72 | .list-item--active > div > .list-arrow > .list-arrow-icon {
73 | transform: rotate(0deg);
74 | }
75 |
76 | .list-flex-row {
77 | height: 32px;
78 | display: flex;
79 | flex-flow: row;
80 | align-items: center;
81 | justify-content: flex-start;
82 | border: 1px solid transparent;
83 | transition: border-color 100ms ease;
84 | cursor: pointer;
85 | position: relative;
86 | }
87 |
88 | .list-flex-row:hover {
89 | border-color: #18a0fb;
90 | }
91 |
92 | .list-flex-row:hover .list-arrow-icon {
93 | opacity: 1;
94 | }
95 |
96 | .list-icon {
97 | text-align: center;
98 | width: 16px;
99 | opacity: 0.6;
100 | }
101 |
102 | .list-icon img {
103 | height: 12px;
104 | width: 12px;
105 | }
106 |
107 | .list-name {
108 | margin: 0 8px;
109 | font-size: 12px;
110 | white-space: nowrap;
111 | overflow: hidden;
112 | text-overflow: ellipsis;
113 | margin-right: 48px;
114 | }
115 |
116 | .layer-count {
117 | padding-left: 2px;
118 | /* color: #b3b3b3; */
119 | }
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Auto Theme
2 |
3 | 
4 |
5 | A figma plugin for automatically theming your designs from one color mapping to another. This was built specifically for use by the Discord design team.
6 |
7 | ## How to run locally
8 | * Run `yarn` to install dependencies.
9 | * Run `yarn build:watch` to start webpack in watch mode.
10 |
11 | ⭐ To change the UI of your plugin (the react code), start editing [App.tsx](./src/app/components/App.tsx).
12 | ⭐ To interact with the Figma API edit [controller.ts](./src/plugin/controller.ts).
13 | ⭐ Read more on the [Figma API Overview](https://www.figma.com/plugin-docs/api/api-overview/).
14 |
15 | ## How to use this plugin with your team
16 | * Follow the instructions for running locally
17 | * Set up your own themes, see the examples below in the Theme Object section.
18 | * In Figma, create a plugin and select this Auto Theme plugin manifest file.
19 | * Upload the plugin images from the asset directory, then hit publish internally.
20 |
21 | ## How it works
22 | * When a frame or multiple frames are selected the code loops through each layer.
23 | * During the loop, the layer checks to see what "type" the layer is (text, vector, rectangle etc). This allows us to skip certain nodes and handle mappings different for text and shapes.
24 | * If the layer has a fill, it fetches that nodes Style ID using `figma.getStyleById`.
25 | * It then imports that style from our main library using `figma.importStyleByKeyAsync`
26 | * Once we have that styles `key` then we check to see if it has a match in one of our theme objects, if it has a match we update that node with a new color.
27 |
28 | 
29 |
30 | ## Theme Object
31 |
32 | Themes are objects with key value pairs to handle how we map each color to another corresponding color. [See example theme](https://github.com/destefanis/auto-theme/blob/master/src/plugin/example-theme.ts).
33 |
34 | ```
35 | '4b93d40f61be15e255e87948a715521c3ae957e6': {
36 | name: "Dark / Header / Primary (White)",
37 | mapsToName: "Light / Header / Primary (900)",
38 | mapsToKey: '3eddc15e90bbd7064aea7cc13dc13e23a712f0b0',
39 | },
40 | ```
41 |
42 | The first string of numbers is our `style.key` which in our design system is called "Dark / Header / Primary (White)". This color in light theme is "Light / Header / Primary (900)", so we replace our first key with the `mapsToKey` string. Swapping one style key for another.
43 |
44 | ```
45 | "style_key_goes_here": {
46 | name: "",
47 | mapsToKey: "style_key_to_switch_with_goes_here",
48 | mapsToName: "",
49 | },
50 | ```
51 |
52 | This does mean you'll need to know the `keys` of each of your styles.
53 |
54 | ### How do I find my style keys?
55 | I built [Inspector Plugin](https://www.figma.com/community/plugin/760351147138040099) for this very reason.
56 |
57 | ### Instance Switching
58 |
59 | Some of your designs may use components like the status bar on iOS. In order to solve for this, the plugin allows you to swap instances of components.
60 |
61 | ```
62 | "component_key_goes_here": {
63 | name: "",
64 | mapsToKey: "component_key_to_switch_with_goes_here",
65 | mapsToName: "",
66 | },
67 | ```
68 |
69 | This way if you'd like to switch `iPhone X Status Bar / Dark` with `iPhone X Status Bar / Light` rather than try and theme them, you can. Only instances will check to see if it's parent component is listed in the themes you've declared, otherwise it will be treated normally.
70 |
71 | ### Can I use multiple themes?
72 | Yes, create a new theme and import it, then hook up a button in the UI to send a message to the [controller.ts](https://github.com/destefanis/auto-theme/blob/master/src/plugin/controller.ts#L60) to
73 | call that theme. There are two examples of this in the code already.
74 |
75 | ## Toolings
76 | This repo is using:
77 | * React + Webpack
78 | * TypeScript
79 | * TSLint
80 | * Prettier precommit hook
81 |
--------------------------------------------------------------------------------
/src/app/assets/confetti.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/app/components/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import "../styles/figma-plugin-ds.css";
3 | import "../styles/ui.css";
4 | import "../styles/nav.css";
5 | import "../styles/controls.css";
6 | import "../styles/empty-state.css";
7 |
8 | import ListItem from "./ListItem";
9 |
10 | declare function require(path: string): any;
11 |
12 | const App = ({}) => {
13 | const [selectedLayersLength, setSelectLayersLength] = React.useState(0);
14 | const [activeTab, setActiveTab] = React.useState("themes");
15 | const [skippedLayers, setSkippedLayers] = React.useState([]);
16 | const [activeLayer, setActiveLayer] = React.useState(0);
17 |
18 | const onRunApp = React.useCallback(() => {
19 | const message = "";
20 | parent.postMessage({ pluginMessage: { type: "run-app", message } }, "*");
21 | }, []);
22 |
23 | // This tells the controller.ts file to theme
24 | // our selection from dark to light.
25 | const themeToLight = React.useCallback(() => {
26 | const message = "dark-to-light-theme";
27 | parent.postMessage(
28 | { pluginMessage: { type: "theme-update", message } },
29 | "*"
30 | );
31 | }, []);
32 |
33 | // This tells the controller.ts file to theme
34 | // our selection from light to dark.
35 | const themeToDark = React.useCallback(() => {
36 | const message = "light-to-dark-theme";
37 | parent.postMessage(
38 | { pluginMessage: { type: "theme-update", message } },
39 | "*"
40 | );
41 | }, []);
42 |
43 | function setThemesActive() {
44 | setActiveTab("themes");
45 | }
46 |
47 | function setLayersActive() {
48 | setActiveTab("layers");
49 | }
50 |
51 | // When the user selects a layer in the skipped layer list.
52 | const handleLayerSelect = id => {
53 | setActiveLayer(id);
54 | parent.postMessage(
55 | { pluginMessage: { type: "select-layer", id: id } },
56 | "*"
57 | );
58 | };
59 |
60 | React.useEffect(() => {
61 | onRunApp();
62 |
63 | window.onmessage = event => {
64 | const { type, message } = event.data.pluginMessage;
65 |
66 | if (type === "selection-updated") {
67 | let nodeArray = JSON.parse(message);
68 | setSelectLayersLength(nodeArray.length);
69 | }
70 |
71 | if (type === "layers-skipped") {
72 | let unthemedLayers = JSON.parse(message);
73 | setSkippedLayers(skippedLayers => [
74 | ...skippedLayers,
75 | ...unthemedLayers
76 | ]);
77 | }
78 | };
79 | }, []);
80 |
81 | const listItems = skippedLayers.map((node, index) => (
82 |
88 | ));
89 |
90 | return (
91 |
92 | {selectedLayersLength === 0 ? (
93 |
94 |
95 |
})
96 |
97 |
98 | Select a layer to get started.
99 |
100 |
101 | ) : (
102 |
103 |
124 | {activeTab === "themes" ? (
125 |
126 |
127 | {selectedLayersLength} layers selected for themeing
128 |
129 |
135 |
141 |
142 | ) : (
143 |
144 | {skippedLayers.length === 0 ? (
145 |
146 |
147 | No layers have been skipped yet.
148 |
149 |
150 | ) : (
151 |
152 | )}
153 |
154 | )}
155 |
156 | )}
157 |
158 | );
159 | };
160 |
161 | export default App;
162 |
--------------------------------------------------------------------------------
/src/app/styles/ui.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | applet,
6 | object,
7 | iframe,
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6,
14 | p,
15 | blockquote,
16 | pre,
17 | a,
18 | abbr,
19 | acronym,
20 | address,
21 | big,
22 | cite,
23 | code,
24 | del,
25 | dfn,
26 | em,
27 | img,
28 | ins,
29 | kbd,
30 | q,
31 | s,
32 | samp,
33 | small,
34 | strike,
35 | strong,
36 | sub,
37 | sup,
38 | tt,
39 | var,
40 | b,
41 | u,
42 | i,
43 | center,
44 | dl,
45 | dt,
46 | dd,
47 | ol,
48 | ul,
49 | li,
50 | fieldset,
51 | form,
52 | label,
53 | legend,
54 | table,
55 | caption,
56 | tbody,
57 | tfoot,
58 | thead,
59 | tr,
60 | th,
61 | td,
62 | article,
63 | aside,
64 | canvas,
65 | details,
66 | embed,
67 | figure,
68 | figcaption,
69 | footer,
70 | header,
71 | hgroup,
72 | menu,
73 | nav,
74 | output,
75 | ruby,
76 | section,
77 | summary,
78 | time,
79 | mark,
80 | audio,
81 | video {
82 | margin: 0;
83 | padding: 0;
84 | border: 0;
85 | font-size: 100%;
86 | font: inherit;
87 | vertical-align: baseline;
88 | }
89 |
90 | /* make sure to set some focus styles for accessibility */
91 | :focus {
92 | outline: 0;
93 | }
94 |
95 | /* HTML5 display-role reset for older browsers */
96 | article,
97 | aside,
98 | details,
99 | figcaption,
100 | figure,
101 | footer,
102 | header,
103 | hgroup,
104 | menu,
105 | nav,
106 | section {
107 | display: block;
108 | }
109 |
110 | body {
111 | line-height: 1;
112 | }
113 |
114 | ol,
115 | ul {
116 | list-style: none;
117 | }
118 |
119 | blockquote,
120 | q {
121 | quotes: none;
122 | }
123 |
124 | blockquote:before,
125 | blockquote:after,
126 | q:before,
127 | q:after {
128 | content: "";
129 | content: none;
130 | }
131 |
132 | table {
133 | border-collapse: collapse;
134 | border-spacing: 0;
135 | }
136 |
137 | input[type="search"]::-webkit-search-cancel-button,
138 | input[type="search"]::-webkit-search-decoration,
139 | input[type="search"]::-webkit-search-results-button,
140 | input[type="search"]::-webkit-search-results-decoration {
141 | -webkit-appearance: none;
142 | -moz-appearance: none;
143 | }
144 |
145 | input[type="search"] {
146 | -webkit-appearance: none;
147 | -moz-appearance: none;
148 | -webkit-box-sizing: content-box;
149 | -moz-box-sizing: content-box;
150 | box-sizing: content-box;
151 | }
152 |
153 | textarea {
154 | overflow: auto;
155 | vertical-align: top;
156 | resize: vertical;
157 | }
158 |
159 | /**
160 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
161 | */
162 |
163 | audio,
164 | canvas,
165 | video {
166 | display: inline-block;
167 | *display: inline;
168 | *zoom: 1;
169 | max-width: 100%;
170 | }
171 |
172 | /**
173 | * Prevent modern browsers from displaying `audio` without controls.
174 | * Remove excess height in iOS 5 devices.
175 | */
176 |
177 | audio:not([controls]) {
178 | display: none;
179 | height: 0;
180 | }
181 |
182 | /**
183 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
184 | * Known issue: no IE 6 support.
185 | */
186 |
187 | [hidden] {
188 | display: none;
189 | }
190 |
191 | /**
192 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
193 | * `em` units.
194 | * 2. Prevent iOS text size adjust after orientation change, without disabling
195 | * user zoom.
196 | */
197 |
198 | html {
199 | font-size: 100%; /* 1 */
200 | -webkit-text-size-adjust: 100%; /* 2 */
201 | -ms-text-size-adjust: 100%; /* 2 */
202 | }
203 |
204 | /**
205 | * Address `outline` inconsistency between Chrome and other browsers.
206 | */
207 |
208 | a:focus {
209 | outline: thin dotted;
210 | }
211 |
212 | /**
213 | * Improve readability when focused and also mouse hovered in all browsers.
214 | */
215 |
216 | a:active,
217 | a:hover {
218 | outline: 0;
219 | }
220 |
221 | /**
222 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
223 | * 2. Improve image quality when scaled in IE 7.
224 | */
225 |
226 | img {
227 | border: 0; /* 1 */
228 | -ms-interpolation-mode: bicubic; /* 2 */
229 | }
230 |
231 | /**
232 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
233 | */
234 |
235 | figure {
236 | margin: 0;
237 | }
238 |
239 | /**
240 | * Correct margin displayed oddly in IE 6/7.
241 | */
242 |
243 | form {
244 | margin: 0;
245 | }
246 |
247 | /**
248 | * Define consistent border, margin, and padding.
249 | */
250 |
251 | fieldset {
252 | border: 1px solid #c0c0c0;
253 | margin: 0 2px;
254 | padding: 0.35em 0.625em 0.75em;
255 | }
256 |
257 | /**
258 | * 1. Correct color not being inherited in IE 6/7/8/9.
259 | * 2. Correct text not wrapping in Firefox 3.
260 | * 3. Correct alignment displayed oddly in IE 6/7.
261 | */
262 |
263 | legend {
264 | border: 0; /* 1 */
265 | padding: 0;
266 | white-space: normal; /* 2 */
267 | *margin-left: -7px; /* 3 */
268 | }
269 |
270 | /**
271 | * 1. Correct font size not being inherited in all browsers.
272 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
273 | * and Chrome.
274 | * 3. Improve appearance and consistency in all browsers.
275 | */
276 |
277 | button,
278 | input,
279 | select,
280 | textarea {
281 | font-size: 100%; /* 1 */
282 | margin: 0; /* 2 */
283 | vertical-align: baseline; /* 3 */
284 | *vertical-align: middle; /* 3 */
285 | }
286 |
287 | /**
288 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in
289 | * the UA stylesheet.
290 | */
291 |
292 | button,
293 | input {
294 | line-height: normal;
295 | }
296 |
297 | /**
298 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
299 | * All other form control elements do not inherit `text-transform` values.
300 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
301 | * Correct `select` style inheritance in Firefox 4+ and Opera.
302 | */
303 |
304 | button,
305 | select {
306 | text-transform: none;
307 | }
308 |
309 | /**
310 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
311 | * and `video` controls.
312 | * 2. Correct inability to style clickable `input` types in iOS.
313 | * 3. Improve usability and consistency of cursor style between image-type
314 | * `input` and others.
315 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
316 | * Known issue: inner spacing remains in IE 6.
317 | */
318 |
319 | button,
320 | html input[type="button"], /* 1 */
321 | input[type="reset"],
322 | input[type="submit"] {
323 | -webkit-appearance: button; /* 2 */
324 | cursor: pointer; /* 3 */
325 | *overflow: visible; /* 4 */
326 | }
327 |
328 | /**
329 | * Re-set default cursor for disabled elements.
330 | */
331 |
332 | button[disabled],
333 | html input[disabled] {
334 | cursor: default;
335 | }
336 |
337 | /**
338 | * 1. Address box sizing set to content-box in IE 8/9.
339 | * 2. Remove excess padding in IE 8/9.
340 | * 3. Remove excess padding in IE 7.
341 | * Known issue: excess padding remains in IE 6.
342 | */
343 |
344 | input[type="checkbox"],
345 | input[type="radio"] {
346 | box-sizing: border-box; /* 1 */
347 | padding: 0; /* 2 */
348 | *height: 13px; /* 3 */
349 | *width: 13px; /* 3 */
350 | }
351 |
352 | /**
353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
355 | * (include `-moz` to future-proof).
356 | */
357 |
358 | input[type="search"] {
359 | -webkit-appearance: textfield; /* 1 */
360 | -moz-box-sizing: content-box;
361 | -webkit-box-sizing: content-box; /* 2 */
362 | box-sizing: content-box;
363 | }
364 |
365 | /**
366 | * Remove inner padding and search cancel button in Safari 5 and Chrome
367 | * on OS X.
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Remove inner padding and border in Firefox 3+.
377 | */
378 |
379 | button::-moz-focus-inner,
380 | input::-moz-focus-inner {
381 | border: 0;
382 | padding: 0;
383 | }
384 |
385 | /**
386 | * 1. Remove default vertical scrollbar in IE 6/7/8/9.
387 | * 2. Improve readability and alignment in all browsers.
388 | */
389 |
390 | textarea {
391 | overflow: auto; /* 1 */
392 | vertical-align: top; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove most spacing between table cells.
397 | */
398 |
399 | table {
400 | border-collapse: collapse;
401 | border-spacing: 0;
402 | }
403 |
404 | html,
405 | button,
406 | input,
407 | select,
408 | textarea {
409 | color: #222;
410 | }
411 |
412 | ::-moz-selection {
413 | background: #b3d4fc;
414 | text-shadow: none;
415 | }
416 |
417 | ::selection {
418 | background: #b3d4fc;
419 | text-shadow: none;
420 | }
421 |
422 | img {
423 | vertical-align: middle;
424 | }
425 |
426 | fieldset {
427 | border: 0;
428 | margin: 0;
429 | padding: 0;
430 | }
431 |
432 | textarea {
433 | resize: vertical;
434 | }
435 |
436 | .chromeframe {
437 | margin: 0.2em 0;
438 | background: #ccc;
439 | color: #000;
440 | padding: 0.2em 0;
441 | }
442 |
--------------------------------------------------------------------------------
/src/plugin/controller.ts:
--------------------------------------------------------------------------------
1 | // Plugin window dimensions
2 | figma.showUI(__html__, { width: 320, height: 358 });
3 |
4 | // Imported themes
5 | import { darkTheme } from "./dark-to-light-theme";
6 | import { lightTheme } from "./light-to-dark-theme";
7 |
8 | // Utility function for serializing nodes to pass back to the UI.
9 | function serializeNodes(nodes) {
10 | let serializedNodes = JSON.stringify(nodes, [
11 | "name",
12 | "type",
13 | "children",
14 | "id"
15 | ]);
16 |
17 | return serializedNodes;
18 | }
19 |
20 | // Utility function for flattening the
21 | // selection of nodes in Figma into an array.
22 | const flatten = obj => {
23 | const array = Array.isArray(obj) ? obj : [obj];
24 | return array.reduce((acc, value) => {
25 | acc.push(value);
26 | if (value.children) {
27 | acc = acc.concat(flatten(value.children));
28 | delete value.children;
29 | }
30 | return acc;
31 | }, []);
32 | };
33 |
34 | figma.ui.onmessage = msg => {
35 | let skippedLayers = [];
36 |
37 | if (msg.type === "run-app") {
38 | // If nothing's selected, we tell the UI to keep the empty state.
39 | if (figma.currentPage.selection.length === 0) {
40 | figma.ui.postMessage({
41 | type: "selection-updated",
42 | message: 0
43 | });
44 | } else {
45 | let selectedNodes = flatten(figma.currentPage.selection);
46 |
47 | // Update the UI with the number of selected nodes.
48 | // This will display our theming controls.
49 | figma.ui.postMessage({
50 | type: "selection-updated",
51 | message: serializeNodes(selectedNodes)
52 | });
53 | }
54 | }
55 |
56 | // When a theme is selected
57 | if (msg.type === "theme-update") {
58 | const nodesToTheme = figma.currentPage.selection;
59 |
60 | if (msg.message === "dark-to-light-theme") {
61 | // Update the layers with this theme, by passing in the
62 | // selected nodes and the theme object.
63 | nodesToTheme.map(selected => updateTheme(selected, darkTheme));
64 | }
65 |
66 | if (msg.message === "light-to-dark-theme") {
67 | // Update the layers with this theme, by passing in the
68 | // selected nodes and the theme object.
69 | nodesToTheme.map(selected => updateTheme(selected, lightTheme));
70 | }
71 |
72 | // Need to wait for some promises to resolve before
73 | // sending the skipped layers back to the UI.
74 | setTimeout(function() {
75 | figma.ui.postMessage({
76 | type: "layers-skipped",
77 | message: serializeNodes(skippedLayers)
78 | });
79 | }, 500);
80 |
81 | figma.notify(`Theming complete`, { timeout: 750 });
82 | }
83 |
84 | // When a layer is selected from the skipped layers.
85 | if (msg.type === "select-layer") {
86 | let layer = figma.getNodeById(msg.id);
87 | let layerArray = [];
88 |
89 | // Using selection and viewport requires an array.
90 | layerArray.push(layer);
91 |
92 | // Moves the layer into focus and selects so the user can update it.
93 | figma.notify(`Layer ${layer.name} selected`, { timeout: 750 });
94 | figma.currentPage.selection = layerArray;
95 | figma.viewport.scrollAndZoomIntoView(layerArray);
96 | }
97 |
98 | // Swap styles with the corresponding/mapped styles
99 | async function replaceStyles(
100 | node,
101 | style,
102 | mappings,
103 | applyStyle: (node, styleId) => void
104 | ) {
105 | // Find the style the ID corresponds to in the team library
106 | let importedStyle = await figma.importStyleByKeyAsync(style.key);
107 |
108 | // Once the promise is resolved, then see if the
109 | // key matches anything in the mappings object.
110 | if (mappings[importedStyle.key] !== undefined) {
111 | let mappingStyle = mappings[importedStyle.key];
112 |
113 | // Use the mapping value to fetch the official style.
114 | let newStyle = await figma.importStyleByKeyAsync(mappingStyle.mapsToKey);
115 |
116 | // Update the node with the new color.
117 | applyStyle(node, newStyle.id);
118 | } else {
119 | skippedLayers.push(node);
120 | }
121 | }
122 |
123 | // Fix layers with no style attached to them, just hex colors
124 | async function fixStyles(
125 | node,
126 | nodeType,
127 | style,
128 | mappings,
129 | applyStyle: (node, styleId) => void
130 | ) {
131 | let styleName = nodeType.toLowerCase() + " " + style;
132 | console.log(styleName);
133 | // See if the key matches anything in the mappings object.
134 | if (mappings[styleName] !== undefined) {
135 | let mappingStyle = mappings[styleName];
136 |
137 | // Use the mapping value to fetch the official style.
138 | let newStyle = await figma.importStyleByKeyAsync(mappingStyle.mapsToKey);
139 |
140 | // Update the node with the new color.
141 | applyStyle(node, newStyle.id);
142 | } else {
143 | skippedLayers.push(node);
144 | }
145 | }
146 |
147 | async function replaceComponent(
148 | node,
149 | key,
150 | mappings,
151 | applyComponent: (node, masterComponent) => void
152 | ) {
153 | let componentToSwitchWith = mappings[key];
154 | let importedComponent = await figma.importComponentByKeyAsync(
155 | componentToSwitchWith.mapsToKey
156 | );
157 | // Switch the existing component to a new component.
158 | applyComponent(node, importedComponent);
159 | }
160 |
161 | async function swapComponent(node, key, mappings) {
162 | await replaceComponent(
163 | node,
164 | key,
165 | mappings,
166 | (node, masterComponent) => (node.masterComponent = masterComponent)
167 | );
168 | }
169 |
170 | async function replaceFills(node, style, mappings) {
171 | await replaceStyles(
172 | node,
173 | style,
174 | mappings,
175 | (node, styleId) => (node.fillStyleId = styleId)
176 | );
177 | }
178 |
179 | async function replaceNoStyleFill(node, nodeType, style, mappings) {
180 | await fixStyles(
181 | node,
182 | nodeType,
183 | style,
184 | mappings,
185 | (node, styleId) => (node.fillStyleId = styleId)
186 | );
187 | }
188 |
189 | async function replaceStrokes(node, style, mappings) {
190 | await replaceStyles(
191 | node,
192 | style,
193 | mappings,
194 | (node, styleId) => (node.strokeStyleId = styleId)
195 | );
196 | }
197 |
198 | async function replaceEffects(node, style, mappings) {
199 | await replaceStyles(
200 | node,
201 | style,
202 | mappings,
203 | (node, styleId) => (node.effectStyleId = styleId)
204 | );
205 | }
206 |
207 | // Updates the node with the new theme depending on
208 | // the type of the node.
209 | function updateTheme(node, theme) {
210 | switch (node.type) {
211 | case "COMPONENT":
212 | case "COMPONENT_SET":
213 | case "RECTANGLE":
214 | case "GROUP":
215 | case "ELLIPSE":
216 | case "POLYGON":
217 | case "STAR":
218 | case "LINE":
219 | case "BOOLEAN_OPERATION":
220 | case "FRAME":
221 | case "LINE":
222 | case "VECTOR": {
223 | if (node.children) {
224 | node.children.forEach(child => {
225 | updateTheme(child, theme);
226 | });
227 | }
228 | if (node.fills) {
229 | if (node.fillStyleId && typeof node.fillStyleId !== "symbol") {
230 | let style = figma.getStyleById(node.fillStyleId);
231 | // Pass in the layer we want to change, the style ID the node is using.
232 | // and the set of mappings we want to check against.
233 | replaceFills(node, style, theme);
234 | } else if (node.fillStyleId === "") {
235 | // No style on the layer? Let's fix it for them.
236 | // First we need the fill type determined above ex:is it #ffffff?), then
237 | // we pass that hex into a new function.
238 | let style = determineFill(node.fills);
239 | let nodeType = node.type;
240 | replaceNoStyleFill(node, nodeType, style, theme);
241 | } else {
242 | skippedLayers.push(node);
243 | }
244 | }
245 |
246 | if (node.strokeStyleId) {
247 | replaceStrokes(node, figma.getStyleById(node.strokeStyleId), theme);
248 | }
249 |
250 | if (node.effectStyleId) {
251 | replaceEffects(node, figma.getStyleById(node.effectStyleId), theme);
252 | }
253 |
254 | break;
255 | }
256 | case "INSTANCE": {
257 | let componentKey = node.masterComponent.key;
258 | // If this instance is in mapping, then call it and skip it's children
259 | // otherwise check for the normal differences.
260 | if (theme[componentKey] !== undefined) {
261 | swapComponent(node, componentKey, theme);
262 | } else {
263 | if (node.fills) {
264 | if (node.fillStyleId && typeof node.fillStyleId !== "symbol") {
265 | let style = figma.getStyleById(node.fillStyleId);
266 | // Pass in the layer we want to change, the style ID the node is using.
267 | // and the set of mappings we want to check against.
268 | replaceFills(node, style, theme);
269 | } else if (node.fillStyleId === "") {
270 | // No style on the layer? Let's fix it for them.
271 | // First we need the fill type determined above ex:is it #ffffff?), then
272 | // we pass that hex into a new function.
273 | let style = determineFill(node.fills);
274 | let nodeType = node.type;
275 | replaceNoStyleFill(node, nodeType, style, theme);
276 | } else {
277 | skippedLayers.push(node);
278 | }
279 | }
280 |
281 | if (node.strokeStyleId) {
282 | replaceStrokes(node, figma.getStyleById(node.strokeStyleId), theme);
283 | }
284 |
285 | if (node.effectStyleId) {
286 | replaceEffects(node, figma.getStyleById(node.effectStyleId), theme);
287 | }
288 |
289 | if (node.children) {
290 | node.children.forEach(child => {
291 | updateTheme(child, theme);
292 | });
293 | }
294 | }
295 | break;
296 | }
297 | case "TEXT": {
298 | if (node.fillStyleId && typeof node.fillStyleId !== "symbol") {
299 | replaceFills(node, figma.getStyleById(node.fillStyleId), theme);
300 | } else if (node.fillStyleId === "") {
301 | let style = determineFill(node.fills);
302 | let nodeType = node.type;
303 | replaceNoStyleFill(node, nodeType, style, theme);
304 | } else {
305 | skippedLayers.push(node);
306 | }
307 | }
308 | default: {
309 | // do nothing
310 | }
311 | }
312 | }
313 |
314 | // Determine a nodes fills
315 | function determineFill(fills) {
316 | let fillValues = [];
317 | let rgbObj;
318 |
319 | fills.forEach(fill => {
320 | if (fill.type === "SOLID" && fill.visible === true) {
321 | rgbObj = convertColor(fill.color);
322 | fillValues.push(RGBToHex(rgbObj["r"], rgbObj["g"], rgbObj["b"]));
323 | }
324 | });
325 |
326 | return fillValues[0];
327 | }
328 |
329 | // Utility functions for color conversion.
330 | function convertColor(color) {
331 | const colorObj = color;
332 | const figmaColor = {};
333 |
334 | Object.entries(colorObj).forEach(cf => {
335 | const [key, value] = cf;
336 |
337 | if (["r", "g", "b"].includes(key)) {
338 | figmaColor[key] = (255 * (value as number)).toFixed(0);
339 | }
340 | if (key === "a") {
341 | figmaColor[key] = value;
342 | }
343 | });
344 | return figmaColor;
345 | }
346 |
347 | function RGBToHex(r, g, b) {
348 | r = Number(r).toString(16);
349 | g = Number(g).toString(16);
350 | b = Number(b).toString(16);
351 |
352 | if (r.length == 1) r = "0" + r;
353 | if (g.length == 1) g = "0" + g;
354 | if (b.length == 1) b = "0" + b;
355 |
356 | return "#" + r + g + b;
357 | }
358 | };
359 |
--------------------------------------------------------------------------------
/src/plugin/light-to-dark-theme.ts:
--------------------------------------------------------------------------------
1 | // For mapping from light to dark theme.
2 | const lightTheme = {
3 | // Components
4 | "33425bd93c1b8cea071df9b5297f0b19583a643b": {
5 | componentName: "iPhone X Status Bar / Light",
6 | mapsToKey: "f0d4aa5e63fff4392e3b3c22884523369f5d0424"
7 | },
8 | "0489bde7fd0346a97eff3170167714838a8ffb9c": {
9 | componentName: "iPhone X Home Indicator / Light",
10 | mapsToKey: "5b8dce7a790466da546d319a69f5de220e1a66f1"
11 | },
12 | "867fa47defeb07293aa37e5467e4ca487019dd78": {
13 | componentName: "System (Light) / Numeric Keyboard",
14 | mapsToKey: "3ee4cf479eefd5e181ff4abd1c982011438e692d"
15 | },
16 | bca69f03db8e938cd78ca91c84e35553804ca8c1: {
17 | componentName: "System (Light) / Keyboard",
18 | mapsToKey: "e4dcbeb8549332e4c969ef4d0d302e75a7932c25"
19 | },
20 | "46d6bed4edd9482b1452afab5ab0292b516c9e09": {
21 | componentName: "Navigation Tab / Light",
22 | mapsToKey: "64d16554561a7496b2d23854074ce923655918e0"
23 | },
24 | "00230f03c08e00a787e9c2659c3165bcad7ae06b": {
25 | componentName: "Header / Guild / Light",
26 | mapsToKey: "e9622ab25248f31fb02b6faa00308b8faa4acb3e"
27 | },
28 | "4592fb98edf78fdeea07d23445de948286e7c5f2": {
29 | componentName: "Header / DM",
30 | mapsToKey: "ff8b5c71a60fc212647c40e481c0bf1886a3ec85"
31 | },
32 | "790d7d3d884a6d3dadc79bf3c48a4918f4c16ba7": {
33 | componentName: "Status Bar / Light",
34 | mapsToKey: "3588fe4d5a302b2fca2be2b0cb5c12e2a2f41c05"
35 | },
36 | a9231a3d9fac5b7d26d56904c7b28d5d00bfff97: {
37 | componentName: "Guild Selected / Light",
38 | mapsToKey: "9e0a9f99024fb9baedcacbb123c84d7cc4b8f87a"
39 | },
40 | "8c80d5bd7bdd80acec9793a719f6caaebba1c6ee": {
41 | componentName: "Messages Selected / Light",
42 | mapsToKey: "c25d89953041d095215c972fa55dc6f7776d9a54"
43 | },
44 | bd56c3e89162d8274c4ca591f2ca1e1064658570: {
45 | componentName: "Navigation Tab / Dark",
46 | mapsToKey: "43c14ca23834d2aa3bf1e027a0635c7393e87378"
47 | },
48 | "05fc2a6b4207baa8f672dc9e8a5d750c5d60711b": {
49 | componentName: "Windows Bar / Light",
50 | mapsToKey: "d31d651767116b73f9209c5362669782ff3a8a25"
51 | },
52 | // Headings
53 | b19a14675b8adeb1528ab5f84e57b2eeed10d46c: {
54 | name: "Light / Header / Primary (900)",
55 | mapsToName: "Dark / Header / Primary (White)",
56 | mapsToKey: "5c1691cbeaaf4270107d34f1a12f02fdd04afa02"
57 | },
58 | "608f2ea1aa64ff7f202e8c22cc4147a02be9d85b": {
59 | name: "Light / Header / Secondary (600)",
60 | mapsToName: "Dark / Header / Secondary (300)",
61 | mapsToKey: "bc090cb3b1c7313ae276acbd791b5b87b478ec59"
62 | },
63 | // Text
64 | "546c7d46e754ac2b23b338783d72f206b77b6436": {
65 | name: "Light / Text / Normal (700)",
66 | mapsToName: "Dark / Text / Normal (200)",
67 | mapsToKey: "5c77a96137b698b5575557c069cabd6877d66e1e"
68 | },
69 | "7d8703ec132ddaf6968f6d190d1e80031c559d7c": {
70 | name: "Light / Text / Muted (500)",
71 | mapsToName: "Dark / Text / Muted (400)",
72 | mapsToKey: "5d84ad92f3ad152f196e2093a3c0542a08dfba11"
73 | },
74 | "64d3058dd508a4985670b2d19418a06a3503c9c2": {
75 | name: "Light / Text / Link",
76 | mapsToName: "Dark / Text / Link",
77 | mapsToKey: "bf03232753079bdd5bec6c55343b659876b5283f"
78 | },
79 | "15320fd498dcd4e113c5bd587dca2d11d4492e84": {
80 | name: "themes/light/text/text-brand",
81 | mapsToName: "themes/dark/text/text-brand",
82 | mapsToKey: "6e4aef7677e2ea82c87465276522da7ef5a07121"
83 | },
84 | c8d237080d38671193403b49cdc6a5778a14bf45: {
85 | name: "themes/light/text/text-danger",
86 | mapsToName: "themes/dark/text/text-danger",
87 | mapsToKey: "094cbaac0817be7bbfd8292cb98fc1e515e7ea0e"
88 | },
89 | "0d95a7d4d30ef99ebd04abd5b2dd4708913f765b": {
90 | name: "themes/light/text/text-warning",
91 | mapsToName: "themes/dark/text/text-warning",
92 | mapsToKey: "df0622bb33232fe041c468e8d3dd37e5428b10e7"
93 | },
94 | "71f64b08bdec4daf747a850b128e0994c4593c04": {
95 | name: "themes/light/text/text-positive",
96 | mapsToName: "themes/dark/text/text-positive",
97 | mapsToKey: "7733117cf1ef570b77332c86ba783af6cb735fc1"
98 | },
99 | // Interactive Text & Icons
100 | "9c23a031773711e026394f4354661c37ee5b4682": {
101 | name: "Light / Interactive Text & Icons / Normal (600)",
102 | mapsToName: "Dark / Interactive Text & Icons / Normal (300)",
103 | mapsToKey: "287463bade90c1eed5ea4cb0b5d63794daa8aec2"
104 | },
105 | "vector #757575": {
106 | name: "Unstyled Icon",
107 | mapsToName: "Dark / Interactive Text & Icons / Normal (300)",
108 | mapsToKey: "287463bade90c1eed5ea4cb0b5d63794daa8aec2"
109 | },
110 | "vector #4f5660": {
111 | name: "Unstyled Icon",
112 | mapsToName: "Dark / Interactive Text & Icons / Normal (300)",
113 | mapsToKey: "287463bade90c1eed5ea4cb0b5d63794daa8aec2"
114 | },
115 | "boolean_operation #757575": {
116 | name: "Unstyled Icon",
117 | mapsToName: "Dark / Interactive Text & Icons / Normal (300)",
118 | mapsToKey: "287463bade90c1eed5ea4cb0b5d63794daa8aec2"
119 | },
120 | "boolean_operation #4f5660": {
121 | name: "Unstyled Icon",
122 | mapsToName: "Dark / Interactive Text & Icons / Normal (300)",
123 | mapsToKey: "287463bade90c1eed5ea4cb0b5d63794daa8aec2"
124 | },
125 | e9542e95adf3bbe74286c2cf279fee64f7ba3279: {
126 | name: "themes/light/interactive/interactive-hover",
127 | mapsToName: "themes/dark/interactive/interactive-hover",
128 | mapsToKey: "502dcdf04992818dcbaed125ad711b446dee4c68"
129 | },
130 | "620c98e8f9255a6107dee91745669e5b702b413c": {
131 | name: "Light / Interactive Text & Icons / Active (900)",
132 | mapsToName: "Dark / Interactive Text & Icons / Active (White)",
133 | mapsToKey: "3eddc15e90bbd7064aea7cc13dc13e23a712f0b0"
134 | },
135 | "9328cd78a39149b070d68f98d9fe4df7a92bf67d": {
136 | name: "Light / Interactive Text & Icons / Muted (300)",
137 | mapsToName: "Dark / Interactive Text & Icons / Muted (500)",
138 | mapsToKey: "fa698aa2a724522a7c29efb0a662aec75a1be5a1"
139 | },
140 | // Backgrounds
141 | "2449a2983d43793d80baa20c6c60e8a48e7f3a0c": {
142 | name: "Light / Background / Primary (White)",
143 | mapsToName: "Dark / Background / Primary (600)",
144 | mapsToKey: "4b93d40f61be15e255e87948a715521c3ae957e6"
145 | },
146 | "frame #ffffff": {
147 | name: "White Background",
148 | mapsToName: "Dark / Background / Primary (600)",
149 | mapsToKey: "4b93d40f61be15e255e87948a715521c3ae957e6"
150 | },
151 | "frame #5865f2": {
152 | name: "Unstyled Brand",
153 | mapsToName: "other/blurple (brand-500)",
154 | mapsToKey: "25b165222f45fd70dc3c8e68d1a25f8d379a597d"
155 | },
156 | "rectangle #5865f2": {
157 | name: "Unstyled Brand",
158 | mapsToName: "other/blurple (brand-500)",
159 | mapsToKey: "25b165222f45fd70dc3c8e68d1a25f8d379a597d"
160 | },
161 | "83704278c845a6a7ceb1f837387972ccb6d41960": {
162 | name: "Light / Background / Secondary (130)",
163 | mapsToName: "Dark / Background / Secondary (630)",
164 | mapsToKey: "fb1358e5bd6dec072801298238cf49ff77b79a4b"
165 | },
166 | "6acd84c794796d112d4e9d22c4c8a5cae940a61d": {
167 | name: "Light / Background / Secondary Alternate",
168 | mapsToName: "Dark / Background / Secondary Alternate",
169 | mapsToKey: "abf9ad88ae1ade1a4b945b012f0965c9cdc068c9"
170 | },
171 | dbd02a76b7b77c1976114c04068f0fbc22015fab: {
172 | name: "Light / Background / Tertiary (200)",
173 | mapsToName: "Dark / Background / Tertiary (700)",
174 | mapsToKey: "ef179b6abe6cb8779857e05a6333d33f7a2b9320"
175 | },
176 | "7a199ce029a847f3a361dfb6a6e0ee4e4ba84d4f": {
177 | name: "Light / Background / Accent (500)",
178 | mapsToName: "Dark / Background / Accent (500)",
179 | mapsToKey: "3dd0e30ce0a8287eb91ec1fbeff92031e634ed01"
180 | },
181 | "634ef95b53ab529a774f27ed16be07c0b3fb3a5f": {
182 | name: "Light / Override / Read Channels 360",
183 | mapsToName: "Dark / Override / Read Channels",
184 | mapsToKey: "bfcdf063eb2c1edb446ba5d7880da6a324cc9b4f"
185 | },
186 | "6c8b08a42f9614842e880bf7bb795014d8fbae94": {
187 | name: "Light / Background / Floating (White)",
188 | mapsToName: "Dark / Background / Floating (800)",
189 | mapsToKey: "11516f4b43f381afb5a6bdf2c34b9437f0eecde1"
190 | },
191 | c592ea0b26929cf1374f973b857027dbd21ffb12: {
192 | name: "themes/light/background/status-danger-background",
193 | mapsToName: "themes/dark/background/status-danger-background",
194 | mapsToKey: "b659c283950f8b335922f52e40cefd3cf679d297"
195 | },
196 | "45f2139348b50263fda4704d4a9accea74540dcc": {
197 | name: "themes/light/background/status-warning-background",
198 | mapsToName: "themes/dark/background/status-warning-background",
199 | mapsToKey: "3dbd679897876b69bc9cc8fa38be83c525ac5ed5"
200 | },
201 | "2a135fa63c0cea473936ced51ccd767b2f156739": {
202 | name: "themes/light/background/status-positive-background",
203 | mapsToName: "themes/dark/background/status-positive-background",
204 | mapsToKey: "746e170ac6e7ba80d171f01313735a3ec5535ef8"
205 | },
206 | "30d44092c13231213143b50015907463dd1b6211": {
207 | name: "themes/light/background/background-mentioned",
208 | mapsToName: "themes/dark/background/background-mentioned",
209 | mapsToKey: "da21c08d5f887ae8d6195d7f8a7585219d670b93"
210 | },
211 | "4d15ee684eb9fd6cb114d7fb585c83c9b0a598fd": {
212 | name: "themes/light/background/background-mentioned-hover",
213 | mapsToName: "themes/dark/background/background-mentioned-hover",
214 | mapsToKey: "39c91bf62536cb1c6f51087853c35afcc6462bac"
215 | },
216 | "440a2d66490b7162417c740e66355f39d7b9e41a": {
217 | name: "themes/light/background/background-message-hover",
218 | mapsToName: "themes/dark/background/background-message-hover",
219 | mapsToKey: "1054e0c4bc3e52ae2c7c48aa0d0f95ed5d998587"
220 | },
221 | "5747d5e2f1e6047746c77e9368e8d21324eb93d9": {
222 | name: "themes/light/background/background-mobile-primary",
223 | mapsToName: "themes/dark/background/background-mobile-primary",
224 | mapsToKey: "72a70771ff2a268130e7352250f374722f4d8bfe"
225 | },
226 | de9f518c35096095c02c215543174a04900b07d7: {
227 | name: "themes/light/background/background-mobile-secondary",
228 | mapsToName: "themes/dark/background/background-mobile-secondary",
229 | mapsToKey: "251f85bc338c5411608c2dc141a538305ab6b4c1"
230 | },
231 | "1fae53b19be2fe85aa44529cd3243c7b280173f1": {
232 | name: "themes/light/background/background-nested-floating",
233 | mapsToName: "themes/dark/background/background-nested-floating",
234 | mapsToKey: "1e1caa8f31ed3bb7ce6e6ce20dfe3187b20766c8"
235 | },
236 | // Background Modifiers
237 | "35307396ae29aaeb583ae65891c69ec689f0c41e": {
238 | name: "Light / Background Mod / Hover",
239 | mapsToName: "Dark / Background Mod / Hover",
240 | mapsToKey: "d6c9270834b11c99ee651f0f5072ad2c63701165"
241 | },
242 | ddadf76919d9bacb925242a024dc1e2f5f517a46: {
243 | name: "Light / Background Mod / Active",
244 | mapsToName: "Dark / Background Mod / Active",
245 | mapsToKey: "bcf890d7a215c65deef97fb3d3f5bcebc9869bab"
246 | },
247 | "5af2eaf14901472c26b641997796bdba76ee1794": {
248 | name: "Light / Background Mod / Selected",
249 | mapsToName: "Dark / Background Mod / Selected",
250 | mapsToKey: "ce012db42f35fb58b4fe1d6d8b46c4905a8fad0a"
251 | },
252 | "08c7091f8d6950dc3f616afe8ed45b086f9124c7": {
253 | name: "Light / Background Mod / Accent",
254 | mapsToName: "Dark / Background Mod / Accent",
255 | mapsToKey: "a6a3dc153f0e589408186176ebf8f20ed2f9bda3"
256 | },
257 | // Status
258 | "6faa6d09b47caeb32fa0f5f81c561dcb7d68e9b1": {
259 | name: "themes/light/status/status-positive",
260 | mapsToName: "themes/dark/status/status-positive",
261 | mapsToKey: "61c493d9d14f2a5ae52c2037149773f0cd7690a5"
262 | },
263 | "0c9cfa27f153e6a5a9954242bb6ae3cac02d4468": {
264 | name: "themes/light/status/status-danger",
265 | mapsToName: "themes/dark/status/status-danger",
266 | mapsToKey: "0ff4d563aae53dd8012f78a67f9fd182693a0f21"
267 | },
268 | "9fa2f99cffe7ba587f259e98fb4de12c0b893223": {
269 | name: "themes/light/status/status-warning",
270 | mapsToName: "themes/dark/status/status-warning",
271 | mapsToKey: "f719fb8e7bf04342010ecb37165e55aa8a638d35"
272 | },
273 | // Other
274 | "3c098a8d09acbd25ef37e7fc0b657c2dc78f243e": {
275 | name: "themes/light/other/channeltextarea-background",
276 | mapsToName: "themes/dark/other/channeltextarea-background",
277 | mapsToKey: "6c54be693a4bbdff6fa4c02f672bc5c9e4654f8b"
278 | },
279 | d1dbae483f4eefcf5adccfbba8e6d50dbef1ec27: {
280 | name: "themes/light/other/focus-primary",
281 | mapsToName: "themes/dark/other/focus-primary",
282 | mapsToKey: "a4d76cf75156ab760df1685a30dadab20724010e"
283 | },
284 | bbdc5cb26595f77283b8dfe51e659c5bfdc6a2d0: {
285 | name: "themes/light/other/control-brand-foreground",
286 | mapsToName: "themes/dark/other/control-brand-foreground",
287 | mapsToKey: "7337ac931b2c9b699d44e6e783637e5afac50298"
288 | },
289 | "084969be9bfee752064df1c504b6ba07a8d727ad": {
290 | name: "themes/light/other/scrollbar-thin-thumb",
291 | mapsToName: "themes/dark/other/scrollbar-thin-thumb",
292 | mapsToKey: "a926774d558d0e70f505df697c21c12dc4270206"
293 | },
294 | "6436d02f21d749b84cbd8736bd453dad1c4ac3ab": {
295 | name: "themes/light/other/scrollbar-auto-thumb",
296 | mapsToName: "themes/dark/other/scrollbar-auto-thumb",
297 | mapsToKey: "2ab24b1a3901fae7960deb8a36e49f0d6b1732af"
298 | },
299 | "54fb146609c07fba199d4066f8c2ce14829a0d0a": {
300 | name: "themes/light/other/scrollbar-auto-track",
301 | mapsToName: "themes/dark/other/scrollbar-auto-track",
302 | mapsToKey: "d509bf14b1c3aac55dc0fd6b822f628956ad80c3"
303 | },
304 | // Effects
305 | bf64ca51f902a903935680f692618a5eba4ea894: {
306 | name: "Border Elevation / Light",
307 | mapsToName: "Border Elevation / Dark",
308 | mapsToKey: "b7edafef4513a59a40c8ba7adb382a0b6d3313ff"
309 | },
310 | "30f011bbe03506a59052d7f8435cc1ec3b743b19": {
311 | name: "High Elevation / Light",
312 | mapsToName: "High Elevation / Dark",
313 | mapsToKey: "67aabb2beb8092e4c0094e0175657bb0758e6ba8"
314 | },
315 | // Deprecated
316 | "5afa1524777579ea2eebc983f3210547c838fd3a": {
317 | name: "BETA_DEPRECATED/header/header-primary",
318 | mapsToName: "themes/dark/header/header-primary",
319 | mapsToKey: "5c1691cbeaaf4270107d34f1a12f02fdd04afa02"
320 | },
321 | "206fc2ae47513da5db7cd705e758593221bb4b63": {
322 | name: "BETA_DEPRECATED/header/header-secondary",
323 | mapsToName: "themes/dark/header/header-secondary",
324 | mapsToKey: "bc090cb3b1c7313ae276acbd791b5b87b478ec59"
325 | },
326 | ac344309d7e7d20a6b518d49d1501e3d134d996b: {
327 | name: "BETA_DEPRECATED/background/background-primary",
328 | mapsToName: "themes/dark/background/background-primary",
329 | mapsToKey: "4b93d40f61be15e255e87948a715521c3ae957e6"
330 | },
331 | "5100d653a726bf86e3b43a3349c396474bd63950": {
332 | name: "BETA_DEPRECATED/background/background-secondary",
333 | mapsToName: "themes/dark/background/background-secondary",
334 | mapsToKey: "fb1358e5bd6dec072801298238cf49ff77b79a4b"
335 | },
336 | "6e18949a990499bc0af852de9de4f2e378b1f954": {
337 | name: "BETA_DEPRECATED/text/text-normal",
338 | mapsToName: "themes/dark/text/text-normal",
339 | mapsToKey: "5c77a96137b698b5575557c069cabd6877d66e1e"
340 | },
341 | "15d9230a1d41d9acd21b63012f86613f879cfaae": {
342 | name: "BETA_DEPRECATED/text/text-muted",
343 | mapsToName: "themes/dark/text/text-muted",
344 | mapsToKey: "5d84ad92f3ad152f196e2093a3c0542a08dfba11"
345 | }
346 | };
347 |
348 | export { lightTheme };
349 |
--------------------------------------------------------------------------------
/src/plugin/dark-to-light-theme.ts:
--------------------------------------------------------------------------------
1 | // For mapping from dark to light theme.
2 | const darkTheme = {
3 | // Components (swaps instances rather ie Name / Dark to Name / Light)
4 | f0d4aa5e63fff4392e3b3c22884523369f5d0424: {
5 | componentName: "iPhone X Status Bar / Dark",
6 | mapsToKey: "33425bd93c1b8cea071df9b5297f0b19583a643b"
7 | },
8 | "5b8dce7a790466da546d319a69f5de220e1a66f1": {
9 | componentName: "iPhone X Home Indicator / Dark",
10 | mapsToKey: "0489bde7fd0346a97eff3170167714838a8ffb9c"
11 | },
12 | "3ee4cf479eefd5e181ff4abd1c982011438e692d": {
13 | componentName: "System (Dark) / Numeric Keyboard",
14 | mapsToKey: "867fa47defeb07293aa37e5467e4ca487019dd78"
15 | },
16 | e4dcbeb8549332e4c969ef4d0d302e75a7932c25: {
17 | componentName: "System (Dark) / Keyboard",
18 | mapsToKey: "bca69f03db8e938cd78ca91c84e35553804ca8c1"
19 | },
20 | e9622ab25248f31fb02b6faa00308b8faa4acb3e: {
21 | componentName: "Header / Guild / Dark",
22 | mapsToKey: "00230f03c08e00a787e9c2659c3165bcad7ae06b"
23 | },
24 | "4592fb98edf78fdeea07d23445de948286e7c5f2": {
25 | componentName: "Header / DM",
26 | mapsToKey: "ff8b5c71a60fc212647c40e481c0bf1886a3ec85"
27 | },
28 | "46d6bed4edd9482b1452afab5ab0292b516c9e09": {
29 | componentName: "Navigation Tab / Dark",
30 | mapsToKey: "64d16554561a7496b2d23854074ce923655918e0"
31 | },
32 | "9e0a9f99024fb9baedcacbb123c84d7cc4b8f87a": {
33 | componentName: "Guild Selected / Dark",
34 | mapsToKey: "a9231a3d9fac5b7d26d56904c7b28d5d00bfff97"
35 | },
36 | c25d89953041d095215c972fa55dc6f7776d9a54: {
37 | componentName: "Messages Selected / Dark",
38 | mapsToKey: "8c80d5bd7bdd80acec9793a719f6caaebba1c6ee"
39 | },
40 | // Android System Components
41 | "3588fe4d5a302b2fca2be2b0cb5c12e2a2f41c05": {
42 | componentName: "Status Bar / Dark",
43 | mapsToKey: "790d7d3d884a6d3dadc79bf3c48a4918f4c16ba7"
44 | },
45 | "43c14ca23834d2aa3bf1e027a0635c7393e87378": {
46 | componentName: "Navigation Tab / Dark",
47 | mapsToKey: "bd56c3e89162d8274c4ca591f2ca1e1064658570"
48 | },
49 | // Desktop Components
50 | d31d651767116b73f9209c5362669782ff3a8a25: {
51 | componentName: "Windows Bar / Dark",
52 | mapsToKey: "05fc2a6b4207baa8f672dc9e8a5d750c5d60711b"
53 | },
54 | // Headings
55 | "5c1691cbeaaf4270107d34f1a12f02fdd04afa02": {
56 | name: "Dark / Header / Primary (White)",
57 | mapsToName: "Light / Header / Primary (900)",
58 | mapsToKey: "b19a14675b8adeb1528ab5f84e57b2eeed10d46c"
59 | },
60 | "text #ffffff": {
61 | name: "Unstyled Dark Header",
62 | mapsToName: "Light / Header / Primary (900)",
63 | mapsToKey: "b19a14675b8adeb1528ab5f84e57b2eeed10d46c"
64 | },
65 | "text #b9bbbe": {
66 | name: "Unstyled Dark Secondary Header",
67 | mapsToName: "themes/light/header/header-secondary",
68 | mapsToKey: "608f2ea1aa64ff7f202e8c22cc4147a02be9d85b"
69 | },
70 | "text #a3a6aa": {
71 | name: "Unstyled Dark Muted",
72 | mapsToName: "themes/light/text/text-muted",
73 | mapsToKey: "7d8703ec132ddaf6968f6d190d1e80031c559d7c"
74 | },
75 | bc090cb3b1c7313ae276acbd791b5b87b478ec59: {
76 | name: "Dark / Header / Secondary (300)",
77 | mapsToName: "Light / Header / Secondary (600)",
78 | mapsToKey: "608f2ea1aa64ff7f202e8c22cc4147a02be9d85b"
79 | },
80 | // Text
81 | "5c77a96137b698b5575557c069cabd6877d66e1e": {
82 | name: "Dark / Text / Normal (200)",
83 | mapsToName: "Light / Text / Normal (700)",
84 | mapsToKey: "546c7d46e754ac2b23b338783d72f206b77b6436"
85 | },
86 | "5d84ad92f3ad152f196e2093a3c0542a08dfba11": {
87 | name: "Dark / Text / Muted (400)",
88 | mapsToName: "Light / Text / Muted (500)",
89 | mapsToKey: "7d8703ec132ddaf6968f6d190d1e80031c559d7c"
90 | },
91 | bf03232753079bdd5bec6c55343b659876b5283f: {
92 | name: "Dark / Text / Link",
93 | mapsToName: "Light / Text / Link",
94 | mapsToKey: "64d3058dd508a4985670b2d19418a06a3503c9c2"
95 | },
96 | "6e4aef7677e2ea82c87465276522da7ef5a07121": {
97 | name: "themes/dark/text/text-brand",
98 | mapsToName: "themes/light/text/text-brand",
99 | mapsToKey: "15320fd498dcd4e113c5bd587dca2d11d4492e84"
100 | },
101 | "094cbaac0817be7bbfd8292cb98fc1e515e7ea0e": {
102 | name: "themes/dark/text/text-danger",
103 | mapsToName: "themes/light/text/text-danger",
104 | mapsToKey: "c8d237080d38671193403b49cdc6a5778a14bf45"
105 | },
106 | df0622bb33232fe041c468e8d3dd37e5428b10e7: {
107 | name: "themes/dark/text/text-warning",
108 | mapsToName: "themes/light/text/text-warning",
109 | mapsToKey: "0d95a7d4d30ef99ebd04abd5b2dd4708913f765b"
110 | },
111 | "7733117cf1ef570b77332c86ba783af6cb735fc1": {
112 | name: "themes/dark/text/text-positive",
113 | mapsToName: "themes/light/text/text-positive",
114 | mapsToKey: "71f64b08bdec4daf747a850b128e0994c4593c04"
115 | },
116 | // Interactive Text & Icons
117 | "287463bade90c1eed5ea4cb0b5d63794daa8aec2": {
118 | name: "Dark / Interactive Text & Icons / Normal (300)",
119 | mapsToName: "Light / Interactive Text & Icons / Normal (600)",
120 | mapsToKey: "9c23a031773711e026394f4354661c37ee5b4682"
121 | },
122 | "boolean_operation #b9bbbe": {
123 | name: "Dark / Interactive Text & Icons / Normal (300)",
124 | mapsToName: "Light / Interactive Text & Icons / Normal (600)",
125 | mapsToKey: "9c23a031773711e026394f4354661c37ee5b4682"
126 | },
127 | "boolean_operation #757575": {
128 | name: "Dark / Interactive Text & Icons / Normal (300)",
129 | mapsToName: "Light / Interactive Text & Icons / Normal (600)",
130 | mapsToKey: "9c23a031773711e026394f4354661c37ee5b4682"
131 | },
132 | "vector #757575": {
133 | name: "Dark / Interactive Text & Icons / Normal (300)",
134 | mapsToName: "Light / Interactive Text & Icons / Normal (600)",
135 | mapsToKey: "9c23a031773711e026394f4354661c37ee5b4682"
136 | },
137 | "vector #b9bbbe": {
138 | name: "Dark / Interactive Text & Icons / Normal (300)",
139 | mapsToName: "Light / Interactive Text & Icons / Normal (600)",
140 | mapsToKey: "9c23a031773711e026394f4354661c37ee5b4682"
141 | },
142 | "502dcdf04992818dcbaed125ad711b446dee4c68": {
143 | name: "Dark / Interactive Text & Icons / Hover (200)",
144 | mapsToName: "themes/light/interactive/interactive-hover",
145 | mapsToKey: "e9542e95adf3bbe74286c2cf279fee64f7ba3279"
146 | },
147 | "3eddc15e90bbd7064aea7cc13dc13e23a712f0b0": {
148 | name: "Dark / Interactive Text & Icons / Active (White)",
149 | mapsToName: "Light / Interactive Text & Icons / Active (900)",
150 | mapsToKey: "620c98e8f9255a6107dee91745669e5b702b413c"
151 | },
152 | "boolean_operation #ffffff": {
153 | name: "Dark / Interactive Text & Icons / Active (White)",
154 | mapsToName: "Light / Interactive Text & Icons / Active (900)",
155 | mapsToKey: "620c98e8f9255a6107dee91745669e5b702b413c"
156 | },
157 | fa698aa2a724522a7c29efb0a662aec75a1be5a1: {
158 | name: "Dark / Interactive Text & Icons / Muted (500)",
159 | mapsToName: "Light / Interactive Text & Icons / Muted (300)",
160 | mapsToKey: "9328cd78a39149b070d68f98d9fe4df7a92bf67d"
161 | },
162 | // Backgrounds
163 | "4b93d40f61be15e255e87948a715521c3ae957e6": {
164 | name: "Dark / Background / Primary (600)",
165 | mapsToName: "Light / Background / Primary (White)",
166 | mapsToKey: "2449a2983d43793d80baa20c6c60e8a48e7f3a0c"
167 | },
168 | "frame #36393f": {
169 | name: "Dark Primary Background",
170 | mapsToName: "Light / Background / Primary (White)",
171 | mapsToKey: "2449a2983d43793d80baa20c6c60e8a48e7f3a0c"
172 | },
173 | "rectangle #36393f": {
174 | name: "Dark Primary Background",
175 | mapsToName: "Dark / Background / Primary (600)",
176 | mapsToKey: "2449a2983d43793d80baa20c6c60e8a48e7f3a0c"
177 | },
178 | "frame #5865f2": {
179 | name: "Unstyled Brand",
180 | mapsToName: "other/blurple (brand-500)",
181 | mapsToKey: "25b165222f45fd70dc3c8e68d1a25f8d379a597d"
182 | },
183 | "rectangle #5865f2": {
184 | name: "Unstyled Brand",
185 | mapsToName: "other/blurple (brand-500)",
186 | mapsToKey: "25b165222f45fd70dc3c8e68d1a25f8d379a597d"
187 | },
188 | fb1358e5bd6dec072801298238cf49ff77b79a4b: {
189 | name: "Dark / Background / Secondary (630)",
190 | mapsToName: "Light / Background / Primary (White)",
191 | mapsToKey: "83704278c845a6a7ceb1f837387972ccb6d41960"
192 | },
193 | abf9ad88ae1ade1a4b945b012f0965c9cdc068c9: {
194 | name: "Dark / Background / Secondary Alternate",
195 | mapsToName: "Light / Background / Secondary Alternate",
196 | mapsToKey: "6acd84c794796d112d4e9d22c4c8a5cae940a61d"
197 | },
198 | ef179b6abe6cb8779857e05a6333d33f7a2b9320: {
199 | name: "Dark / Background / Tertiary (700)",
200 | mapsToName: "Light / Background / Tertiary (200)",
201 | mapsToKey: "dbd02a76b7b77c1976114c04068f0fbc22015fab"
202 | },
203 | "3dd0e30ce0a8287eb91ec1fbeff92031e634ed01": {
204 | name: "Dark / Background / Accent (500)",
205 | mapsToName: "Light / Background / Accent (500)",
206 | mapsToKey: "7a199ce029a847f3a361dfb6a6e0ee4e4ba84d4f"
207 | },
208 | "11516f4b43f381afb5a6bdf2c34b9437f0eecde1": {
209 | name: "Dark / Background / Floating (800)",
210 | mapsToName: "Light / Background / Floating (White)",
211 | mapsToKey: "6c8b08a42f9614842e880bf7bb795014d8fbae94"
212 | },
213 | bfcdf063eb2c1edb446ba5d7880da6a324cc9b4f: {
214 | name: "Dark / Override / Read Channels",
215 | mapsToName: "Light / Override / Read Channels 360",
216 | mapsToKey: "634ef95b53ab529a774f27ed16be07c0b3fb3a5f"
217 | },
218 | b659c283950f8b335922f52e40cefd3cf679d297: {
219 | name: "themes/dark/background/status-danger-background",
220 | mapsToName: "themes/light/background/status-danger-background",
221 | mapsToKey: "c592ea0b26929cf1374f973b857027dbd21ffb12"
222 | },
223 | "3dbd679897876b69bc9cc8fa38be83c525ac5ed5": {
224 | name: "themes/dark/background/status-warning-background",
225 | mapsToName: "themes/light/background/status-warning-background",
226 | mapsToKey: "45f2139348b50263fda4704d4a9accea74540dcc"
227 | },
228 | "746e170ac6e7ba80d171f01313735a3ec5535ef8": {
229 | name: "themes/dark/background/status-positive-background",
230 | mapsToName: "themes/light/background/status-positive-background",
231 | mapsToKey: "2a135fa63c0cea473936ced51ccd767b2f156739"
232 | },
233 | da21c08d5f887ae8d6195d7f8a7585219d670b93: {
234 | name: "themes/dark/background/background-mentioned",
235 | mapsToName: "themes/light/background/background-mentioned",
236 | mapsToKey: "30d44092c13231213143b50015907463dd1b6211"
237 | },
238 | "39c91bf62536cb1c6f51087853c35afcc6462bac": {
239 | name: "themes/dark/background/background-mentioned-hover",
240 | mapsToName: "themes/light/background/background-mentioned-hover",
241 | mapsToKey: "4d15ee684eb9fd6cb114d7fb585c83c9b0a598fd"
242 | },
243 | "1054e0c4bc3e52ae2c7c48aa0d0f95ed5d998587": {
244 | name: "themes/dark/background/background-message-hover",
245 | mapsToName: "themes/light/background/background-message-hover",
246 | mapsToKey: "440a2d66490b7162417c740e66355f39d7b9e41a"
247 | },
248 | "72a70771ff2a268130e7352250f374722f4d8bfe": {
249 | name: "themes/dark/background/background-mobile-primary",
250 | mapsToName: "themes/light/background/background-mobile-primary",
251 | mapsToKey: "5747d5e2f1e6047746c77e9368e8d21324eb93d9"
252 | },
253 | "251f85bc338c5411608c2dc141a538305ab6b4c1": {
254 | name: "themes/dark/background/background-mobile-secondary",
255 | mapsToName: "themes/light/background/background-mobile-secondary",
256 | mapsToKey: "de9f518c35096095c02c215543174a04900b07d7"
257 | },
258 | de9f518c35096095c02c215543174a04900b07d7: {
259 | name: "themes/light/background/background-mobile-secondary",
260 | mapsToName: "themes/dark/background/background-mobile-secondary",
261 | mapsToKey: "251f85bc338c5411608c2dc141a538305ab6b4c1"
262 | },
263 | "1e1caa8f31ed3bb7ce6e6ce20dfe3187b20766c8": {
264 | name: "themes/dark/background/background-nested-floating",
265 | mapsToName: "themes/light/background/background-nested-floating",
266 | mapsToKey: "1fae53b19be2fe85aa44529cd3243c7b280173f1"
267 | },
268 | // Background Modifiers
269 | d6c9270834b11c99ee651f0f5072ad2c63701165: {
270 | name: "Dark / Background Mod / Hover",
271 | mapsToName: "Light / Background Mod / Hover",
272 | mapsToKey: "35307396ae29aaeb583ae65891c69ec689f0c41e"
273 | },
274 | bcf890d7a215c65deef97fb3d3f5bcebc9869bab: {
275 | name: "Dark / Background Mod / Active",
276 | mapsToName: "Light / Background Mod / Active",
277 | mapsToKey: "ddadf76919d9bacb925242a024dc1e2f5f517a46"
278 | },
279 | ce012db42f35fb58b4fe1d6d8b46c4905a8fad0a: {
280 | name: "Dark / Background Mod / Selected",
281 | mapsToName: "Light / Background Mod / Selected",
282 | mapsToKey: "5af2eaf14901472c26b641997796bdba76ee1794"
283 | },
284 | a6a3dc153f0e589408186176ebf8f20ed2f9bda3: {
285 | name: "Dark / Background Mod / Accent",
286 | mapsToName: "Light / Background Mod / Accent",
287 | mapsToKey: "08c7091f8d6950dc3f616afe8ed45b086f9124c7"
288 | },
289 | // Status
290 | "61c493d9d14f2a5ae52c2037149773f0cd7690a5": {
291 | name: "themes/dark/status/status-positive",
292 | mapsToName: "themes/light/status/status-positive",
293 | mapsToKey: "6faa6d09b47caeb32fa0f5f81c561dcb7d68e9b1"
294 | },
295 | "0ff4d563aae53dd8012f78a67f9fd182693a0f21": {
296 | name: "themes/dark/status/status-danger",
297 | mapsToName: "themes/light/status/status-danger",
298 | mapsToKey: "0c9cfa27f153e6a5a9954242bb6ae3cac02d4468"
299 | },
300 | f719fb8e7bf04342010ecb37165e55aa8a638d35: {
301 | name: "themes/dark/status/status-warning",
302 | mapsToName: "themes/light/status/status-warning",
303 | mapsToKey: "9fa2f99cffe7ba587f259e98fb4de12c0b893223"
304 | },
305 | // Other
306 | "6c54be693a4bbdff6fa4c02f672bc5c9e4654f8b": {
307 | name: "themes/dark/other/channeltextarea-background",
308 | mapsToName: "themes/light/other/channeltextarea-background",
309 | mapsToKey: "3c098a8d09acbd25ef37e7fc0b657c2dc78f243e"
310 | },
311 | a4d76cf75156ab760df1685a30dadab20724010e: {
312 | name: "themes/dark/other/focus-primary0",
313 | mapsToName: "themes/light/other/focus-primary",
314 | mapsToKey: "d1dbae483f4eefcf5adccfbba8e6d50dbef1ec27"
315 | },
316 | "7337ac931b2c9b699d44e6e783637e5afac50298": {
317 | name: "themes/dark/other/control-brand-foreground",
318 | mapsToName: "themes/light/other/control-brand-foreground",
319 | mapsToKey: "bbdc5cb26595f77283b8dfe51e659c5bfdc6a2d0"
320 | },
321 | a926774d558d0e70f505df697c21c12dc4270206: {
322 | name: "themes/dark/other/scrollbar-thin-thumb",
323 | mapsToName: "themes/light/other/scrollbar-thin-thumb",
324 | mapsToKey: "084969be9bfee752064df1c504b6ba07a8d727ad"
325 | },
326 | "2ab24b1a3901fae7960deb8a36e49f0d6b1732af": {
327 | name: "themes/dark/other/scrollbar-auto-thumb",
328 | mapsToName: "themes/light/other/scrollbar-auto-thumb",
329 | mapsToKey: "6436d02f21d749b84cbd8736bd453dad1c4ac3ab"
330 | },
331 | d509bf14b1c3aac55dc0fd6b822f628956ad80c3: {
332 | name: "themes/dark/other/scrollbar-auto-track",
333 | mapsToName: "themes/light/other/scrollbar-auto-track",
334 | mapsToKey: "54fb146609c07fba199d4066f8c2ce14829a0d0a"
335 | },
336 | // Effects
337 | b7edafef4513a59a40c8ba7adb382a0b6d3313ff: {
338 | name: "Border Elevation / Dark",
339 | mapsToName: "Border Elevation / Light",
340 | mapsToKey: "bf64ca51f902a903935680f692618a5eba4ea894"
341 | },
342 | "67aabb2beb8092e4c0094e0175657bb0758e6ba8": {
343 | name: "High Elevation / Dark",
344 | mapsToName: "High Elevation / Light",
345 | mapsToKey: "30f011bbe03506a59052d7f8435cc1ec3b743b19"
346 | },
347 | // Deprecated
348 | d104f004f79d0e422c44d14efdd5e527d57a185f: {
349 | name: "BETA_DEPRECATED/header/header-primary",
350 | mapsToName: "themes/light/header/header-primary",
351 | mapsToKey: "b19a14675b8adeb1528ab5f84e57b2eeed10d46c"
352 | },
353 | "1aee47626b0083fe2830fb8262d9ba2d1790949f": {
354 | name: "BETA_DEPRECATED/header/header-secondary",
355 | mapsToName: "themes/light/header/header-secondary",
356 | mapsToKey: "608f2ea1aa64ff7f202e8c22cc4147a02be9d85b"
357 | },
358 | bd768f7dda36913ff061b1f82a273264e710e9e0: {
359 | name: "BETA_DEPRECATED/background/background-primary",
360 | mapsToName: "themes/light/background/background-primary",
361 | mapsToKey: "2449a2983d43793d80baa20c6c60e8a48e7f3a0c"
362 | },
363 | e8c94a8857a45794172b8e7e1f4392b388403cfd: {
364 | name: "BETA_DEPRECATED/background/background-secondary",
365 | mapsToName: "themes/light/background/background-secondary",
366 | mapsToKey: "83704278c845a6a7ceb1f837387972ccb6d41960"
367 | },
368 | "8ed7c2cbc95b1ef5dbd750e29446fb30f5e2c7d6": {
369 | name: "themes/light/text/text-normal",
370 | mapsToName: "themes/light/background/background-primary",
371 | mapsToKey: "546c7d46e754ac2b23b338783d72f206b77b6436"
372 | },
373 | "7a18a8af03b002b7433560a024d0416017a927bd": {
374 | name: "BETA_DEPRECATED/text/text-muted",
375 | mapsToName: "themes/light/text/text-muted",
376 | mapsToKey: "7d8703ec132ddaf6968f6d190d1e80031c559d7c"
377 | }
378 | };
379 |
380 | export { darkTheme };
381 |
--------------------------------------------------------------------------------
/figma.d.ts:
--------------------------------------------------------------------------------
1 | // Figma Plugin API version 1, update 10
2 |
3 | declare global {
4 | // Global variable with Figma's plugin API.
5 | const figma: PluginAPI;
6 | const __html__: string;
7 |
8 | interface PluginAPI {
9 | readonly apiVersion: "1.0.0";
10 | readonly command: string;
11 | readonly viewport: ViewportAPI;
12 | closePlugin(message?: string): void;
13 |
14 | notify(message: string, options?: NotificationOptions): NotificationHandler;
15 |
16 | showUI(html: string, options?: ShowUIOptions): void;
17 | readonly ui: UIAPI;
18 |
19 | readonly clientStorage: ClientStorageAPI;
20 |
21 | getNodeById(id: string): BaseNode | null;
22 | getStyleById(id: string): BaseStyle | null;
23 |
24 | readonly root: DocumentNode;
25 | currentPage: PageNode;
26 |
27 | on(
28 | type: "selectionchange" | "currentpagechange" | "close",
29 | callback: () => void
30 | ): void;
31 | once(
32 | type: "selectionchange" | "currentpagechange" | "close",
33 | callback: () => void
34 | ): void;
35 | off(
36 | type: "selectionchange" | "currentpagechange" | "close",
37 | callback: () => void
38 | ): void;
39 |
40 | readonly mixed: unique symbol;
41 |
42 | createRectangle(): RectangleNode;
43 | createLine(): LineNode;
44 | createEllipse(): EllipseNode;
45 | createPolygon(): PolygonNode;
46 | createStar(): StarNode;
47 | createVector(): VectorNode;
48 | createText(): TextNode;
49 | createFrame(): FrameNode;
50 | createComponent(): ComponentNode;
51 | createPage(): PageNode;
52 | createSlice(): SliceNode;
53 | /**
54 | * [DEPRECATED]: This API often fails to create a valid boolean operation. Use figma.union, figma.subtract, figma.intersect and figma.exclude instead.
55 | */
56 | createBooleanOperation(): BooleanOperationNode;
57 |
58 | createPaintStyle(): PaintStyle;
59 | createTextStyle(): TextStyle;
60 | createEffectStyle(): EffectStyle;
61 | createGridStyle(): GridStyle;
62 |
63 | // The styles are returned in the same order as displayed in the UI. Only
64 | // local styles are returned. Never styles from team library.
65 | getLocalPaintStyles(): PaintStyle[];
66 | getLocalTextStyles(): TextStyle[];
67 | getLocalEffectStyles(): EffectStyle[];
68 | getLocalGridStyles(): GridStyle[];
69 |
70 | importComponentByKeyAsync(key: string): Promise;
71 | importStyleByKeyAsync(key: string): Promise;
72 |
73 | listAvailableFontsAsync(): Promise;
74 | loadFontAsync(fontName: FontName): Promise;
75 | readonly hasMissingFont: boolean;
76 |
77 | createNodeFromSvg(svg: string): FrameNode;
78 |
79 | createImage(data: Uint8Array): Image;
80 | getImageByHash(hash: string): Image;
81 |
82 | group(
83 | nodes: ReadonlyArray,
84 | parent: BaseNode & ChildrenMixin,
85 | index?: number
86 | ): GroupNode;
87 | flatten(
88 | nodes: ReadonlyArray,
89 | parent?: BaseNode & ChildrenMixin,
90 | index?: number
91 | ): VectorNode;
92 |
93 | union(
94 | nodes: ReadonlyArray,
95 | parent: BaseNode & ChildrenMixin,
96 | index?: number
97 | ): BooleanOperationNode;
98 | subtract(
99 | nodes: ReadonlyArray,
100 | parent: BaseNode & ChildrenMixin,
101 | index?: number
102 | ): BooleanOperationNode;
103 | intersect(
104 | nodes: ReadonlyArray,
105 | parent: BaseNode & ChildrenMixin,
106 | index?: number
107 | ): BooleanOperationNode;
108 | exclude(
109 | nodes: ReadonlyArray,
110 | parent: BaseNode & ChildrenMixin,
111 | index?: number
112 | ): BooleanOperationNode;
113 | }
114 |
115 | interface ClientStorageAPI {
116 | getAsync(key: string): Promise;
117 | setAsync(key: string, value: any): Promise;
118 | }
119 |
120 | interface NotificationOptions {
121 | timeout?: number;
122 | }
123 |
124 | interface NotificationHandler {
125 | cancel: () => void;
126 | }
127 |
128 | interface ShowUIOptions {
129 | visible?: boolean;
130 | width?: number;
131 | height?: number;
132 | }
133 |
134 | interface UIPostMessageOptions {
135 | origin?: string;
136 | }
137 |
138 | interface OnMessageProperties {
139 | origin: string;
140 | }
141 |
142 | type MessageEventHandler = (
143 | pluginMessage: any,
144 | props: OnMessageProperties
145 | ) => void;
146 |
147 | interface UIAPI {
148 | show(): void;
149 | hide(): void;
150 | resize(width: number, height: number): void;
151 | close(): void;
152 |
153 | postMessage(pluginMessage: any, options?: UIPostMessageOptions): void;
154 | onmessage: MessageEventHandler | undefined;
155 | on(type: "message", callback: MessageEventHandler): void;
156 | once(type: "message", callback: MessageEventHandler): void;
157 | off(type: "message", callback: MessageEventHandler): void;
158 | }
159 |
160 | interface ViewportAPI {
161 | center: { x: number; y: number };
162 | zoom: number;
163 | scrollAndZoomIntoView(nodes: ReadonlyArray): void;
164 | }
165 |
166 | ////////////////////////////////////////////////////////////////////////////////
167 | // Datatypes
168 |
169 | type Transform = [[number, number, number], [number, number, number]];
170 |
171 | interface Vector {
172 | readonly x: number;
173 | readonly y: number;
174 | }
175 |
176 | interface RGB {
177 | readonly r: number;
178 | readonly g: number;
179 | readonly b: number;
180 | }
181 |
182 | interface RGBA {
183 | readonly r: number;
184 | readonly g: number;
185 | readonly b: number;
186 | readonly a: number;
187 | }
188 |
189 | interface FontName {
190 | readonly family: string;
191 | readonly style: string;
192 | }
193 |
194 | type TextCase = "ORIGINAL" | "UPPER" | "LOWER" | "TITLE";
195 |
196 | type TextDecoration = "NONE" | "UNDERLINE" | "STRIKETHROUGH";
197 |
198 | interface ArcData {
199 | readonly startingAngle: number;
200 | readonly endingAngle: number;
201 | readonly innerRadius: number;
202 | }
203 |
204 | interface ShadowEffect {
205 | readonly type: "DROP_SHADOW" | "INNER_SHADOW";
206 | readonly color: RGBA;
207 | readonly offset: Vector;
208 | readonly radius: number;
209 | readonly visible: boolean;
210 | readonly blendMode: BlendMode;
211 | }
212 |
213 | interface BlurEffect {
214 | readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR";
215 | readonly radius: number;
216 | readonly visible: boolean;
217 | }
218 |
219 | type Effect = ShadowEffect | BlurEffect;
220 |
221 | type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE";
222 |
223 | interface Constraints {
224 | readonly horizontal: ConstraintType;
225 | readonly vertical: ConstraintType;
226 | }
227 |
228 | interface ColorStop {
229 | readonly position: number;
230 | readonly color: RGBA;
231 | }
232 |
233 | interface ImageFilters {
234 | readonly exposure?: number;
235 | readonly contrast?: number;
236 | readonly saturation?: number;
237 | readonly temperature?: number;
238 | readonly tint?: number;
239 | readonly highlights?: number;
240 | readonly shadows?: number;
241 | }
242 |
243 | interface SolidPaint {
244 | readonly type: "SOLID";
245 | readonly color: RGB;
246 |
247 | readonly visible?: boolean;
248 | readonly opacity?: number;
249 | readonly blendMode?: BlendMode;
250 | }
251 |
252 | interface GradientPaint {
253 | readonly type:
254 | | "GRADIENT_LINEAR"
255 | | "GRADIENT_RADIAL"
256 | | "GRADIENT_ANGULAR"
257 | | "GRADIENT_DIAMOND";
258 | readonly gradientTransform: Transform;
259 | readonly gradientStops: ReadonlyArray;
260 |
261 | readonly visible?: boolean;
262 | readonly opacity?: number;
263 | readonly blendMode?: BlendMode;
264 | }
265 |
266 | interface ImagePaint {
267 | readonly type: "IMAGE";
268 | readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE";
269 | readonly imageHash: string | null;
270 | readonly imageTransform?: Transform; // setting for "CROP"
271 | readonly scalingFactor?: number; // setting for "TILE"
272 | readonly filters?: ImageFilters;
273 |
274 | readonly visible?: boolean;
275 | readonly opacity?: number;
276 | readonly blendMode?: BlendMode;
277 | }
278 |
279 | type Paint = SolidPaint | GradientPaint | ImagePaint;
280 |
281 | interface Guide {
282 | readonly axis: "X" | "Y";
283 | readonly offset: number;
284 | }
285 |
286 | interface RowsColsLayoutGrid {
287 | readonly pattern: "ROWS" | "COLUMNS";
288 | readonly alignment: "MIN" | "MAX" | "STRETCH" | "CENTER";
289 | readonly gutterSize: number;
290 |
291 | readonly count: number; // Infinity when "Auto" is set in the UI
292 | readonly sectionSize?: number; // Not set for alignment: "STRETCH"
293 | readonly offset?: number; // Not set for alignment: "CENTER"
294 |
295 | readonly visible?: boolean;
296 | readonly color?: RGBA;
297 | }
298 |
299 | interface GridLayoutGrid {
300 | readonly pattern: "GRID";
301 | readonly sectionSize: number;
302 |
303 | readonly visible?: boolean;
304 | readonly color?: RGBA;
305 | }
306 |
307 | type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid;
308 |
309 | interface ExportSettingsConstraints {
310 | readonly type: "SCALE" | "WIDTH" | "HEIGHT";
311 | readonly value: number;
312 | }
313 |
314 | interface ExportSettingsImage {
315 | readonly format: "JPG" | "PNG";
316 | readonly contentsOnly?: boolean; // defaults to true
317 | readonly suffix?: string;
318 | readonly constraint?: ExportSettingsConstraints;
319 | }
320 |
321 | interface ExportSettingsSVG {
322 | readonly format: "SVG";
323 | readonly contentsOnly?: boolean; // defaults to true
324 | readonly suffix?: string;
325 | readonly svgOutlineText?: boolean; // defaults to true
326 | readonly svgIdAttribute?: boolean; // defaults to false
327 | readonly svgSimplifyStroke?: boolean; // defaults to true
328 | }
329 |
330 | interface ExportSettingsPDF {
331 | readonly format: "PDF";
332 | readonly contentsOnly?: boolean; // defaults to true
333 | readonly suffix?: string;
334 | }
335 |
336 | type ExportSettings =
337 | | ExportSettingsImage
338 | | ExportSettingsSVG
339 | | ExportSettingsPDF;
340 |
341 | type WindingRule = "NONZERO" | "EVENODD";
342 |
343 | interface VectorVertex {
344 | readonly x: number;
345 | readonly y: number;
346 | readonly strokeCap?: StrokeCap;
347 | readonly strokeJoin?: StrokeJoin;
348 | readonly cornerRadius?: number;
349 | readonly handleMirroring?: HandleMirroring;
350 | }
351 |
352 | interface VectorSegment {
353 | readonly start: number;
354 | readonly end: number;
355 | readonly tangentStart?: Vector; // Defaults to { x: 0, y: 0 }
356 | readonly tangentEnd?: Vector; // Defaults to { x: 0, y: 0 }
357 | }
358 |
359 | interface VectorRegion {
360 | readonly windingRule: WindingRule;
361 | readonly loops: ReadonlyArray>;
362 | }
363 |
364 | interface VectorNetwork {
365 | readonly vertices: ReadonlyArray;
366 | readonly segments: ReadonlyArray;
367 | readonly regions?: ReadonlyArray; // Defaults to []
368 | }
369 |
370 | interface VectorPath {
371 | readonly windingRule: WindingRule | "NONE";
372 | readonly data: string;
373 | }
374 |
375 | type VectorPaths = ReadonlyArray;
376 |
377 | interface LetterSpacing {
378 | readonly value: number;
379 | readonly unit: "PIXELS" | "PERCENT";
380 | }
381 |
382 | type LineHeight =
383 | | {
384 | readonly value: number;
385 | readonly unit: "PIXELS" | "PERCENT";
386 | }
387 | | {
388 | readonly unit: "AUTO";
389 | };
390 |
391 | type BlendMode =
392 | | "PASS_THROUGH"
393 | | "NORMAL"
394 | | "DARKEN"
395 | | "MULTIPLY"
396 | | "LINEAR_BURN"
397 | | "COLOR_BURN"
398 | | "LIGHTEN"
399 | | "SCREEN"
400 | | "LINEAR_DODGE"
401 | | "COLOR_DODGE"
402 | | "OVERLAY"
403 | | "SOFT_LIGHT"
404 | | "HARD_LIGHT"
405 | | "DIFFERENCE"
406 | | "EXCLUSION"
407 | | "HUE"
408 | | "SATURATION"
409 | | "COLOR"
410 | | "LUMINOSITY";
411 |
412 | interface Font {
413 | fontName: FontName;
414 | }
415 |
416 | type Reaction = { action: Action; trigger: Trigger };
417 |
418 | type Action =
419 | | { readonly type: "BACK" | "CLOSE" }
420 | | { readonly type: "URL"; url: string }
421 | | {
422 | readonly type: "NODE";
423 | readonly destinationId: string | null;
424 | readonly navigation: Navigation;
425 | readonly transition: Transition | null;
426 | readonly preserveScrollPosition: boolean;
427 |
428 | // Only present if navigation == "OVERLAY" and the destination uses
429 | // overlay position type "RELATIVE"
430 | readonly overlayRelativePosition?: Vector;
431 | };
432 |
433 | interface SimpleTransition {
434 | readonly type: "DISSOLVE" | "SMART_ANIMATE";
435 | readonly easing: Easing;
436 | readonly duration: number;
437 | }
438 |
439 | interface DirectionalTransition {
440 | readonly type: "MOVE_IN" | "MOVE_OUT" | "PUSH" | "SLIDE_IN" | "SLIDE_OUT";
441 | readonly direction: "LEFT" | "RIGHT" | "TOP" | "BOTTOM";
442 | readonly matchLayers: boolean;
443 |
444 | readonly easing: Easing;
445 | readonly duration: number;
446 | }
447 |
448 | export type Transition = SimpleTransition | DirectionalTransition;
449 |
450 | type Trigger =
451 | | { readonly type: "ON_CLICK" | "ON_HOVER" | "ON_PRESS" | "ON_DRAG" }
452 | | { readonly type: "AFTER_TIMEOUT"; readonly timeout: number }
453 | | {
454 | readonly type:
455 | | "MOUSE_ENTER"
456 | | "MOUSE_LEAVE"
457 | | "MOUSE_UP"
458 | | "MOUSE_DOWN";
459 | readonly delay: number;
460 | };
461 |
462 | type Navigation = "NAVIGATE" | "SWAP" | "OVERLAY";
463 |
464 | interface Easing {
465 | readonly type: "EASE_IN" | "EASE_OUT" | "EASE_IN_AND_OUT" | "LINEAR";
466 | }
467 |
468 | type OverflowDirection = "NONE" | "HORIZONTAL" | "VERTICAL" | "BOTH";
469 |
470 | type OverlayPositionType =
471 | | "CENTER"
472 | | "TOP_LEFT"
473 | | "TOP_CENTER"
474 | | "TOP_RIGHT"
475 | | "BOTTOM_LEFT"
476 | | "BOTTOM_CENTER"
477 | | "BOTTOM_RIGHT"
478 | | "MANUAL";
479 |
480 | type OverlayBackground =
481 | | { readonly type: "NONE" }
482 | | { readonly type: "SOLID_COLOR"; readonly color: RGBA };
483 |
484 | type OverlayBackgroundInteraction = "NONE" | "CLOSE_ON_CLICK_OUTSIDE";
485 |
486 | ////////////////////////////////////////////////////////////////////////////////
487 | // Mixins
488 |
489 | interface BaseNodeMixin {
490 | readonly id: string;
491 | readonly parent: (BaseNode & ChildrenMixin) | null;
492 | name: string; // Note: setting this also sets \`autoRename\` to false on TextNodes
493 | readonly removed: boolean;
494 | toString(): string;
495 | remove(): void;
496 |
497 | getPluginData(key: string): string;
498 | setPluginData(key: string, value: string): void;
499 |
500 | // Namespace is a string that must be at least 3 alphanumeric characters, and should
501 | // be a name related to your plugin. Other plugins will be able to read this data.
502 | getSharedPluginData(namespace: string, key: string): string;
503 | setSharedPluginData(namespace: string, key: string, value: string): void;
504 | }
505 |
506 | interface SceneNodeMixin {
507 | visible: boolean;
508 | locked: boolean;
509 | }
510 |
511 | interface ChildrenMixin {
512 | readonly children: ReadonlyArray;
513 |
514 | appendChild(child: SceneNode): void;
515 | insertChild(index: number, child: SceneNode): void;
516 |
517 | findAll(callback?: (node: SceneNode) => boolean): SceneNode[];
518 | findOne(callback: (node: SceneNode) => boolean): SceneNode | null;
519 | }
520 |
521 | interface ConstraintMixin {
522 | constraints: Constraints;
523 | }
524 |
525 | interface LayoutMixin {
526 | readonly absoluteTransform: Transform;
527 | relativeTransform: Transform;
528 | x: number;
529 | y: number;
530 | rotation: number; // In degrees
531 |
532 | readonly width: number;
533 | readonly height: number;
534 |
535 | layoutAlign: "MIN" | "CENTER" | "MAX"; // applicable only inside auto-layout frames
536 |
537 | resize(width: number, height: number): void;
538 | resizeWithoutConstraints(width: number, height: number): void;
539 | }
540 |
541 | interface BlendMixin {
542 | opacity: number;
543 | blendMode: BlendMode;
544 | isMask: boolean;
545 | effects: ReadonlyArray;
546 | effectStyleId: string;
547 | }
548 |
549 | interface ContainerMixin {
550 | backgrounds: ReadonlyArray; // DEPRECATED: use 'fills' instead
551 | layoutGrids: ReadonlyArray;
552 | clipsContent: boolean;
553 | guides: ReadonlyArray;
554 | gridStyleId: string;
555 | backgroundStyleId: string; // DEPRECATED: use 'fillStyleId' instead
556 | }
557 |
558 | type StrokeCap =
559 | | "NONE"
560 | | "ROUND"
561 | | "SQUARE"
562 | | "ARROW_LINES"
563 | | "ARROW_EQUILATERAL";
564 | type StrokeJoin = "MITER" | "BEVEL" | "ROUND";
565 | type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH";
566 |
567 | interface GeometryMixin {
568 | fills: ReadonlyArray | PluginAPI["mixed"];
569 | strokes: ReadonlyArray;
570 | strokeWeight: number;
571 | strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE";
572 | strokeCap: StrokeCap | PluginAPI["mixed"];
573 | strokeJoin: StrokeJoin | PluginAPI["mixed"];
574 | dashPattern: ReadonlyArray;
575 | fillStyleId: string | PluginAPI["mixed"];
576 | strokeStyleId: string;
577 | }
578 |
579 | interface CornerMixin {
580 | cornerRadius: number | PluginAPI["mixed"];
581 | cornerSmoothing: number;
582 | }
583 |
584 | interface RectangleCornerMixin {
585 | topLeftRadius: number;
586 | topRightRadius: number;
587 | bottomLeftRadius: number;
588 | bottomRightRadius: number;
589 | }
590 |
591 | interface ExportMixin {
592 | exportSettings: ReadonlyArray;
593 | exportAsync(settings?: ExportSettings): Promise; // Defaults to PNG format
594 | }
595 |
596 | interface ReactionMixin {
597 | readonly reactions: ReadonlyArray;
598 | }
599 |
600 | interface DefaultShapeMixin
601 | extends BaseNodeMixin,
602 | SceneNodeMixin,
603 | ReactionMixin,
604 | BlendMixin,
605 | GeometryMixin,
606 | LayoutMixin,
607 | ExportMixin {}
608 |
609 | interface DefaultFrameMixin
610 | extends BaseNodeMixin,
611 | SceneNodeMixin,
612 | ReactionMixin,
613 | ChildrenMixin,
614 | ContainerMixin,
615 | GeometryMixin,
616 | CornerMixin,
617 | RectangleCornerMixin,
618 | BlendMixin,
619 | ConstraintMixin,
620 | LayoutMixin,
621 | ExportMixin {
622 | layoutMode: "NONE" | "HORIZONTAL" | "VERTICAL";
623 | counterAxisSizingMode: "FIXED" | "AUTO"; // applicable only if layoutMode != "NONE"
624 | horizontalPadding: number; // applicable only if layoutMode != "NONE"
625 | verticalPadding: number; // applicable only if layoutMode != "NONE"
626 | itemSpacing: number; // applicable only if layoutMode != "NONE"
627 |
628 | overflowDirection: OverflowDirection;
629 | numberOfFixedChildren: number;
630 |
631 | readonly overlayPositionType: OverlayPositionType;
632 | readonly overlayBackground: OverlayBackground;
633 | readonly overlayBackgroundInteraction: OverlayBackgroundInteraction;
634 | }
635 |
636 | ////////////////////////////////////////////////////////////////////////////////
637 | // Nodes
638 |
639 | interface DocumentNode extends BaseNodeMixin {
640 | readonly type: "DOCUMENT";
641 |
642 | readonly children: ReadonlyArray;
643 |
644 | appendChild(child: PageNode): void;
645 | insertChild(index: number, child: PageNode): void;
646 |
647 | findAll(
648 | callback?: (node: PageNode | SceneNode) => boolean
649 | ): Array;
650 | findOne(
651 | callback: (node: PageNode | SceneNode) => boolean
652 | ): PageNode | SceneNode | null;
653 | }
654 |
655 | interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
656 | readonly type: "PAGE";
657 | clone(): PageNode;
658 |
659 | guides: ReadonlyArray;
660 | selection: ReadonlyArray;
661 |
662 | backgrounds: ReadonlyArray;
663 |
664 | readonly prototypeStartNode:
665 | | FrameNode
666 | | GroupNode
667 | | ComponentNode
668 | | InstanceNode
669 | | null;
670 | }
671 |
672 | interface FrameNode extends DefaultFrameMixin {
673 | readonly type: "FRAME";
674 | clone(): FrameNode;
675 | }
676 |
677 | interface GroupNode
678 | extends BaseNodeMixin,
679 | SceneNodeMixin,
680 | ReactionMixin,
681 | ChildrenMixin,
682 | ContainerMixin,
683 | BlendMixin,
684 | LayoutMixin,
685 | ExportMixin {
686 | readonly type: "GROUP";
687 | clone(): GroupNode;
688 | }
689 |
690 | interface SliceNode
691 | extends BaseNodeMixin,
692 | SceneNodeMixin,
693 | LayoutMixin,
694 | ExportMixin {
695 | readonly type: "SLICE";
696 | clone(): SliceNode;
697 | }
698 |
699 | interface RectangleNode
700 | extends DefaultShapeMixin,
701 | ConstraintMixin,
702 | CornerMixin,
703 | RectangleCornerMixin {
704 | readonly type: "RECTANGLE";
705 | clone(): RectangleNode;
706 | }
707 |
708 | interface LineNode extends DefaultShapeMixin, ConstraintMixin {
709 | readonly type: "LINE";
710 | clone(): LineNode;
711 | }
712 |
713 | interface EllipseNode
714 | extends DefaultShapeMixin,
715 | ConstraintMixin,
716 | CornerMixin {
717 | readonly type: "ELLIPSE";
718 | clone(): EllipseNode;
719 | arcData: ArcData;
720 | }
721 |
722 | interface PolygonNode
723 | extends DefaultShapeMixin,
724 | ConstraintMixin,
725 | CornerMixin {
726 | readonly type: "POLYGON";
727 | clone(): PolygonNode;
728 | pointCount: number;
729 | }
730 |
731 | interface StarNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
732 | readonly type: "STAR";
733 | clone(): StarNode;
734 | pointCount: number;
735 | innerRadius: number;
736 | }
737 |
738 | interface VectorNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
739 | readonly type: "VECTOR";
740 | clone(): VectorNode;
741 | vectorNetwork: VectorNetwork;
742 | vectorPaths: VectorPaths;
743 | handleMirroring: HandleMirroring | PluginAPI["mixed"];
744 | }
745 |
746 | interface TextNode extends DefaultShapeMixin, ConstraintMixin {
747 | readonly type: "TEXT";
748 | clone(): TextNode;
749 | characters: string;
750 | readonly hasMissingFont: boolean;
751 | textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED";
752 | textAlignVertical: "TOP" | "CENTER" | "BOTTOM";
753 | textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT";
754 | paragraphIndent: number;
755 | paragraphSpacing: number;
756 | autoRename: boolean;
757 |
758 | textStyleId: string | PluginAPI["mixed"];
759 | fontSize: number | PluginAPI["mixed"];
760 | fontName: FontName | PluginAPI["mixed"];
761 | textCase: TextCase | PluginAPI["mixed"];
762 | textDecoration: TextDecoration | PluginAPI["mixed"];
763 | letterSpacing: LetterSpacing | PluginAPI["mixed"];
764 | lineHeight: LineHeight | PluginAPI["mixed"];
765 |
766 | getRangeFontSize(start: number, end: number): number | PluginAPI["mixed"];
767 | setRangeFontSize(start: number, end: number, value: number): void;
768 | getRangeFontName(start: number, end: number): FontName | PluginAPI["mixed"];
769 | setRangeFontName(start: number, end: number, value: FontName): void;
770 | getRangeTextCase(start: number, end: number): TextCase | PluginAPI["mixed"];
771 | setRangeTextCase(start: number, end: number, value: TextCase): void;
772 | getRangeTextDecoration(
773 | start: number,
774 | end: number
775 | ): TextDecoration | PluginAPI["mixed"];
776 | setRangeTextDecoration(
777 | start: number,
778 | end: number,
779 | value: TextDecoration
780 | ): void;
781 | getRangeLetterSpacing(
782 | start: number,
783 | end: number
784 | ): LetterSpacing | PluginAPI["mixed"];
785 | setRangeLetterSpacing(
786 | start: number,
787 | end: number,
788 | value: LetterSpacing
789 | ): void;
790 | getRangeLineHeight(
791 | start: number,
792 | end: number
793 | ): LineHeight | PluginAPI["mixed"];
794 | setRangeLineHeight(start: number, end: number, value: LineHeight): void;
795 | getRangeFills(start: number, end: number): Paint[] | PluginAPI["mixed"];
796 | setRangeFills(start: number, end: number, value: Paint[]): void;
797 | getRangeTextStyleId(
798 | start: number,
799 | end: number
800 | ): string | PluginAPI["mixed"];
801 | setRangeTextStyleId(start: number, end: number, value: string): void;
802 | getRangeFillStyleId(
803 | start: number,
804 | end: number
805 | ): string | PluginAPI["mixed"];
806 | setRangeFillStyleId(start: number, end: number, value: string): void;
807 | }
808 |
809 | interface ComponentNode extends DefaultFrameMixin {
810 | readonly type: "COMPONENT";
811 | clone(): ComponentNode;
812 |
813 | createInstance(): InstanceNode;
814 | description: string;
815 | readonly remote: boolean;
816 | readonly key: string; // The key to use with "importComponentByKeyAsync"
817 | }
818 |
819 | interface InstanceNode extends DefaultFrameMixin {
820 | readonly type: "INSTANCE";
821 | clone(): InstanceNode;
822 | masterComponent: ComponentNode;
823 | }
824 |
825 | interface BooleanOperationNode
826 | extends DefaultShapeMixin,
827 | ChildrenMixin,
828 | CornerMixin {
829 | readonly type: "BOOLEAN_OPERATION";
830 | clone(): BooleanOperationNode;
831 | booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE";
832 | }
833 |
834 | type BaseNode = DocumentNode | PageNode | SceneNode;
835 |
836 | type SceneNode =
837 | | SliceNode
838 | | FrameNode
839 | | GroupNode
840 | | ComponentNode
841 | | InstanceNode
842 | | BooleanOperationNode
843 | | VectorNode
844 | | StarNode
845 | | LineNode
846 | | EllipseNode
847 | | PolygonNode
848 | | RectangleNode
849 | | TextNode;
850 |
851 | type NodeType =
852 | | "DOCUMENT"
853 | | "PAGE"
854 | | "SLICE"
855 | | "FRAME"
856 | | "GROUP"
857 | | "COMPONENT"
858 | | "INSTANCE"
859 | | "BOOLEAN_OPERATION"
860 | | "VECTOR"
861 | | "STAR"
862 | | "LINE"
863 | | "ELLIPSE"
864 | | "POLYGON"
865 | | "RECTANGLE"
866 | | "TEXT";
867 |
868 | ////////////////////////////////////////////////////////////////////////////////
869 | // Styles
870 | type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID";
871 |
872 | interface BaseStyle {
873 | readonly id: string;
874 | readonly type: StyleType;
875 | name: string;
876 | description: string;
877 | remote: boolean;
878 | readonly key: string; // The key to use with "importStyleByKeyAsync"
879 | remove(): void;
880 | }
881 |
882 | interface PaintStyle extends BaseStyle {
883 | type: "PAINT";
884 | paints: ReadonlyArray;
885 | }
886 |
887 | interface TextStyle extends BaseStyle {
888 | type: "TEXT";
889 | fontSize: number;
890 | textDecoration: TextDecoration;
891 | fontName: FontName;
892 | letterSpacing: LetterSpacing;
893 | lineHeight: LineHeight;
894 | paragraphIndent: number;
895 | paragraphSpacing: number;
896 | textCase: TextCase;
897 | }
898 |
899 | interface EffectStyle extends BaseStyle {
900 | type: "EFFECT";
901 | effects: ReadonlyArray;
902 | }
903 |
904 | interface GridStyle extends BaseStyle {
905 | type: "GRID";
906 | layoutGrids: ReadonlyArray;
907 | }
908 |
909 | ////////////////////////////////////////////////////////////////////////////////
910 | // Other
911 |
912 | interface Image {
913 | readonly hash: string;
914 | getBytesAsync(): Promise;
915 | }
916 | } // declare global
917 |
918 | export {};
919 |
--------------------------------------------------------------------------------