├── .babelrc ├── .gitignore ├── .prettierrc.yml ├── LICENSE ├── README.md ├── assets ├── Figma_cover-1.png ├── Figma_cover-2.png ├── Figma_cover.png └── logo.png ├── dist ├── bundle.js ├── code.js ├── common.css ├── fonts │ └── icomoon │ │ ├── fonts │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ │ ├── selection.json │ │ ├── style.css │ │ ├── style.scss │ │ └── variables.scss └── index.html ├── manifest.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── app │ ├── _helpers │ │ └── colors.ts │ ├── _hooks │ │ └── useOnClickOutside.ts │ ├── assets │ │ ├── fonts │ │ │ └── icomoon │ │ │ │ ├── Read Me.txt │ │ │ │ ├── demo-files │ │ │ │ ├── demo.css │ │ │ │ └── demo.js │ │ │ │ ├── demo.html │ │ │ │ ├── fonts │ │ │ │ ├── icomoon.svg │ │ │ │ ├── icomoon.ttf │ │ │ │ └── icomoon.woff │ │ │ │ ├── selection.json │ │ │ │ ├── style.css │ │ │ │ ├── style.scss │ │ │ │ └── variables.scss │ │ ├── icons │ │ │ ├── adjust.svg │ │ │ ├── check.svg │ │ │ ├── close.svg │ │ │ ├── create.svg │ │ │ ├── down.svg │ │ │ ├── download.svg │ │ │ ├── empty.svg │ │ │ ├── eyedropper.svg │ │ │ ├── link-broken.svg │ │ │ ├── link-connected.svg │ │ │ ├── minus.svg │ │ │ ├── notice.svg │ │ │ ├── plus.svg │ │ │ └── right.svg │ │ └── logo.svg │ ├── components │ │ ├── app │ │ │ └── index.tsx │ │ ├── common │ │ │ ├── CreateNewMode │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ ├── DesignTokens │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ ├── EditColor │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ ├── OnMessageWrapper │ │ │ │ └── index.tsx │ │ │ ├── PaletteItem │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ └── Settings │ │ │ │ ├── Field │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ └── ui │ │ │ ├── Button │ │ │ ├── Button.module.scss │ │ │ └── Button.tsx │ │ │ ├── ColorItem │ │ │ ├── index.tsx │ │ │ └── styles.module.scss │ │ │ ├── Empty │ │ │ ├── index.tsx │ │ │ └── styles.module.scss │ │ │ ├── InputField │ │ │ ├── index.tsx │ │ │ └── styles.module.scss │ │ │ └── ModalDialog │ │ │ └── index.tsx │ ├── index.html │ ├── index.tsx │ ├── layout │ │ ├── index.tsx │ │ └── styles │ │ │ ├── styles.module.scss │ │ │ └── tabs.module.scss │ ├── models │ │ └── IAction.ts │ ├── store │ │ ├── figmaStyles │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ ├── types.ts │ │ │ └── useFigmaStyles.ts │ │ ├── middleware.ts │ │ ├── rootReducer.ts │ │ ├── store.ts │ │ └── ui │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ ├── types.ts │ │ │ └── useUI.ts │ └── styles │ │ ├── constructor.scss │ │ ├── elements │ │ ├── _chrome-picker.scss │ │ ├── _modal.scss │ │ └── _page.scss │ │ ├── generic │ │ ├── _fonts.scss │ │ ├── _normalize.scss │ │ └── _reset.scss │ │ └── settings │ │ └── _settings.scss └── plugin │ └── controller.ts ├── tsconfig.json ├── typings ├── SCSSTypes.ts └── SVGTypes.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # misc 5 | .DS_Store 6 | .env 7 | .env.local 8 | .env.development.local 9 | .env.test.local 10 | .env.production.local 11 | 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | .idea 17 | .idea/deployment.xml 18 | .idea/encodings.xml 19 | .idea/misc.xml 20 | .idea/modules.xml 21 | .idea/start-com.iml 22 | .idea/workspace.xml 23 | *.DS_Store 24 | *node_modules 25 | node_modules/* 26 | .idea/vcs.xml 27 | .eslintcache 28 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | trailingComma: es5 2 | singleQuote: true 3 | printWidth: 120 4 | tabWidth: 2 5 | bracketSpacing: true 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aleksandr Kharchuk & Daniela Muntyan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Token Master. Figma plugin 2 | # What is Token Master? 3 | 4 | Token Master is a plugin for Figma that automates your color design tokens. With Token Master, you can edit styles in design tokens and quickly create new color modes. Just pick tokens with the same colors and edit them at once! 5 | 6 | [Link to the Figma plugin page](https://www.figma.com/community/plugin/1048365292339473455/Token-Master) 7 | 8 | # **How it works** 9 | 10 | 1. Install Token Master from the link. 11 | 2. Download all styles from your file or add a new color mode. Use keywords [light] and [dark] in your style names to add new color mode. **Example:** [light]Style name/ [dark]Style name. 12 | 3. To edit a style, click on it. Then, you can use the eyedropper or the color picker in the modal window to select a color. 13 | 4. Also, you can copy the whole group of your design tokens and create a new color mode. 14 | 15 | ![Figma cover-2.png](https://github.com/SpiritMod/token-master/blob/master/assets/Figma_cover-2.png?raw=true) 16 | 17 | ![Figma cover.png](https://github.com/SpiritMod/token-master/blob/master/assets/Figma_cover.png?raw=true) 18 | 19 | ![Figma cover-1.png](https://github.com/SpiritMod/token-master/blob/master/assets/Figma_cover-1.png?raw=true) 20 | 21 | # **Styles name examples** 22 | 23 | We recommend using [light] / [dark] at the beginning of the style name: 24 | 25 | ```jsx 26 | [light] Color name 27 | [dark] Color name 28 | ``` 29 | 30 | But you can use [light] / [dark] at any place of your style name: 31 | 32 | ```jsx 33 | Color name [light] 34 | Color name [dark] 35 | ``` 36 | 37 | ```jsx 38 | Style / color-name [light] 39 | Style / color-name [dark] 40 | ``` 41 | 42 | ```jsx 43 | Style [light] / color-name 44 | Style [dark] / color-name 45 | ``` 46 | 47 | 59 | 60 | # **How to install in the Dev environment** 61 | 62 | 1. Select the Plugins Page in the Figma File Browser 63 | 2. Use the plus (+) button in the Development section 64 | 3. Choose file manifest.json 65 | 4. Done 66 | 67 | -------------------------------------------------------------------------------- /assets/Figma_cover-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/assets/Figma_cover-1.png -------------------------------------------------------------------------------- /assets/Figma_cover-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/assets/Figma_cover-2.png -------------------------------------------------------------------------------- /assets/Figma_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/assets/Figma_cover.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/assets/logo.png -------------------------------------------------------------------------------- /dist/code.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";void 0===String.prototype.replaceAll&&(String.prototype.replaceAll=function(e,t){return this.replace(new RegExp(e,"g"),(()=>t))}),figma.showUI(__html__,{width:342,height:444});const e=()=>{const e=(figma.getLocalPaintStyles(),figma.getLocalPaintStyles().reduce(((e,t)=>(e.push({description:t.description,id:t.id,key:t.key,name:t.name,paints:t.paints,remote:t.remote,type:t.type}),e)),[])),t={};return e.reduce(((e,a)=>{const s=a.name.replaceAll(" ","/").split("/")[0];return t.hasOwnProperty(s)||(t[s]=Object.assign({},{[s]:a.name.replaceAll(" ","/").split("/")[0]}),e.push(a.name.replaceAll(" ","/").split("/")[0])),e}),[]).reduce(((t,a)=>{const s=e.reduce(((e,t)=>("SOLID"===t.paints[0].type&&t.name.replaceAll(" ","/").split("/")[0]===a&&e.push(t),e)),[]);return s.length>0&&t.push({themeName:s[0].name.replaceAll(" ","/").split("/")[0],data:s}),t}),[])};let t=e();figma.clientStorage.getAsync(`themesList_${figma.currentPage.id}`).then((e=>{figma.ui.postMessage({type:"themes_list",message:e||"[]"})}),(()=>{figma.ui.postMessage({type:"themes_list",message:[]})})),figma.ui.postMessage({type:"styles",message:[...t]}),figma.on("selectionchange",(()=>{console.log("selectionchange")})),figma.on("currentpagechange",(()=>{console.log("currentpagechange")})),figma.ui.onmessage=t=>{if("update_themes_list"===t.type&&figma.clientStorage.setAsync(`themesList_${figma.currentPage.id}`,JSON.stringify(t.data)).then((()=>{}),(e=>{console.log("error: ",e)})),"update_paint_styles"===t.type){const{color:a,styles:s}=t.data;s.map((e=>{figma.getStyleById(e.id).paints=[Object.assign(Object.assign({},e.paints[0]),{opacity:a.a,color:{r:a.r,g:a.g,b:a.b}})]})),figma.ui.postMessage({type:"styles",message:[...e()]}),figma.notify("Updated colors")}if("create_new_mode"===t.type){const{name:a,oldName:s,data:i}=t.data;i.reduce(((e,t)=>(e.push(...t.data),e)),[]).map((e=>{const t=e.name.replaceAll(s,a),i=figma.createPaintStyle();i.name=t,i.paints=e.paints})),figma.notify(`Created new mode "${a}"`),figma.ui.postMessage({type:"styles",message:[...e()]})}}}(); 2 | -------------------------------------------------------------------------------- /dist/common.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | /* Document 3 | ========================================================================== */ 4 | /** 5 | * 1. Correct the line height in all browsers. 6 | * 2. Prevent adjustments of font size after orientation changes in iOS. 7 | */ 8 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap"); 9 | html { 10 | line-height: 1.15; 11 | /* 1 */ 12 | -webkit-text-size-adjust: 100%; 13 | /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | /** 19 | * Remove the margin in all browsers. 20 | */ 21 | body { 22 | margin: 0; 23 | } 24 | 25 | /** 26 | * Correct the font size and margin on `h1` elements within `section` and 27 | * `article` contexts in Chrome, Firefox, and Safari. 28 | */ 29 | h1 { 30 | font-size: 2em; 31 | margin: 0.67em 0; 32 | } 33 | 34 | /* Grouping content 35 | ========================================================================== */ 36 | /** 37 | * 1. Add the correct box sizing in Firefox. 38 | * 2. Show the overflow in Edge and IE. 39 | */ 40 | hr { 41 | box-sizing: content-box; 42 | /* 1 */ 43 | height: 0; 44 | /* 1 */ 45 | overflow: visible; 46 | /* 2 */ 47 | } 48 | 49 | /** 50 | * 1. Correct the inheritance and scaling of font size in all browsers. 51 | * 2. Correct the odd `em` font sizing in all browsers. 52 | */ 53 | pre { 54 | font-family: monospace, monospace; 55 | /* 1 */ 56 | font-size: 1em; 57 | /* 2 */ 58 | } 59 | 60 | /* Text-level semantics 61 | ========================================================================== */ 62 | /** 63 | * Remove the gray background on active links in IE 10. 64 | */ 65 | a { 66 | background-color: transparent; 67 | } 68 | 69 | /** 70 | * 1. Remove the bottom border in Chrome 57- 71 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 72 | */ 73 | abbr[title] { 74 | border-bottom: none; 75 | /* 1 */ 76 | text-decoration: underline; 77 | /* 2 */ 78 | text-decoration: underline dotted; 79 | /* 2 */ 80 | } 81 | 82 | /** 83 | * Add the correct font weight in Chrome, Edge, and Safari. 84 | */ 85 | b, 86 | strong { 87 | font-weight: bolder; 88 | } 89 | 90 | /** 91 | * 1. Correct the inheritance and scaling of font size in all browsers. 92 | * 2. Correct the odd `em` font sizing in all browsers. 93 | */ 94 | code, 95 | kbd, 96 | samp { 97 | font-family: monospace, monospace; 98 | /* 1 */ 99 | font-size: 1em; 100 | /* 2 */ 101 | } 102 | 103 | /** 104 | * Add the correct font size in all browsers. 105 | */ 106 | small { 107 | font-size: 80%; 108 | } 109 | 110 | /** 111 | * Prevent `sub` and `sup` elements from affecting the line height in 112 | * all browsers. 113 | */ 114 | sub, 115 | sup { 116 | font-size: 75%; 117 | line-height: 0; 118 | position: relative; 119 | vertical-align: baseline; 120 | } 121 | 122 | sub { 123 | bottom: -0.25em; 124 | } 125 | 126 | sup { 127 | top: -0.5em; 128 | } 129 | 130 | /* Embedded content 131 | ========================================================================== */ 132 | /** 133 | * Remove the border on images inside links in IE 10. 134 | */ 135 | img { 136 | border-style: none; 137 | } 138 | 139 | /* Forms 140 | ========================================================================== */ 141 | /** 142 | * 1. Change the font styles in all browsers. 143 | * 2. Remove the margin in Firefox and Safari. 144 | */ 145 | button, 146 | input, 147 | optgroup, 148 | select, 149 | textarea { 150 | font-family: inherit; 151 | /* 1 */ 152 | font-size: 100%; 153 | /* 1 */ 154 | line-height: 1.15; 155 | /* 1 */ 156 | margin: 0; 157 | /* 2 */ 158 | } 159 | 160 | /** 161 | * Show the overflow in IE. 162 | * 1. Show the overflow in Edge. 163 | */ 164 | button, 165 | input { 166 | /* 1 */ 167 | overflow: visible; 168 | } 169 | 170 | /** 171 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 172 | * 1. Remove the inheritance of text transform in Firefox. 173 | */ 174 | button, 175 | select { 176 | /* 1 */ 177 | text-transform: none; 178 | } 179 | 180 | /** 181 | * Correct the inability to style clickable types in iOS and Safari. 182 | */ 183 | button, 184 | [type=button], 185 | [type=reset], 186 | [type=submit] { 187 | -webkit-appearance: button; 188 | } 189 | 190 | /** 191 | * Remove the inner border and padding in Firefox. 192 | */ 193 | button::-moz-focus-inner, 194 | [type=button]::-moz-focus-inner, 195 | [type=reset]::-moz-focus-inner, 196 | [type=submit]::-moz-focus-inner { 197 | border-style: none; 198 | padding: 0; 199 | } 200 | 201 | /** 202 | * Restore the focus styles unset by the previous rule. 203 | */ 204 | button:-moz-focusring, 205 | [type=button]:-moz-focusring, 206 | [type=reset]:-moz-focusring, 207 | [type=submit]:-moz-focusring { 208 | outline: 1px dotted ButtonText; 209 | } 210 | 211 | /** 212 | * Correct the padding in Firefox. 213 | */ 214 | fieldset { 215 | padding: 0.35em 0.75em 0.625em; 216 | } 217 | 218 | /** 219 | * 1. Correct the text wrapping in Edge and IE. 220 | * 2. Correct the color inheritance from `fieldset` elements in IE. 221 | * 3. Remove the padding so developers are not caught out when they zero out 222 | * `fieldset` elements in all browsers. 223 | */ 224 | legend { 225 | box-sizing: border-box; 226 | /* 1 */ 227 | color: inherit; 228 | /* 2 */ 229 | display: table; 230 | /* 1 */ 231 | max-width: 100%; 232 | /* 1 */ 233 | padding: 0; 234 | /* 3 */ 235 | white-space: normal; 236 | /* 1 */ 237 | } 238 | 239 | /** 240 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 241 | */ 242 | progress { 243 | vertical-align: baseline; 244 | } 245 | 246 | /** 247 | * Remove the default vertical scrollbar in IE 10+. 248 | */ 249 | textarea { 250 | overflow: auto; 251 | } 252 | 253 | /** 254 | * 1. Add the correct box sizing in IE 10. 255 | * 2. Remove the padding in IE 10. 256 | */ 257 | [type=checkbox], 258 | [type=radio] { 259 | box-sizing: border-box; 260 | /* 1 */ 261 | padding: 0; 262 | /* 2 */ 263 | } 264 | 265 | /** 266 | * Correct the cursor style of increment and decrement buttons in Chrome. 267 | */ 268 | [type=number]::-webkit-inner-spin-button, 269 | [type=number]::-webkit-outer-spin-button { 270 | height: auto; 271 | } 272 | 273 | /** 274 | * 1. Correct the odd appearance in Chrome and Safari. 275 | * 2. Correct the outline style in Safari. 276 | */ 277 | [type=search] { 278 | -webkit-appearance: textfield; 279 | /* 1 */ 280 | outline-offset: -2px; 281 | /* 2 */ 282 | } 283 | 284 | /** 285 | * Remove the inner padding in Chrome and Safari on macOS. 286 | */ 287 | [type=search]::-webkit-search-decoration { 288 | -webkit-appearance: none; 289 | } 290 | 291 | /** 292 | * 1. Correct the inability to style clickable types in iOS and Safari. 293 | * 2. Change font properties to `inherit` in Safari. 294 | */ 295 | ::-webkit-file-upload-button { 296 | -webkit-appearance: button; 297 | /* 1 */ 298 | font: inherit; 299 | /* 2 */ 300 | } 301 | 302 | /* Interactive 303 | ========================================================================== */ 304 | /* 305 | * Add the correct display in Edge, IE 10+, and Firefox. 306 | */ 307 | details { 308 | display: block; 309 | } 310 | 311 | /* 312 | * Add the correct display in all browsers. 313 | */ 314 | summary { 315 | display: list-item; 316 | } 317 | 318 | /* Misc 319 | ========================================================================== */ 320 | /** 321 | * Add the correct display in IE 10+. 322 | */ 323 | template { 324 | display: none; 325 | } 326 | 327 | /** 328 | * Add the correct display in IE 10. 329 | */ 330 | [hidden] { 331 | display: none; 332 | } 333 | 334 | html, body, div, span, object, iframe, 335 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 336 | abbr, address, cite, code, 337 | del, dfn, em, img, ins, kbd, q, samp, 338 | small, strong, sub, sup, var, 339 | b, i, 340 | dl, dt, dd, ol, ul, li, 341 | fieldset, form, label, legend, 342 | table, caption, tbody, tfoot, thead, tr, th, td, 343 | article, aside, canvas, details, figcaption, figure, 344 | footer, header, hgroup, menu, nav, section, summary, 345 | time, mark, audio, video { 346 | margin: 0; 347 | padding: 0; 348 | border: 0; 349 | outline: 0; 350 | background: transparent; 351 | } 352 | 353 | body { 354 | line-height: 1; 355 | } 356 | 357 | ol, ul { 358 | list-style: none; 359 | } 360 | 361 | p { 362 | margin: 0; 363 | } 364 | 365 | blockquote, q { 366 | quotes: none; 367 | } 368 | 369 | blockquote:before, blockquote:after, 370 | q:before, q:after { 371 | content: ""; 372 | content: none; 373 | } 374 | 375 | table { 376 | border-collapse: collapse; 377 | border-spacing: 0; 378 | } 379 | 380 | @font-face { 381 | font-family: "icomoon"; 382 | src: url("fonts/icomoon/fonts/icomoon.ttf?vixr3b") format("truetype"), url("fonts/icomoon/fonts/icomoon.woff?vixr3b") format("woff"), url("fonts/icomoon/fonts/icomoon.svg?vixr3b#icomoon") format("svg"); 383 | font-weight: normal; 384 | font-style: normal; 385 | font-display: block; 386 | } 387 | [class^=icon-], [class*=" icon-"] { 388 | /* use !important to prevent issues with browser extensions that change fonts */ 389 | font-family: "icomoon" !important; 390 | speak: never; 391 | font-style: normal; 392 | font-weight: normal; 393 | font-variant: normal; 394 | text-transform: none; 395 | line-height: 1; 396 | /* Better Font Rendering =========== */ 397 | -webkit-font-smoothing: antialiased; 398 | -moz-osx-font-smoothing: grayscale; 399 | } 400 | 401 | .icon-download:before { 402 | content: "\e900"; 403 | } 404 | 405 | .icon-adjust:before { 406 | content: "\e901"; 407 | } 408 | 409 | .icon-link-broken:before { 410 | content: "\e902"; 411 | } 412 | 413 | .icon-link-connected:before { 414 | content: "\e903"; 415 | } 416 | 417 | .icon-minus:before { 418 | content: "\e904"; 419 | } 420 | 421 | .icon-plus:before { 422 | content: "\e905"; 423 | } 424 | 425 | * { 426 | box-sizing: border-box; 427 | } 428 | 429 | html { 430 | height: 100%; 431 | } 432 | 433 | body { 434 | display: flex; 435 | flex-direction: column; 436 | line-height: 1.2; 437 | min-height: 100%; 438 | width: auto; 439 | color: rgba(0, 0, 0, 0.8); 440 | font-family: "Inter", medium-content-sans-serif-font, -apple-system, sans-serif; 441 | font-weight: normal; 442 | font-style: normal; 443 | font-size: 16px; 444 | direction: ltr; 445 | -webkit-font-smoothing: antialiased; 446 | -moz-osx-font-smoothing: grayscale; 447 | overflow-x: hidden; 448 | background: #red; 449 | transition: opacity 0.2s ease 0s; 450 | } 451 | body.is-loading { 452 | opacity: 0; 453 | visibility: hidden; 454 | } 455 | body.is-loading.is-visible { 456 | opacity: 1; 457 | visibility: visible; 458 | } 459 | 460 | #react-page, .page { 461 | display: flex; 462 | flex: 1 1 auto; 463 | width: 100%; 464 | flex-direction: column; 465 | } 466 | 467 | main, 468 | .js-wrapper { 469 | flex: 1 0 auto; 470 | /* @include media('screen', '<1025px', 'portrait') { 471 | flex: 1 0 auto; 472 | display: flex; 473 | flex-direction: column; 474 | }*/ 475 | } 476 | 477 | .fade { 478 | transition: opacity 0.15s linear; 479 | } 480 | 481 | @media (prefers-reduced-motion: reduce) { 482 | .fade { 483 | transition: none; 484 | } 485 | } 486 | .fade:not(.show) { 487 | opacity: 0; 488 | } 489 | 490 | .modal-open { 491 | overflow: hidden; 492 | } 493 | 494 | .modal-open .modal { 495 | overflow-x: hidden; 496 | overflow-y: auto; 497 | } 498 | 499 | .modal { 500 | position: fixed; 501 | top: 0; 502 | left: 0; 503 | z-index: 1050; 504 | display: none; 505 | width: 100%; 506 | height: 100%; 507 | overflow: hidden; 508 | outline: 0; 509 | } 510 | 511 | .modal-dialog { 512 | position: relative; 513 | width: auto; 514 | margin: 0.5rem; 515 | pointer-events: none; 516 | } 517 | 518 | @media (prefers-reduced-motion: reduce) { 519 | .modal.fade .modal-dialog { 520 | transition: none; 521 | } 522 | } 523 | .modal.show .modal-dialog { 524 | -webkit-transform: none; 525 | transform: none; 526 | } 527 | 528 | .modal-dialog-scrollable { 529 | display: -ms-flexbox; 530 | display: flex; 531 | max-height: calc(100% - 1rem); 532 | } 533 | 534 | .modal-dialog-scrollable .modal-content { 535 | max-height: calc(100vh - 1rem); 536 | overflow: hidden; 537 | } 538 | 539 | .modal-dialog-scrollable .modal-header, 540 | .modal-dialog-scrollable .modal-footer { 541 | -ms-flex-negative: 0; 542 | flex-shrink: 0; 543 | } 544 | 545 | .modal-dialog-scrollable .modal-body { 546 | overflow-y: auto; 547 | } 548 | 549 | .modal-dialog-centered { 550 | display: -ms-flexbox; 551 | display: flex; 552 | -ms-flex-align: center; 553 | align-items: center; 554 | min-height: calc(100% - 1rem); 555 | } 556 | 557 | .modal-dialog-centered::before { 558 | display: block; 559 | height: calc(100vh - 1rem); 560 | content: ""; 561 | } 562 | 563 | .modal-dialog-centered.modal-dialog-scrollable { 564 | -ms-flex-direction: column; 565 | flex-direction: column; 566 | -ms-flex-pack: center; 567 | justify-content: center; 568 | height: 100%; 569 | } 570 | 571 | .modal-dialog-centered.modal-dialog-scrollable .modal-content { 572 | max-height: none; 573 | } 574 | 575 | .modal-dialog-centered.modal-dialog-scrollable::before { 576 | content: none; 577 | } 578 | 579 | .modal-content { 580 | position: relative; 581 | display: -ms-flexbox; 582 | display: flex; 583 | -ms-flex-direction: column; 584 | flex-direction: column; 585 | width: 100%; 586 | pointer-events: auto; 587 | background-color: #fff; 588 | background-clip: padding-box; 589 | border: none; 590 | border-radius: 0.3rem; 591 | outline: 0; 592 | } 593 | 594 | .modal-backdrop { 595 | position: fixed; 596 | top: 0; 597 | left: 0; 598 | z-index: 1040; 599 | width: 100vw; 600 | height: 100vh; 601 | background-color: #000; 602 | } 603 | .modal-backdrop.fade { 604 | opacity: 0; 605 | } 606 | .modal-backdrop.show { 607 | opacity: 0.3; 608 | } 609 | 610 | .modal-header { 611 | display: -ms-flexbox; 612 | display: flex; 613 | -ms-flex-align: start; 614 | align-items: flex-start; 615 | -ms-flex-pack: justify; 616 | justify-content: space-between; 617 | padding: 1rem 1rem; 618 | border-bottom: 1px solid #dee2e6; 619 | border-top-left-radius: calc(0.3rem - 1px); 620 | border-top-right-radius: calc(0.3rem - 1px); 621 | } 622 | 623 | .modal-title { 624 | margin-bottom: 0; 625 | line-height: 1.5; 626 | } 627 | 628 | .modal-body { 629 | position: relative; 630 | -ms-flex: 1 1 auto; 631 | flex: 1 1 auto; 632 | padding: 1rem; 633 | } 634 | 635 | .modal-footer { 636 | display: -ms-flexbox; 637 | display: flex; 638 | -ms-flex-wrap: wrap; 639 | flex-wrap: wrap; 640 | -ms-flex-align: center; 641 | align-items: center; 642 | -ms-flex-pack: end; 643 | justify-content: flex-end; 644 | padding: 0.75rem; 645 | border-top: 1px solid #dee2e6; 646 | border-bottom-right-radius: calc(0.3rem - 1px); 647 | border-bottom-left-radius: calc(0.3rem - 1px); 648 | } 649 | 650 | .modal-footer > * { 651 | margin: 0.25rem; 652 | } 653 | 654 | .modal-scrollbar-measure { 655 | position: absolute; 656 | top: -9999px; 657 | width: 50px; 658 | height: 50px; 659 | overflow: scroll; 660 | } 661 | 662 | .modal .modal-dialog { 663 | display: flex; 664 | align-items: center; 665 | justify-content: center; 666 | vertical-align: middle; 667 | position: relative; 668 | width: 100%; 669 | max-width: 288px; 670 | margin: 0 auto; 671 | } 672 | .modal .modal-content { 673 | min-height: auto; 674 | color: #000; 675 | padding: 0 16px 16px; 676 | background-color: #fff; 677 | border: 0.5px solid rgba(0, 0, 0, 0.2); 678 | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.15); 679 | border-radius: 2px; 680 | } 681 | .modal .modal-header, 682 | .modal .modal-body { 683 | padding: 0; 684 | border: none; 685 | } 686 | .modal .modal-header { 687 | padding: 12px 16px !important; 688 | margin: 0 -16px; 689 | box-sizing: border-box; 690 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 691 | } 692 | .modal .modal-close { 693 | position: absolute; 694 | top: 4px; 695 | right: 4px; 696 | z-index: 1; 697 | cursor: pointer; 698 | width: 32px; 699 | height: 32px; 700 | font-size: 32px; 701 | background: transparent; 702 | border-radius: 3px; 703 | color: rgba(0, 0, 0, 0.8); 704 | transition: all 0.15s ease 0s; 705 | } 706 | .modal .modal-close:hover { 707 | background: rgba(0, 0, 0, 0.06); 708 | } 709 | .modal .modal-close:hover svg { 710 | fill: black; 711 | } 712 | .modal .modal-close svg { 713 | fill: rgba(0, 0, 0, 0.8); 714 | } 715 | .modal .modal-title { 716 | width: 100%; 717 | margin-bottom: 0; 718 | text-align: left; 719 | padding: 0 32px 0 0; 720 | font-style: normal; 721 | font-weight: 600; 722 | font-size: 11px; 723 | line-height: 16px; 724 | letter-spacing: 0.005em; 725 | color: rgba(0, 0, 0, 0.8); 726 | } 727 | .modal .align-centered .modal-body { 728 | display: flex; 729 | align-items: center; 730 | justify-content: center; 731 | } 732 | .modal .align-centered .modal-text { 733 | width: 100%; 734 | } 735 | 736 | .chrome-picker { 737 | width: 100% !important; 738 | border-radius: 0 !important; 739 | box-shadow: none !important; 740 | box-sizing: initial !important; 741 | font-family: inherit !important; 742 | } 743 | .chrome-picker > div:nth-child(1) { 744 | border-radius: 0 !important; 745 | } 746 | .chrome-picker > div:nth-child(2) { 747 | padding: 16px 16px 0 !important; 748 | } 749 | .chrome-picker > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > div > div:nth-child(2) { 750 | border-radius: 7px; 751 | } 752 | .chrome-picker .hue-horizontal { 753 | border-radius: 7px; 754 | } 755 | .chrome-picker input:focus, .chrome-picker input:focus-visible { 756 | border-color: #18a0fb !important; 757 | box-shadow: 0 0 0 2px #18a0fb !important; 758 | outline: none; 759 | } 760 | 761 | .react_tabs { 762 | -webkit-tap-highlight-color: transparent; 763 | flex: 1 1 auto; 764 | display: flex; 765 | flex-direction: column; 766 | width: 100%; 767 | } 768 | 769 | .react_tabs__tab_list { 770 | display: flex; 771 | align-items: center; 772 | justify-content: flex-start; 773 | height: 100%; 774 | } 775 | 776 | .react_tabs__tab { 777 | display: inline-block; 778 | border-bottom: none; 779 | position: relative; 780 | list-style: none; 781 | cursor: pointer; 782 | font-style: normal; 783 | font-weight: 500; 784 | font-size: 11px; 785 | line-height: 1.45; 786 | letter-spacing: 0.005em; 787 | color: rgba(0, 0, 0, 0.3); 788 | } 789 | .react_tabs__tab:not(:last-child) { 790 | margin-right: 16px; 791 | } 792 | 793 | .react_tabs__tab__selected { 794 | color: rgba(0, 0, 0, 0.8); 795 | font-weight: 600; 796 | } 797 | 798 | .react_tabs__tab__disabled { 799 | color: GrayText; 800 | cursor: default; 801 | } 802 | 803 | .react_tabs__tab:focus { 804 | outline: none; 805 | } 806 | 807 | .react_tabs__tab_panel { 808 | display: none; 809 | width: 100%; 810 | } 811 | 812 | .react_tabs__tab_panel__selected { 813 | display: block; 814 | } 815 | 816 | .layout { 817 | display: flex; 818 | flex: 1 1 auto; 819 | flex-direction: column; 820 | position: relative; 821 | padding: 40px 16px 16px; 822 | } 823 | 824 | .header { 825 | position: fixed; 826 | left: 0; 827 | right: 0; 828 | top: 0; 829 | height: 40px; 830 | border-bottom: 1px solid #E5E5E5; 831 | background: #fff; 832 | z-index: 99; 833 | padding: 0 16px; 834 | } 835 | 836 | .main { 837 | display: flex; 838 | flex: 1 1 auto; 839 | width: 100%; 840 | } 841 | 842 | .link { 843 | display: block; 844 | position: absolute; 845 | right: 4px; 846 | top: 50%; 847 | transform: translateY(-50%); 848 | } 849 | .link:hover svg path { 850 | fill: #0376c3; 851 | } 852 | .link svg { 853 | width: 32px; 854 | height: 32px; 855 | } 856 | .link svg path { 857 | fill: rgba(0, 0, 0, 0.8); 858 | } 859 | 860 | .settings { 861 | display: flex; 862 | flex: 1 1 auto; 863 | flex-direction: column; 864 | width: 100%; 865 | height: 100%; 866 | } 867 | .settings__header { 868 | display: flex; 869 | align-items: center; 870 | justify-content: space-between; 871 | height: 40px; 872 | padding: 4px 0; 873 | margin: 0 -12px 0 0; 874 | } 875 | .settings__header h4 { 876 | font-style: normal; 877 | font-weight: 600; 878 | font-size: 11px; 879 | line-height: 1.45; 880 | letter-spacing: 0.005em; 881 | color: rgba(0, 0, 0, 0.8); 882 | } 883 | .settings__header button { 884 | width: 32px; 885 | height: 32px; 886 | border: none; 887 | box-shadow: none; 888 | padding: 0; 889 | margin: 0; 890 | font-size: 32px; 891 | background: transparent; 892 | border-radius: 3px; 893 | color: rgba(0, 0, 0, 0.8); 894 | cursor: pointer; 895 | transition: all 0.15s ease 0s; 896 | } 897 | .settings__header button:hover { 898 | background: rgba(0, 0, 0, 0.06); 899 | } 900 | .settings__header button:hover svg { 901 | fill: black; 902 | } 903 | .settings__header button svg { 904 | fill: rgba(0, 0, 0, 0.8); 905 | } 906 | .settings__content { 907 | flex: 1 1 auto; 908 | } 909 | .settings__modes { 910 | display: flex; 911 | flex-direction: column; 912 | flex: 1 1 auto; 913 | height: 100%; 914 | } 915 | .settings__footer { 916 | height: 48px; 917 | margin: 0 -16px -15px; 918 | padding: 7px 4px 8px; 919 | display: flex; 920 | align-items: center; 921 | justify-content: flex-start; 922 | border-top: 1px solid #E5E5E5; 923 | } 924 | .settings__footer button { 925 | height: 32px; 926 | margin: 0; 927 | padding: 0; 928 | } 929 | .settings__footer button:not(:last-child) { 930 | margin-left: 8px; 931 | } 932 | .settings__list { 933 | display: flex; 934 | flex-direction: column; 935 | flex: 1 1 auto; 936 | height: 100%; 937 | } 938 | 939 | .api { 940 | margin: 0 -16px; 941 | padding: 0 16px 10px; 942 | border-bottom: 1px solid #E5E5E5; 943 | } 944 | .wrapper { 945 | margin: 0 -16px; 946 | } 947 | .wrapper.empty { 948 | padding: 3px 0 0 0; 949 | } 950 | 951 | .accordion { 952 | position: relative; 953 | border-radius: 0 !important; 954 | border: none !important; 955 | box-shadow: none !important; 956 | z-index: 10; 957 | } 958 | .accordion:before { 959 | display: none !important; 960 | opacity: 1 !important; 961 | background-color: #E5E5E5 !important; 962 | } 963 | .accordion__expanded_root { 964 | margin: 0 !important; 965 | } 966 | .accordion__root { 967 | padding: 0 16px !important; 968 | border: none !important; 969 | box-shadow: none !important; 970 | min-height: 32px !important; 971 | position: relative; 972 | margin: 0 !important; 973 | } 974 | .accordion__root:after { 975 | content: ""; 976 | display: block; 977 | position: absolute; 978 | left: 0; 979 | right: 0; 980 | top: 0; 981 | bottom: 0; 982 | z-index: 4; 983 | border: 1px solid #18a0fb; 984 | pointer-events: none; 985 | opacity: 0; 986 | } 987 | .accordion__root:hover:after { 988 | opacity: 1; 989 | } 990 | .accordion__summary { 991 | margin: 0 !important; 992 | } 993 | .accordion__expanded .accordion__iconWrapper { 994 | transform: translateY(-50%) rotate(90deg) !important; 995 | } 996 | .accordion__iconWrapper { 997 | display: block; 998 | position: absolute; 999 | left: 5px; 1000 | top: 50%; 1001 | transform: translateY(-50%) rotate(0deg) !important; 1002 | } 1003 | .accordion__iconWrapper svg { 1004 | fill: rgba(0, 0, 0, 0.3); 1005 | width: 8px; 1006 | height: 8px; 1007 | } 1008 | .accordion__title { 1009 | font-style: normal; 1010 | font-weight: 600; 1011 | font-size: 11px; 1012 | line-height: 16px; 1013 | letter-spacing: 0.005em; 1014 | font-feature-settings: "ss02" on; 1015 | color: rgba(0, 0, 0, 0.8); 1016 | } 1017 | .accordion__details { 1018 | padding: 0 16px !important; 1019 | } 1020 | 1021 | .createMode { 1022 | padding: 0 0 0; 1023 | } 1024 | 1025 | .header { 1026 | font-style: normal; 1027 | font-weight: normal; 1028 | font-size: 11px; 1029 | line-height: 16px; 1030 | letter-spacing: 0.005em; 1031 | color: rgba(0, 0, 0, 0.8); 1032 | padding: 12px 0; 1033 | } 1034 | 1035 | .field { 1036 | position: relative; 1037 | } 1038 | .field input { 1039 | font-style: normal; 1040 | font-weight: normal; 1041 | font-size: 11px; 1042 | line-height: 1.45; 1043 | letter-spacing: 0.005em; 1044 | color: rgba(0, 0, 0, 0.8); 1045 | width: 100%; 1046 | height: 30px; 1047 | border: 1px solid rgba(0, 0, 0, 0.1); 1048 | box-sizing: border-box; 1049 | border-radius: 2px; 1050 | margin-right: 0; 1051 | padding: 0 50% 0 8px; 1052 | transition: border-color 0.15s ease 0s, box-shadow 0.15s ease 0s; 1053 | text-overflow: ellipsis; 1054 | white-space: nowrap; 1055 | overflow: hidden; 1056 | } 1057 | .field input:focus, .field input:focus-visible { 1058 | border-color: #18a0fb; 1059 | box-shadow: 0 0 0 1px #18a0fb; 1060 | outline: none; 1061 | } 1062 | .field input.input_error { 1063 | border-color: #FF3333; 1064 | box-shadow: 0 0 0 1px #FF3333; 1065 | } 1066 | 1067 | .content { 1068 | margin: 0; 1069 | } 1070 | 1071 | .footer { 1072 | display: flex; 1073 | align-items: center; 1074 | justify-content: flex-end; 1075 | padding-top: 10px; 1076 | } 1077 | .footer button { 1078 | margin: 0; 1079 | } 1080 | 1081 | .dropdown { 1082 | display: block; 1083 | position: absolute; 1084 | top: 0; 1085 | right: 0; 1086 | max-width: 40%; 1087 | z-index: 10; 1088 | } 1089 | .dropdown__btn { 1090 | position: relative; 1091 | display: flex; 1092 | align-items: center; 1093 | justify-content: flex-end; 1094 | cursor: pointer; 1095 | height: 30px; 1096 | padding: 0 20px 0 4px; 1097 | border: 1px solid rgba(0, 0, 0, 0); 1098 | border-radius: 2px; 1099 | } 1100 | .dropdown__btn:hover svg { 1101 | fill: rgba(0, 0, 0, 0.8); 1102 | } 1103 | .dropdown__btn span { 1104 | font-style: normal; 1105 | font-weight: normal; 1106 | font-size: 11px; 1107 | line-height: 16px; 1108 | letter-spacing: 0.005em; 1109 | color: rgba(0, 0, 0, 0.8); 1110 | } 1111 | .dropdown__btn svg { 1112 | display: block; 1113 | width: 8px; 1114 | height: 8px; 1115 | position: absolute; 1116 | right: 5px; 1117 | top: 50%; 1118 | transform: translateY(-50%); 1119 | fill: rgba(0, 0, 0, 0.3); 1120 | } 1121 | .dropdown__list { 1122 | display: none; 1123 | position: absolute; 1124 | top: -5px; 1125 | right: 0; 1126 | left: 0; 1127 | flex-direction: column; 1128 | align-items: flex-start; 1129 | justify-content: flex-start; 1130 | padding: 8px 0; 1131 | border-radius: 2px; 1132 | background-color: #222; 1133 | box-shadow: 0 5px 17px rgba(0, 0, 0, 0.2), 0 2px 7px rgba(0, 0, 0, 0.15), inset 0 0 0 1px #000, 0 0 0 1px rgba(0, 0, 0, 0.1); 1134 | } 1135 | .dropdown__list_open { 1136 | display: flex; 1137 | } 1138 | .dropdown__list_item { 1139 | position: relative; 1140 | display: flex; 1141 | align-items: center; 1142 | justify-content: flex-start; 1143 | color: #fff; 1144 | font-size: 11px; 1145 | line-height: 16px; 1146 | padding: 0 8px 0 24px; 1147 | height: 24px; 1148 | width: 100%; 1149 | cursor: pointer; 1150 | } 1151 | .dropdown__list_item:hover { 1152 | background: #18a0fb; 1153 | } 1154 | .dropdown__list_item svg { 1155 | fill: #fff; 1156 | width: 16px; 1157 | height: 16px; 1158 | display: block; 1159 | position: absolute; 1160 | left: 4px; 1161 | top: 50%; 1162 | transform: translateY(-50%); 1163 | } 1164 | 1165 | .picker { 1166 | margin: 0 -16px 0; 1167 | } 1168 | 1169 | .pipette { 1170 | position: relative; 1171 | width: 30px; 1172 | height: 30px; 1173 | } 1174 | .pipette input { 1175 | position: absolute; 1176 | left: 0; 1177 | right: 0; 1178 | top: 0; 1179 | bottom: 0; 1180 | z-index: 0; 1181 | display: block; 1182 | margin: 0; 1183 | padding: 0; 1184 | width: 32px; 1185 | height: 32px; 1186 | opacity: 0; 1187 | border: none; 1188 | } 1189 | .pipette label { 1190 | position: relative; 1191 | z-index: 1; 1192 | cursor: pointer; 1193 | display: block; 1194 | } 1195 | .pipette label:hover svg { 1196 | fill: black; 1197 | } 1198 | .pipette label svg { 1199 | fill: rgba(0, 0, 0, 0.8); 1200 | } 1201 | 1202 | .footer { 1203 | display: flex; 1204 | align-items: center; 1205 | justify-content: space-between; 1206 | padding-top: 12px; 1207 | } 1208 | .footer button { 1209 | margin: 0; 1210 | } 1211 | 1212 | .base { 1213 | display: flex; 1214 | align-items: center; 1215 | justify-content: center; 1216 | height: 30px; 1217 | box-shadow: none; 1218 | padding: 6px 15px; 1219 | margin: 0 5px; 1220 | outline: none; 1221 | border-radius: 6px; 1222 | box-sizing: border-box; 1223 | font-style: normal; 1224 | font-weight: 500; 1225 | font-size: 11px; 1226 | line-height: 1.3; 1227 | letter-spacing: 0.01em; 1228 | } 1229 | 1230 | .primary { 1231 | background-color: #18a0fb; 1232 | color: #ffffff; 1233 | border: none; 1234 | } 1235 | .primary:hover { 1236 | background-color: #0376c3; 1237 | cursor: pointer; 1238 | } 1239 | 1240 | .secondary { 1241 | background-color: #ffffff; 1242 | color: rgba(0, 0, 0, 0.8); 1243 | border: 1px solid rgba(0, 0, 0, 0.8); 1244 | } 1245 | .secondary:hover { 1246 | border-color: #0376c3; 1247 | color: #0376c3; 1248 | cursor: pointer; 1249 | } 1250 | 1251 | .text { 1252 | border: none !important; 1253 | color: rgba(0, 0, 0, 0.8); 1254 | } 1255 | .text:hover { 1256 | color: #0376c3; 1257 | } 1258 | .text:hover svg path { 1259 | fill: #0376c3; 1260 | } 1261 | .text svg { 1262 | width: 32px; 1263 | height: 32px; 1264 | fill: rgba(0, 0, 0, 0.8); 1265 | } 1266 | .text.disabled { 1267 | background-color: transparent !important; 1268 | color: rgba(0, 0, 0, 0.3); 1269 | } 1270 | .text.disabled svg path { 1271 | fill: rgba(0, 0, 0, 0.3); 1272 | } 1273 | 1274 | .disabled { 1275 | pointer-events: none; 1276 | background-color: #b3b3b3; 1277 | color: white; 1278 | border: 1px solid #b3b3b3; 1279 | } 1280 | .disabled:hover { 1281 | background-color: #b3b3b3; 1282 | color: white; 1283 | border: 1px solid #b3b3b3; 1284 | } 1285 | 1286 | .item { 1287 | margin: 0 -16px 0; 1288 | border-bottom: 1px solid #E5E5E5; 1289 | } 1290 | .item:last-child { 1291 | border-bottom: none !important; 1292 | } 1293 | 1294 | .header { 1295 | display: flex; 1296 | align-items: center; 1297 | justify-content: space-between; 1298 | padding: 0 4px 0 16px; 1299 | height: 40px; 1300 | } 1301 | 1302 | .title { 1303 | font-style: normal; 1304 | font-weight: 600; 1305 | font-size: 11px; 1306 | line-height: 1.4; 1307 | letter-spacing: 0.005em; 1308 | color: rgba(0, 0, 0, 0.8); 1309 | } 1310 | 1311 | .content { 1312 | padding: 8px 16px; 1313 | display: flex; 1314 | align-items: flex-start; 1315 | flex-wrap: wrap; 1316 | } 1317 | 1318 | .col_left { 1319 | width: 30%; 1320 | padding-right: 16px; 1321 | } 1322 | 1323 | .col_right { 1324 | width: 70%; 1325 | } 1326 | 1327 | .pointer { 1328 | cursor: pointer; 1329 | } 1330 | 1331 | .empty { 1332 | display: flex; 1333 | align-items: center; 1334 | justify-content: center; 1335 | flex-direction: column; 1336 | height: 100%; 1337 | } 1338 | 1339 | .icon { 1340 | display: flex; 1341 | align-items: center; 1342 | justify-content: center; 1343 | margin-bottom: 16px; 1344 | } 1345 | .icon svg { 1346 | width: 64px; 1347 | height: 64px; 1348 | } 1349 | 1350 | .text { 1351 | font-style: normal; 1352 | font-weight: normal; 1353 | font-size: 11px; 1354 | line-height: 1.45; 1355 | letter-spacing: 0.005em; 1356 | color: rgba(0, 0, 0, 0.3); 1357 | text-align: center; 1358 | } 1359 | 1360 | .field { 1361 | display: flex; 1362 | align-items: center; 1363 | justify-content: space-between; 1364 | height: 32px; 1365 | margin: 0 -12px 8px 0; 1366 | } 1367 | .field:last-child { 1368 | margin-bottom: 0; 1369 | } 1370 | .field input { 1371 | font-style: normal; 1372 | font-weight: normal; 1373 | font-size: 11px; 1374 | line-height: 1.45; 1375 | letter-spacing: 0.005em; 1376 | color: rgba(0, 0, 0, 0.8); 1377 | width: 100%; 1378 | height: 30px; 1379 | border: 1px solid rgba(0, 0, 0, 0.1); 1380 | box-sizing: border-box; 1381 | border-radius: 2px; 1382 | margin-right: 8px; 1383 | padding: 0 8px; 1384 | transition: border-color 0.15s ease 0s, box-shadow 0.15s ease 0s; 1385 | } 1386 | .field input:focus, .field input:focus-visible { 1387 | border-color: #18a0fb; 1388 | box-shadow: 0 0 0 1px #18a0fb; 1389 | outline: none; 1390 | } 1391 | .field button { 1392 | width: 32px; 1393 | height: 32px; 1394 | border: none; 1395 | box-shadow: none; 1396 | padding: 0; 1397 | margin: 0; 1398 | font-size: 32px; 1399 | background: transparent; 1400 | border-radius: 2px; 1401 | color: rgba(0, 0, 0, 0.8); 1402 | cursor: pointer; 1403 | transition: all 0.15s ease 0s; 1404 | } 1405 | .field button:hover { 1406 | background: rgba(0, 0, 0, 0.06); 1407 | } 1408 | .field button:hover svg { 1409 | fill: black; 1410 | } 1411 | .field button svg { 1412 | fill: rgba(0, 0, 0, 0.8); 1413 | } 1414 | 1415 | .item { 1416 | display: flex; 1417 | align-items: center; 1418 | justify-content: flex-start; 1419 | height: 32px; 1420 | width: 100%; 1421 | padding: 8px 0; 1422 | } 1423 | 1424 | .box { 1425 | position: relative; 1426 | margin: 0 8px 0 0; 1427 | display: block; 1428 | width: 16px; 1429 | height: 16px; 1430 | background: url("data:image/svg+xml;utf8,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M0%200H3V3H0V0Z%22%20fill%3D%22%23E1E1E1%22/%3E%3Cpath%20d%3D%22M3%200H6V3H3V0Z%22%20fill%3D%22white%22/%3E%3Cpath%20d%3D%22M3%203H6V6H3V3Z%22%20fill%3D%22%23E1E1E1%22/%3E%3Cpath%20d%3D%22M0%203H3V6H0V3Z%22%20fill%3D%22white%22/%3E%3C/svg%3E%0A"); 1431 | border-radius: 2px; 1432 | overflow: hidden; 1433 | } 1434 | 1435 | .border { 1436 | border: 1px solid #E5E5E5; 1437 | } 1438 | 1439 | .color { 1440 | width: 8px; 1441 | display: block; 1442 | position: absolute; 1443 | left: 0; 1444 | top: 0; 1445 | bottom: 0; 1446 | z-index: 2; 1447 | } 1448 | 1449 | .opacity { 1450 | display: block; 1451 | position: absolute; 1452 | left: 0; 1453 | right: 0; 1454 | top: 0; 1455 | bottom: 0; 1456 | z-index: 1; 1457 | opacity: 0.61; 1458 | } 1459 | .opacity__fill { 1460 | opacity: 1; 1461 | } 1462 | 1463 | .title { 1464 | font-style: normal; 1465 | font-weight: normal; 1466 | font-size: 11px; 1467 | line-height: 1.45; 1468 | letter-spacing: 0.005em; 1469 | color: rgba(0, 0, 0, 0.8); 1470 | width: calc(100% - 24px); 1471 | overflow: hidden; 1472 | text-overflow: ellipsis; 1473 | } -------------------------------------------------------------------------------- /dist/fonts/icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/fonts/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/dist/fonts/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /dist/fonts/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/dist/fonts/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /dist/fonts/icomoon/selection.json: -------------------------------------------------------------------------------- 1 | {"IcoMoonType":"selection","icons":[{"icon":{"paths":["M523.315 619.315l-11.315 11.312-107.315-107.312 22.63-22.63 68.685 68.688v-249.373h32v249.373l68.685-68.688 22.63 22.63-96 96zM736 672v32h-448v-32h448z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["download"],"colorPermutations":{}},"attrs":[],"properties":{"order":2,"id":5,"name":"download","prevSize":32,"code":59648},"setIdx":0,"setId":0,"iconIdx":0},{"icon":{"paths":["M384 513.6v-225.6h32v225.6c36.515 7.411 64 39.696 64 78.4s-27.485 70.989-64 78.4v65.6h-32v-65.6c-36.515-7.411-64-39.696-64-78.4s27.485-70.989 64-78.4zM448 592c0 26.509-21.491 48-48 48s-48-21.491-48-48c0-26.509 21.491-48 48-48s48 21.491 48 48zM608 736h32v-225.6c36.515-7.411 64-39.696 64-78.4s-27.485-70.989-64-78.4v-65.6h-32v65.6c-36.515 7.411-64 39.696-64 78.4s27.485 70.989 64 78.4v225.6zM672 432c0-26.509-21.491-48-48-48s-48 21.491-48 48c0 26.509 21.491 48 48 48s48-21.491 48-48z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["adjust"],"colorPermutations":{}},"attrs":[],"properties":{"order":3,"id":4,"name":"adjust","prevSize":32,"code":59649},"setIdx":0,"setId":0,"iconIdx":1},{"icon":{"paths":["M576 448v-64c0-35.347-28.653-64-64-64s-64 28.653-64 64v64h-32v-64c0-53.021 42.979-96 96-96s96 42.979 96 96v64h-32zM608 576h-32v64c0 35.347-28.653 64-64 64s-64-28.653-64-64v-64h-32v64c0 53.021 42.979 96 96 96s96-42.979 96-96v-64z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["link-broken"],"colorPermutations":{}},"attrs":[],"properties":{"order":4,"id":3,"name":"link-broken","prevSize":32,"code":59650},"setIdx":0,"setId":0,"iconIdx":2},{"icon":{"paths":["M512 320c35.347 0 64 28.653 64 64v64h32v-64c0-53.021-42.979-96-96-96s-96 42.979-96 96v64h32v-64c0-35.347 28.653-64 64-64zM576 576h32v64c0 53.021-42.979 96-96 96s-96-42.979-96-96v-64h32v64c0 35.347 28.653 64 64 64s64-28.653 64-64v-64zM496 416v192h32v-192h-32z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["link-connected"],"colorPermutations":{}},"attrs":[],"properties":{"order":5,"id":2,"name":"link-connected","prevSize":32,"code":59651},"setIdx":0,"setId":0,"iconIdx":3},{"icon":{"paths":["M688 528h-352v-32h352v32z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["minus"],"colorPermutations":{}},"attrs":[],"properties":{"order":6,"id":1,"name":"minus","prevSize":32,"code":59652},"setIdx":0,"setId":0,"iconIdx":4},{"icon":{"paths":["M496 496v-160h32v160h160v32h-160v160h-32v-160h-160v-32h160z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["plus"],"colorPermutations":{}},"attrs":[],"properties":{"order":7,"id":0,"name":"plus","prevSize":32,"code":59653},"setIdx":0,"setId":0,"iconIdx":5}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false,"noie8":true,"ie7":false,"cssVars":true,"cssVarsFormat":"scss","flutter":false,"showSelector":false,"showMetrics":false,"showMetadata":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215},"historySize":50,"gridSize":16,"showGrid":true}} -------------------------------------------------------------------------------- /dist/fonts/icomoon/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: 4 | url('fonts/icomoon.ttf?vixr3b') format('truetype'), 5 | url('fonts/icomoon.woff?vixr3b') format('woff'), 6 | url('fonts/icomoon.svg?vixr3b#icomoon') format('svg'); 7 | font-weight: normal; 8 | font-style: normal; 9 | font-display: block; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icomoon' !important; 15 | speak: never; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-download:before { 28 | content: "\e900"; 29 | } 30 | .icon-adjust:before { 31 | content: "\e901"; 32 | } 33 | .icon-link-broken:before { 34 | content: "\e902"; 35 | } 36 | .icon-link-connected:before { 37 | content: "\e903"; 38 | } 39 | .icon-minus:before { 40 | content: "\e904"; 41 | } 42 | .icon-plus:before { 43 | content: "\e905"; 44 | } 45 | -------------------------------------------------------------------------------- /dist/fonts/icomoon/style.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | @font-face { 4 | font-family: '#{$icomoon-font-family}'; 5 | src: 6 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.ttf?vixr3b') format('truetype'), 7 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.woff?vixr3b') format('woff'), 8 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.svg?vixr3b##{$icomoon-font-family}') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | font-display: block; 12 | } 13 | 14 | [class^="icon-"], [class*=" icon-"] { 15 | /* use !important to prevent issues with browser extensions that change fonts */ 16 | font-family: '#{$icomoon-font-family}' !important; 17 | speak: never; 18 | font-style: normal; 19 | font-weight: normal; 20 | font-variant: normal; 21 | text-transform: none; 22 | line-height: 1; 23 | 24 | /* Better Font Rendering =========== */ 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | .icon-download { 30 | &:before { 31 | content: $icon-download; 32 | } 33 | } 34 | .icon-adjust { 35 | &:before { 36 | content: $icon-adjust; 37 | } 38 | } 39 | .icon-link-broken { 40 | &:before { 41 | content: $icon-link-broken; 42 | } 43 | } 44 | .icon-link-connected { 45 | &:before { 46 | content: $icon-link-connected; 47 | } 48 | } 49 | .icon-minus { 50 | &:before { 51 | content: $icon-minus; 52 | } 53 | } 54 | .icon-plus { 55 | &:before { 56 | content: $icon-plus; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /dist/fonts/icomoon/variables.scss: -------------------------------------------------------------------------------- 1 | $icomoon-font-family: "icomoon" !default; 2 | $icomoon-font-path: "fonts" !default; 3 | 4 | $icon-download: "\e900"; 5 | $icon-adjust: "\e901"; 6 | $icon-link-broken: "\e902"; 7 | $icon-link-connected: "\e903"; 8 | $icon-minus: "\e904"; 9 | $icon-plus: "\e905"; 10 | 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Token master", 3 | "id": "00000000", 4 | "api": "1.0.0", 5 | "main": "dist/code.js", 6 | "ui": "dist/index.html", 7 | "editorType": ["figma"] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token_master", 3 | "version": "1.0.0", 4 | "description": "Figma plugin", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "rollup -c", 8 | "dev": "rollup -c -w", 9 | "start": "sirv dist" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.5.0", 13 | "@emotion/styled": "^11.3.0", 14 | "@mui/icons-material": "^5.1.0", 15 | "@mui/material": "^5.1.0", 16 | "bootstrap": "^5.1.3", 17 | "node-sass": "^6.0.1", 18 | "postcss": "^8.3.11", 19 | "react": "^17.0.2", 20 | "react-color": "2.17.3", 21 | "react-dom": "^17.0.2", 22 | "react-redux": "^7.2.6", 23 | "react-tabs": "^3.2.2", 24 | "reactstrap": "^9.0.0", 25 | "redux": "^4.1.1", 26 | "redux-logger": "^3.0.6", 27 | "redux-thunk": "^2.3.0", 28 | "tinycolor2": "^1.4.2" 29 | }, 30 | "devDependencies": { 31 | "@babel/plugin-transform-runtime": "^7.14.5", 32 | "@babel/preset-env": "^7.14.7", 33 | "@babel/preset-react": "^7.14.5", 34 | "@figma/plugin-typings": "^1.26.0", 35 | "@rollup/plugin-babel": "^5.3.0", 36 | "@rollup/plugin-commonjs": "^19.0.1", 37 | "@rollup/plugin-node-resolve": "^13.0.2", 38 | "@rollup/plugin-replace": "^2.4.2", 39 | "@svgr/rollup": "^5.5.0", 40 | "@types/react": "^17.0.14", 41 | "@types/react-dom": "^17.0.9", 42 | "@types/sass": "^1.16.1", 43 | "@types/react-color": "^3.0.6", 44 | "@types/tinycolor2": "^1.4.3", 45 | "babel-loader": "^8.2.2", 46 | "edit-json-file": "^1.6.0", 47 | "prettier": "^2.3.1", 48 | "rollup": "^2.53.2", 49 | "rollup-plugin-bundle-html-plus": "^1.4.0", 50 | "rollup-plugin-html-bundle": "^0.0.3", 51 | "rollup-plugin-livereload": "^2.0.5", 52 | "rollup-plugin-postcss": "^4.0.0", 53 | "rollup-plugin-scss": "^3.0.0", 54 | "rollup-plugin-terser": "^7.0.2", 55 | "rollup-plugin-typescript": "^1.0.1", 56 | "sass": "^1.35.2", 57 | "sirv-cli": "^1.0.12", 58 | "typescript": "^4.3.5", 59 | "typescript-plugin-css-modules": "^3.4.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import replace from '@rollup/plugin-replace'; 6 | import { terser } from 'rollup-plugin-terser'; 7 | import postcss from 'rollup-plugin-postcss'; 8 | import html from 'rollup-plugin-bundle-html-plus'; 9 | import typescript from 'rollup-plugin-typescript'; 10 | import scss from 'rollup-plugin-scss'; 11 | import svgr from '@svgr/rollup'; 12 | 13 | const production = !process.env.ROLLUP_WATCH; 14 | //const production = false; 15 | 16 | export default [ 17 | /* 18 | Transpiling React code and injecting into index.html for Figma 19 | */ 20 | { 21 | input: 'src/app/index.tsx', 22 | output: { 23 | name: 'ui', 24 | file: 'dist/bundle.js', 25 | format: 'umd', 26 | }, 27 | plugins: [ 28 | // What extensions is rollup looking for 29 | resolve({ 30 | extensions: ['.jsx', '.js', '.json', '.ts', '.tsx'], 31 | browser: true, 32 | dedupe: ['react', 'react-dom'], 33 | }), 34 | 35 | scss({ 36 | output: 'dist/common.css', 37 | } 38 | ), 39 | 40 | // Manage process.env 41 | replace({ 42 | preventAssignment: true, 43 | 'process.env.NODE_ENV': JSON.stringify(production), 44 | }), 45 | 46 | typescript({ sourceMap: !production }), 47 | 48 | // Babel config to support React 49 | babel({ 50 | presets: ['@babel/preset-react', '@babel/preset-env'], 51 | babelHelpers: 'runtime', 52 | plugins: ['@babel/plugin-transform-runtime'], 53 | extensions: ['.js', '.ts', 'tsx', 'jsx'], 54 | compact: true, 55 | exclude: 'node_modules/**', 56 | }), 57 | 58 | commonjs(), 59 | 60 | svgr(), 61 | 62 | // Config to allow sass and css modules 63 | postcss({ 64 | extract: false, 65 | modules: true, 66 | use: ['sass'], 67 | }), 68 | 69 | // Injecting UI code into ui.html 70 | html({ 71 | template: 'src/app/index.html', 72 | dest: 'dist', 73 | filename: 'index.html', 74 | inline: true, 75 | inject: 'body', 76 | ignore: /code.js/, 77 | }), 78 | 79 | // If dev mode, serve and livereload 80 | !production && serve(), 81 | !production && livereload('dist'), 82 | 83 | // If prod mode, minify 84 | //production && terser(), 85 | ], 86 | watch: { 87 | clearScreen: true, 88 | }, 89 | }, 90 | 91 | 92 | 93 | /* 94 | Main Figma plugin code 95 | */ 96 | 97 | { 98 | input: 'src/plugin/controller.ts', 99 | output: { 100 | file: 'dist/code.js', 101 | format: 'iife', 102 | name: 'code', 103 | }, 104 | plugins: [resolve(), typescript(), commonjs({ transformMixedEsModules: true }), production && terser()], 105 | }, 106 | ]; 107 | 108 | function serve() { 109 | let started = false; 110 | 111 | return { 112 | writeBundle() { 113 | if (!started) { 114 | started = true; 115 | 116 | // Start localhost dev server on port 5000 to work on the UI in the browser 117 | require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 118 | stdio: ['ignore', 'inherit', 'inherit'], 119 | shell: true, 120 | }); 121 | } 122 | }, 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /src/app/_helpers/colors.ts: -------------------------------------------------------------------------------- 1 | import tinycolor from "tinycolor2"; 2 | 3 | export const rgbToHex = (color) => tinycolor.fromRatio(color).toHexString(); 4 | export const hexToRgba = (color) => tinycolor(color).toRgb(); 5 | 6 | 7 | export const rgbToRgba = (color, alpha):any => { 8 | const rgb = tinycolor.fromRatio(color); 9 | 10 | return { 11 | // @ts-ignore 12 | r: rgb._r, g: rgb._g, b: rgb._b, a: alpha, 13 | } 14 | } 15 | 16 | export const rgbaToFigma = (color) => { 17 | return { 18 | r: parseFloat((color.r/255).toFixed(17)), 19 | g: parseFloat((color.g/255).toFixed(17)), 20 | b: parseFloat((color.b/255).toFixed(17)), 21 | a: color.a, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/_hooks/useOnClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | // Hook 4 | export function useOnClickOutside(ref, handler) { 5 | useEffect( 6 | () => { 7 | const listener = event => { 8 | // Do nothing if clicking ref's element or descendent elements 9 | if (!ref.current || ref.current.contains(event.target)) { 10 | return; 11 | } 12 | handler(event); 13 | }; 14 | document.addEventListener('mousedown', listener); 15 | document.addEventListener('touchstart', listener); 16 | return () => { 17 | document.removeEventListener('mousedown', listener); 18 | document.removeEventListener('touchstart', listener); 19 | }; 20 | }, 21 | // Add ref and handler to effect dependencies 22 | // It's worth noting that because passed in handler is a new ... 23 | // ... function on every render that will cause this effect ... 24 | // ... callback/cleanup to run every render. It's not a big deal ... 25 | // ... but to optimize you can wrap handler in useCallback before ... 26 | // ... passing it into this hook. 27 | [ref, handler] 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/demo-files/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | font-family: sans-serif; 5 | font-size: 1em; 6 | line-height: 1.5; 7 | color: #555; 8 | background: #fff; 9 | } 10 | h1 { 11 | font-size: 1.5em; 12 | font-weight: normal; 13 | } 14 | small { 15 | font-size: .66666667em; 16 | } 17 | a { 18 | color: #e74c3c; 19 | text-decoration: none; 20 | } 21 | a:hover, a:focus { 22 | box-shadow: 0 1px #e74c3c; 23 | } 24 | .bshadow0, input { 25 | box-shadow: inset 0 -2px #e7e7e7; 26 | } 27 | input:hover { 28 | box-shadow: inset 0 -2px #ccc; 29 | } 30 | input, fieldset { 31 | font-family: sans-serif; 32 | font-size: 1em; 33 | margin: 0; 34 | padding: 0; 35 | border: 0; 36 | } 37 | input { 38 | color: inherit; 39 | line-height: 1.5; 40 | height: 1.5em; 41 | padding: .25em 0; 42 | } 43 | input:focus { 44 | outline: none; 45 | box-shadow: inset 0 -2px #449fdb; 46 | } 47 | .glyph { 48 | font-size: 16px; 49 | width: 15em; 50 | padding-bottom: 1em; 51 | margin-right: 4em; 52 | margin-bottom: 1em; 53 | float: left; 54 | overflow: hidden; 55 | } 56 | .liga { 57 | width: 80%; 58 | width: calc(100% - 2.5em); 59 | } 60 | .talign-right { 61 | text-align: right; 62 | } 63 | .talign-center { 64 | text-align: center; 65 | } 66 | .bgc1 { 67 | background: #f1f1f1; 68 | } 69 | .fgc1 { 70 | color: #999; 71 | } 72 | .fgc0 { 73 | color: #000; 74 | } 75 | p { 76 | margin-top: 1em; 77 | margin-bottom: 1em; 78 | } 79 | .mvm { 80 | margin-top: .75em; 81 | margin-bottom: .75em; 82 | } 83 | .mtn { 84 | margin-top: 0; 85 | } 86 | .mtl, .mal { 87 | margin-top: 1.5em; 88 | } 89 | .mbl, .mal { 90 | margin-bottom: 1.5em; 91 | } 92 | .mal, .mhl { 93 | margin-left: 1.5em; 94 | margin-right: 1.5em; 95 | } 96 | .mhmm { 97 | margin-left: 1em; 98 | margin-right: 1em; 99 | } 100 | .mls { 101 | margin-left: .25em; 102 | } 103 | .ptl { 104 | padding-top: 1.5em; 105 | } 106 | .pbs, .pvs { 107 | padding-bottom: .25em; 108 | } 109 | .pvs, .pts { 110 | padding-top: .25em; 111 | } 112 | .unit { 113 | float: left; 114 | } 115 | .unitRight { 116 | float: right; 117 | } 118 | .size1of2 { 119 | width: 50%; 120 | } 121 | .size1of1 { 122 | width: 100%; 123 | } 124 | .clearfix:before, .clearfix:after { 125 | content: " "; 126 | display: table; 127 | } 128 | .clearfix:after { 129 | clear: both; 130 | } 131 | .hidden-true { 132 | display: none; 133 | } 134 | .textbox0 { 135 | width: 3em; 136 | background: #f1f1f1; 137 | padding: .25em .5em; 138 | line-height: 1.5; 139 | height: 1.5em; 140 | } 141 | #testDrive { 142 | display: block; 143 | padding-top: 24px; 144 | line-height: 1.5; 145 | } 146 | .fs0 { 147 | font-size: 16px; 148 | } 149 | .fs1 { 150 | font-size: 32px; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!('boxShadow' in document.body.style)) { 2 | document.body.setAttribute('class', 'noBoxShadow'); 3 | } 4 | 5 | document.body.addEventListener("click", function(e) { 6 | var target = e.target; 7 | if (target.tagName === "INPUT" && 8 | target.getAttribute('class').indexOf('liga') === -1) { 9 | target.select(); 10 | } 11 | }); 12 | 13 | (function() { 14 | var fontSize = document.getElementById('fontSize'), 15 | testDrive = document.getElementById('testDrive'), 16 | testText = document.getElementById('testText'); 17 | function updateTest() { 18 | testDrive.innerHTML = testText.value || String.fromCharCode(160); 19 | if (window.icomoonLiga) { 20 | window.icomoonLiga(testDrive); 21 | } 22 | } 23 | function updateSize() { 24 | testDrive.style.fontSize = fontSize.value + 'px'; 25 | } 26 | fontSize.addEventListener('change', updateSize, false); 27 | testText.addEventListener('input', updateTest, false); 28 | testText.addEventListener('change', updateTest, false); 29 | updateSize(); 30 | }()); 31 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IcoMoon Demo 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Font Name: icomoon (Glyphs: 6)

13 |
14 |
15 |

Grid Size: 32

16 |
17 |
18 | 19 | icon-download 20 |
21 |
22 | 23 | 24 |
25 |
26 | liga: 27 | 28 |
29 |
30 |
31 |
32 | 33 | icon-adjust 34 |
35 |
36 | 37 | 38 |
39 |
40 | liga: 41 | 42 |
43 |
44 |
45 |
46 | 47 | icon-link-broken 48 |
49 |
50 | 51 | 52 |
53 |
54 | liga: 55 | 56 |
57 |
58 |
59 |
60 | 61 | icon-link-connected 62 |
63 |
64 | 65 | 66 |
67 |
68 | liga: 69 | 70 |
71 |
72 |
73 |
74 | 75 | icon-minus 76 |
77 |
78 | 79 | 80 |
81 |
82 | liga: 83 | 84 |
85 |
86 |
87 |
88 | 89 | icon-plus 90 |
91 |
92 | 93 | 94 |
95 |
96 | liga: 97 | 98 |
99 |
100 |
101 | 102 | 103 |
104 |

Font Test Drive

105 | 110 | 112 |
  113 |
114 |
115 | 116 |
117 |

Generated by IcoMoon

118 |
119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/src/app/assets/fonts/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiritMod/token-master/4a812b49ca15bf3cb40de363f9131ebe0195fa6b/src/app/assets/fonts/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/selection.json: -------------------------------------------------------------------------------- 1 | {"IcoMoonType":"selection","icons":[{"icon":{"paths":["M523.315 619.315l-11.315 11.312-107.315-107.312 22.63-22.63 68.685 68.688v-249.373h32v249.373l68.685-68.688 22.63 22.63-96 96zM736 672v32h-448v-32h448z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["download"],"colorPermutations":{}},"attrs":[],"properties":{"order":2,"id":5,"name":"download","prevSize":32,"code":59648},"setIdx":0,"setId":0,"iconIdx":0},{"icon":{"paths":["M384 513.6v-225.6h32v225.6c36.515 7.411 64 39.696 64 78.4s-27.485 70.989-64 78.4v65.6h-32v-65.6c-36.515-7.411-64-39.696-64-78.4s27.485-70.989 64-78.4zM448 592c0 26.509-21.491 48-48 48s-48-21.491-48-48c0-26.509 21.491-48 48-48s48 21.491 48 48zM608 736h32v-225.6c36.515-7.411 64-39.696 64-78.4s-27.485-70.989-64-78.4v-65.6h-32v65.6c-36.515 7.411-64 39.696-64 78.4s27.485 70.989 64 78.4v225.6zM672 432c0-26.509-21.491-48-48-48s-48 21.491-48 48c0 26.509 21.491 48 48 48s48-21.491 48-48z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["adjust"],"colorPermutations":{}},"attrs":[],"properties":{"order":3,"id":4,"name":"adjust","prevSize":32,"code":59649},"setIdx":0,"setId":0,"iconIdx":1},{"icon":{"paths":["M576 448v-64c0-35.347-28.653-64-64-64s-64 28.653-64 64v64h-32v-64c0-53.021 42.979-96 96-96s96 42.979 96 96v64h-32zM608 576h-32v64c0 35.347-28.653 64-64 64s-64-28.653-64-64v-64h-32v64c0 53.021 42.979 96 96 96s96-42.979 96-96v-64z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["link-broken"],"colorPermutations":{}},"attrs":[],"properties":{"order":4,"id":3,"name":"link-broken","prevSize":32,"code":59650},"setIdx":0,"setId":0,"iconIdx":2},{"icon":{"paths":["M512 320c35.347 0 64 28.653 64 64v64h32v-64c0-53.021-42.979-96-96-96s-96 42.979-96 96v64h32v-64c0-35.347 28.653-64 64-64zM576 576h32v64c0 53.021-42.979 96-96 96s-96-42.979-96-96v-64h32v64c0 35.347 28.653 64 64 64s64-28.653 64-64v-64zM496 416v192h32v-192h-32z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["link-connected"],"colorPermutations":{}},"attrs":[],"properties":{"order":5,"id":2,"name":"link-connected","prevSize":32,"code":59651},"setIdx":0,"setId":0,"iconIdx":3},{"icon":{"paths":["M688 528h-352v-32h352v32z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["minus"],"colorPermutations":{}},"attrs":[],"properties":{"order":6,"id":1,"name":"minus","prevSize":32,"code":59652},"setIdx":0,"setId":0,"iconIdx":4},{"icon":{"paths":["M496 496v-160h32v160h160v32h-160v160h-32v-160h-160v-32h160z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"grid":32,"tags":["plus"],"colorPermutations":{}},"attrs":[],"properties":{"order":7,"id":0,"name":"plus","prevSize":32,"code":59653},"setIdx":0,"setId":0,"iconIdx":5}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false,"noie8":true,"ie7":false,"cssVars":true,"cssVarsFormat":"scss","flutter":false,"showSelector":false,"showMetrics":false,"showMetadata":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215},"historySize":50,"gridSize":16,"showGrid":true}} -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: 4 | url('fonts/icomoon.ttf?vixr3b') format('truetype'), 5 | url('fonts/icomoon.woff?vixr3b') format('woff'), 6 | url('fonts/icomoon.svg?vixr3b#icomoon') format('svg'); 7 | font-weight: normal; 8 | font-style: normal; 9 | font-display: block; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icomoon' !important; 15 | speak: never; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-download:before { 28 | content: "\e900"; 29 | } 30 | .icon-adjust:before { 31 | content: "\e901"; 32 | } 33 | .icon-link-broken:before { 34 | content: "\e902"; 35 | } 36 | .icon-link-connected:before { 37 | content: "\e903"; 38 | } 39 | .icon-minus:before { 40 | content: "\e904"; 41 | } 42 | .icon-plus:before { 43 | content: "\e905"; 44 | } 45 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/style.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | @font-face { 4 | font-family: '#{$icomoon-font-family}'; 5 | src: 6 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.ttf?vixr3b') format('truetype'), 7 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.woff?vixr3b') format('woff'), 8 | url('#{$icomoon-font-path}/#{$icomoon-font-family}.svg?vixr3b##{$icomoon-font-family}') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | font-display: block; 12 | } 13 | 14 | [class^="icon-"], [class*=" icon-"] { 15 | /* use !important to prevent issues with browser extensions that change fonts */ 16 | font-family: '#{$icomoon-font-family}' !important; 17 | speak: never; 18 | font-style: normal; 19 | font-weight: normal; 20 | font-variant: normal; 21 | text-transform: none; 22 | line-height: 1; 23 | 24 | /* Better Font Rendering =========== */ 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | .icon-download { 30 | &:before { 31 | content: $icon-download; 32 | } 33 | } 34 | .icon-adjust { 35 | &:before { 36 | content: $icon-adjust; 37 | } 38 | } 39 | .icon-link-broken { 40 | &:before { 41 | content: $icon-link-broken; 42 | } 43 | } 44 | .icon-link-connected { 45 | &:before { 46 | content: $icon-link-connected; 47 | } 48 | } 49 | .icon-minus { 50 | &:before { 51 | content: $icon-minus; 52 | } 53 | } 54 | .icon-plus { 55 | &:before { 56 | content: $icon-plus; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/app/assets/fonts/icomoon/variables.scss: -------------------------------------------------------------------------------- 1 | $icomoon-font-family: "icomoon" !default; 2 | $icomoon-font-path: "fonts/icomoon/fonts" !default; 3 | 4 | $icon-download: "\e900"; 5 | $icon-adjust: "\e901"; 6 | $icon-link-broken: "\e902"; 7 | $icon-link-connected: "\e903"; 8 | $icon-minus: "\e904"; 9 | $icon-plus: "\e905"; 10 | -------------------------------------------------------------------------------- /src/app/assets/icons/adjust.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/assets/icons/create.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/assets/icons/down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/assets/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | download 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/assets/icons/empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/assets/icons/eyedropper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/assets/icons/link-broken.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | link-broken 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/assets/icons/link-connected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | link-connected 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/assets/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | minus 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/assets/icons/notice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | plus 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/assets/icons/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/components/app/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider } from "react-redux"; 3 | 4 | import Layout from "../../layout"; 5 | 6 | // Instruments 7 | import { store } from "../../store/store"; 8 | 9 | // 10 | import OnMessageWrapper from "../common/OnMessageWrapper"; 11 | 12 | 13 | const App = () => { 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/app/components/common/CreateNewMode/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef, useState } from 'react'; 2 | 3 | //styles 4 | import style from "./styles.module.scss"; 5 | 6 | // hooks 7 | import {useOnClickOutside} from "../../../_hooks/useOnClickOutside"; 8 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 9 | import {useUI} from "../../../store/ui/useUI"; 10 | 11 | //components 12 | import Button from "../../ui/Button/Button"; 13 | 14 | //icons 15 | import Down from "../../../assets/icons/down.svg"; 16 | import Check from "../../../assets/icons/check.svg"; 17 | 18 | //types 19 | import {ITokensItem} from "../../../store/figmaStyles/types"; 20 | 21 | 22 | const CreateNewMode = () => { 23 | const ref = useRef(); 24 | 25 | const { tokens, styles, themesList, setThemesList, setListToClientStorage } = useFigmaStyles(); 26 | const { toggleModalCreateMode } = useUI(); 27 | 28 | const [modeName, setModeName] = useState(''); 29 | const [modeBase, setModeBase] = useState(tokens[0]); 30 | const [modeError, setModeError] = useState(false); 31 | const [disabledBtn, setDisabledBtn] = useState(true); 32 | const [isOpen, setIsOpen] = useState(false); 33 | 34 | useEffect(() => { 35 | if (!!tokens.length) { 36 | setModeBase(tokens[0]); 37 | } 38 | }, [tokens]); 39 | 40 | const handleChangeField = (e) => { 41 | const notValidName = styles.reduce((total: number, item) => { 42 | if (item.themeName === e.target.value) { 43 | total = total + 1 44 | } 45 | return total 46 | }, 0); 47 | 48 | if (!!notValidName || e.target.value.length === 0 ) { 49 | setModeError(true); 50 | setDisabledBtn(true); 51 | } else { 52 | setModeError(false); 53 | setDisabledBtn(false); 54 | } 55 | 56 | setModeName(e.target.value); 57 | } 58 | 59 | const handleCreateNewMode = (name, base) => { 60 | const notValidName = styles.reduce((total: number, item) => { 61 | if (item.themeName === name) { 62 | total = total + 1 63 | } 64 | return total 65 | }, 0); 66 | 67 | if (name.length === 0 || base.length === 0 || !!notValidName) { 68 | setModeError(true); 69 | } else { 70 | /* Clone theme styles logic */ 71 | parent.postMessage({ pluginMessage: { type: 'create_new_mode', data: { 72 | data: base.data, 73 | oldName: base.themeName, 74 | name: name 75 | }}}, '*'); 76 | 77 | setThemesList( 78 | [ ...themesList, { id: Date.now(), value: name}] 79 | ); 80 | setListToClientStorage([ ...themesList, { id: Date.now(), value: name}]); 81 | 82 | // close modal 83 | toggleModalCreateMode(); 84 | } 85 | }; 86 | 87 | /* Dropdown */ 88 | useOnClickOutside(ref, () => setIsOpen(false)); 89 | 90 | 91 | const toggleDropdown = () => { 92 | setIsOpen(!isOpen); 93 | } 94 | 95 | const handleSelectedMode = (value) => { 96 | setModeBase(value); 97 | setIsOpen(false) 98 | } 99 | /* END Dropdown */ 100 | 101 | const filteredTokens = tokens.filter((item) => !!item.themeName.replace(/\s/g, '')); 102 | 103 | const list = filteredTokens.map((item:ITokensItem, index) => { 104 | return
handleSelectedMode(item)} 108 | > 109 | { modeBase.themeName === item.themeName && } 110 | {item.themeName} 111 |
112 | }); 113 | 114 | return ( 115 |
116 |
Select mode to create new mode
117 |
118 |
119 | handleChangeField(e)} 126 | /> 127 | 128 |
129 |
toggleDropdown()}> 130 | {`from ${modeBase.themeName}`} 131 | 132 |
133 |
134 | {list} 135 |
136 |
137 | 138 |
139 |
140 |
141 | 142 |
143 |
144 | ); 145 | } 146 | 147 | export default CreateNewMode; 148 | -------------------------------------------------------------------------------- /src/app/components/common/CreateNewMode/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .createMode { 4 | padding: 0 0 0; 5 | } 6 | 7 | .header { 8 | font-style: normal; 9 | font-weight: normal; 10 | font-size: 11px; 11 | line-height: 16px; 12 | letter-spacing: 0.005em; 13 | color: rgba($COLOR-BLACK, 0.8); 14 | padding: 12px 0; 15 | } 16 | 17 | .field { 18 | position: relative; 19 | input { 20 | font-style: normal; 21 | font-weight: normal; 22 | font-size: 11px; 23 | line-height: 1.45; 24 | letter-spacing: 0.005em; 25 | color: rgba($COLOR-BLACK, 0.8); 26 | 27 | width: 100%; 28 | height: 30px; 29 | border: 1px solid rgba($COLOR-BLACK, 0.1); 30 | box-sizing: border-box; 31 | border-radius: 2px; 32 | margin-right: 0; 33 | padding: 0 50% 0 8px; 34 | 35 | transition: border-color 0.15s ease 0s, 36 | box-shadow 0.15s ease 0s; 37 | 38 | text-overflow: ellipsis; 39 | white-space: nowrap; 40 | overflow: hidden; 41 | 42 | &:focus, &:focus-visible { 43 | border-color: $COLOR-FIGMA-BLUE; 44 | box-shadow: 0 0 0 1px $COLOR-FIGMA-BLUE; 45 | outline: none; 46 | } 47 | 48 | &.input_error { 49 | border-color: $COLOR-FIGMA-RED; 50 | box-shadow: 0 0 0 1px $COLOR-FIGMA-RED; 51 | 52 | } 53 | } 54 | } 55 | 56 | .content { 57 | margin: 0; 58 | } 59 | 60 | .footer { 61 | display: flex; 62 | align-items: center; 63 | justify-content: flex-end; 64 | padding-top: 10px; 65 | button { 66 | margin: 0; 67 | } 68 | } 69 | 70 | 71 | 72 | .dropdown { 73 | display: block; 74 | position: absolute; 75 | top: 0; 76 | right: 0; 77 | max-width: 40%; 78 | z-index: 10; 79 | &__btn { 80 | position: relative; 81 | display: flex; 82 | align-items: center; 83 | justify-content: flex-end; 84 | cursor: pointer; 85 | height: 30px; 86 | padding: 0 20px 0 4px; 87 | border: 1px solid rgba(0,0,0,0); 88 | border-radius: 2px; 89 | //transition: border-color 0.15s ease 0s; 90 | &:hover { 91 | //border-color: rgba($COLOR-BLACK, 0.3); 92 | svg { 93 | fill: rgba($COLOR-BLACK, 0.8); 94 | } 95 | } 96 | span { 97 | font-style: normal; 98 | font-weight: normal; 99 | font-size: 11px; 100 | line-height: 16px; 101 | letter-spacing: 0.005em; 102 | color: rgba($COLOR-BLACK, 0.8); 103 | } 104 | svg { 105 | display: block; 106 | width: 8px; 107 | height: 8px; 108 | position: absolute; 109 | right: 5px; 110 | top: 50%; 111 | transform: translateY(-50%); 112 | fill: rgba($COLOR-BLACK, 0.3); 113 | } 114 | } 115 | 116 | &__list { 117 | display: none; 118 | position: absolute; 119 | top: -5px; 120 | right: 0; 121 | left: 0; 122 | flex-direction: column; 123 | align-items: flex-start; 124 | justify-content: flex-start; 125 | padding: 8px 0; 126 | border-radius: 2px; 127 | background-color: #222; 128 | box-shadow: 0 5px 17px rgba(0,0,0,.2), 129 | 0 2px 7px rgba(0,0,0,.15), 130 | inset 0 0 0 1px #000, 131 | 0 0 0 1px rgba(0,0,0,0.1); 132 | 133 | &_open { 134 | display: flex; 135 | } 136 | 137 | &_item { 138 | position: relative; 139 | display: flex; 140 | align-items: center; 141 | justify-content: flex-start; 142 | color: #fff; 143 | font-size: 11px; 144 | line-height: 16px; 145 | padding: 0 8px 0 24px; 146 | height: 24px; 147 | width: 100%; 148 | cursor: pointer; 149 | &:hover { 150 | background: $COLOR-FIGMA-BLUE; 151 | } 152 | svg { 153 | fill: #fff; 154 | width: 16px; 155 | height: 16px; 156 | display: block; 157 | position: absolute; 158 | left: 4px; 159 | top: 50%; 160 | transform: translateY(-50%); 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/app/components/common/DesignTokens/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React from "react"; 3 | import Accordion from "@mui/material/Accordion"; 4 | import AccordionSummary from "@mui/material/AccordionSummary"; 5 | import AccordionDetails from "@mui/material/AccordionDetails"; 6 | 7 | // types 8 | import {ITokensItem} from "../../../store/figmaStyles/types"; 9 | 10 | // styles 11 | import classes from "./styles.module.scss"; 12 | 13 | //icons 14 | import Right from "../../../assets/icons/right.svg"; 15 | 16 | //hooks 17 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 18 | 19 | //components 20 | import PaletteItem from "../PaletteItem"; 21 | import Empty from "../../ui/Empty"; 22 | 23 | const DesignTokens = () => { 24 | 25 | const { tokens } = useFigmaStyles(); 26 | 27 | const emptyData = tokens.reduce( (total:number, item:ITokensItem) => { 28 | !!item.data.length && total++; 29 | return total 30 | }, 0); 31 | 32 | const filteredTokens = tokens.filter((item) => !!item.data.length && !!item.themeName.replace(/\s/g, '')); 33 | 34 | const renderColorsList = (arr) => { 35 | return !!arr.length ? arr.map((item:ITokensItem, index: number) => { 36 | return ( 37 | 47 | } 55 | aria-controls={`panel${index}a-content`} 56 | id={`panel${index}a-header`} 57 | > 58 | 59 | { !!item.themeName.replace(/\s/g, '') ? item.themeName : 'Empty mode name' } 60 | 61 | 62 | 63 | { 64 | item.data.map((current, i) => { 65 | return 66 | }) 67 | } 68 | 69 | 70 | ) 71 | }) : '' 72 | }; 73 | 74 | console.log() 75 | 76 | const list = !!emptyData && renderColorsList(filteredTokens); 77 | 78 | const empty = !emptyData && 79 | 80 | return ( 81 |
82 | {list} 83 | {empty} 84 |
85 | ); 86 | }; 87 | 88 | export default DesignTokens; 89 | -------------------------------------------------------------------------------- /src/app/components/common/DesignTokens/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .wrapper { 4 | margin: 0 -16px; 5 | &.empty { 6 | padding: 3px 0 0 0; 7 | } 8 | } 9 | 10 | .accordion { 11 | $self: &; 12 | position: relative; 13 | border-radius: 0!important; 14 | border: none!important; 15 | box-shadow: none!important; 16 | z-index: 10; 17 | 18 | &:before { 19 | display: none!important; 20 | opacity: 1!important; 21 | background-color: $COLOR-LIGHT-GREY!important; 22 | } 23 | 24 | 25 | &__expanded_root { 26 | margin: 0!important; 27 | #{$self}__root { 28 | &:after { 29 | //display: none; 30 | } 31 | } 32 | } 33 | 34 | 35 | &__root { 36 | padding: 0 16px!important; 37 | border: none!important; 38 | box-shadow: none!important; 39 | min-height: 32px!important; 40 | position: relative; 41 | margin: 0!important; 42 | &:after { 43 | content: ''; 44 | display: block; 45 | position: absolute; 46 | left: 0; 47 | right: 0; 48 | top: 0; 49 | bottom: 0; 50 | z-index: 4; 51 | border: 1px solid #18a0fb; 52 | pointer-events: none; 53 | opacity: 0; 54 | } 55 | &:hover { 56 | &:after { 57 | opacity: 1; 58 | } 59 | } 60 | } 61 | 62 | &__summary { 63 | margin: 0!important; 64 | } 65 | 66 | &__expanded { 67 | #{$self}__iconWrapper { 68 | transform: translateY(-50%) rotate(90deg) !important; 69 | } 70 | } 71 | 72 | &__iconWrapper { 73 | display: block; 74 | position: absolute; 75 | left: 5px; 76 | top: 50%; 77 | transform: translateY(-50%) rotate(0deg) !important; 78 | svg { 79 | fill: rgba(#000,0.3); 80 | width: 8px; 81 | height: 8px; 82 | } 83 | } 84 | 85 | &__title { 86 | font-style: normal; 87 | font-weight: 600; 88 | font-size: 11px; 89 | line-height: 16px; 90 | letter-spacing: 0.005em; 91 | font-feature-settings: 'ss02' on; 92 | color: rgba(0, 0, 0, 0.8); 93 | } 94 | 95 | &__icon { 96 | //background: red!important; 97 | } 98 | 99 | 100 | 101 | &__details { 102 | padding: 0 16px!important; 103 | } 104 | } 105 | 106 | 107 | .line { 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/app/components/common/EditColor/index.tsx: -------------------------------------------------------------------------------- 1 | // core 2 | import React, { useEffect, useState, useRef } from "react"; 3 | import { ChromePicker } from "react-color"; 4 | 5 | // styles 6 | import classes from "./styles.module.scss"; 7 | 8 | // icons 9 | import EyeDropper from "../../../assets/icons/eyedropper.svg"; 10 | 11 | // components 12 | import Button from "../../ui/Button/Button"; 13 | 14 | // helpers 15 | import {hexToRgba, rgbaToFigma} from "../../../_helpers/colors"; 16 | 17 | // hooks 18 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 19 | 20 | const inlineStyles = { 21 | container: { 22 | boxShadow: 'none', 23 | height: 'auto', 24 | width: '100%', 25 | }, 26 | }; 27 | 28 | const EditColor = () => { 29 | 30 | const { selectedColor, editedData } = useFigmaStyles(); 31 | const inputColor = useRef(null); 32 | const [color, setColor] = useState({ rgb: {r: 0, g: 0, b: 0, a: 1} }); 33 | 34 | useEffect(() => { 35 | if (!!selectedColor) { 36 | setColor(selectedColor); 37 | } 38 | }, [selectedColor]); 39 | 40 | 41 | const onChangeComplete = (color) => { 42 | setColor(color); 43 | // color = { 44 | // hex: '#333', 45 | // rgb: { 46 | // r: 51, 47 | // g: 51, 48 | // b: 51, 49 | // a: 1, 50 | // }, 51 | // hsl: { 52 | // h: 0, 53 | // s: 0, 54 | // l: .20, 55 | // a: 1, 56 | // }, 57 | // } 58 | } 59 | 60 | const onChangeInputColor = (e) => { 61 | const newColor = { rgb: hexToRgba(e.target.value) }; 62 | setColor(newColor); 63 | inputColor.current.blur(); 64 | inputColor.current.value = ""; 65 | } 66 | 67 | const handleUpdate = () => { 68 | const newColor = rgbaToFigma(color.rgb); 69 | 70 | parent.postMessage({ pluginMessage: { type: 'update_paint_styles', data: { 71 | color: newColor, 72 | styles: editedData, 73 | }}}, '*'); 74 | }; 75 | 76 | return ( 77 | <> 78 |
79 | 85 |
86 |
87 |
88 | onChangeInputColor(e)} 94 | /> 95 | 98 |
99 | 100 |
101 | 102 | ) 103 | }; 104 | 105 | export default EditColor; 106 | -------------------------------------------------------------------------------- /src/app/components/common/EditColor/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .picker { 4 | margin: 0 -16px 0; 5 | } 6 | 7 | .pipette { 8 | position: relative; 9 | width: 30px; 10 | height: 30px; 11 | input { 12 | position: absolute; 13 | left: 0; 14 | right: 0; 15 | top: 0; 16 | bottom: 0; 17 | z-index: 0; 18 | display: block; 19 | margin: 0; 20 | padding: 0; 21 | width: 32px; 22 | height: 32px; 23 | opacity: 0; 24 | border: none; 25 | } 26 | label { 27 | position: relative; 28 | z-index: 1; 29 | cursor: pointer; 30 | display: block; 31 | &:hover { 32 | svg { 33 | fill: rgba($COLOR-BLACK,1); 34 | } 35 | } 36 | svg { 37 | fill: rgba($COLOR-BLACK,0.8); 38 | } 39 | } 40 | } 41 | 42 | 43 | .footer { 44 | display: flex; 45 | align-items: center; 46 | justify-content: space-between; 47 | padding-top: 12px; 48 | button { 49 | margin: 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/components/common/OnMessageWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | // core 2 | import React, {useEffect} from "react"; 3 | 4 | // hooks 5 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 6 | 7 | interface ComponentProps { 8 | children: JSX.Element | string; 9 | } 10 | 11 | const OnMessageWrapper = (props:ComponentProps) => { 12 | const { children } = props; 13 | 14 | const { setData, setThemesList } = useFigmaStyles(); 15 | 16 | useEffect(() => { 17 | // This is how we read messages sent from the plugin controller 18 | window.onmessage = (event) => { 19 | const {type, message} = event.data.pluginMessage; 20 | 21 | if (type === 'styles') { 22 | setData(message); 23 | } 24 | 25 | if (type === 'themes_list') { 26 | setThemesList(JSON.parse(message)); 27 | } 28 | }; 29 | }, []); 30 | 31 | 32 | return ( 33 | <>{children} 34 | ); 35 | }; 36 | 37 | export default OnMessageWrapper; 38 | -------------------------------------------------------------------------------- /src/app/components/common/PaletteItem/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React from 'react'; 3 | 4 | //components 5 | import ColorItem from "../../ui/ColorItem"; 6 | 7 | //styles 8 | import classes from "./styles.module.scss"; 9 | 10 | // helpers 11 | import {rgbToHex, rgbToRgba} from "../../../_helpers/colors"; 12 | 13 | //hooks 14 | import {useUI} from "../../../store/ui/useUI"; 15 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 16 | 17 | const PaletteItem = (props) => { 18 | 19 | const { data } = props; 20 | 21 | const { toggleModalEditColor } = useUI(); 22 | const { setSelectedColor, setEditedData } = useFigmaStyles(); 23 | 24 | const colors = data.map((item, i) => { 25 | return 35 | }); 36 | 37 | const handleClick = (color) => { 38 | const { r,g,b,a } = color; 39 | const rgba = rgbToRgba({r,g,b}, a); 40 | 41 | setSelectedColor({rgb: rgba}); 42 | setEditedData(data); 43 | 44 | toggleModalEditColor(); 45 | }; 46 | 47 | return ( 48 |
49 |
50 |
51 | 62 |
63 |
64 | {colors} 65 |
66 |
67 |
68 | ); 69 | }; 70 | 71 | export default PaletteItem; 72 | -------------------------------------------------------------------------------- /src/app/components/common/PaletteItem/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .item { 4 | margin: 0 -16px 0; 5 | border-bottom: 1px solid $COLOR-GRAY; 6 | &:last-child { 7 | border-bottom: none!important; 8 | } 9 | } 10 | 11 | .header { 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | padding: 0 4px 0 16px; 16 | height: 40px; 17 | } 18 | 19 | .title { 20 | font-style: normal; 21 | font-weight: 600; 22 | font-size: 11px; 23 | line-height: 1.4; 24 | letter-spacing: 0.005em; 25 | color: rgba($COLOR-BLACK, 0.8); 26 | } 27 | 28 | .content { 29 | padding: 8px 16px; 30 | display: flex; 31 | align-items: flex-start; 32 | flex-wrap: wrap; 33 | } 34 | 35 | .col_left { 36 | width: 30%; 37 | padding-right: 16px; 38 | } 39 | 40 | .col_right { 41 | width: 70%; 42 | } 43 | 44 | .pointer { 45 | cursor: pointer; 46 | } 47 | -------------------------------------------------------------------------------- /src/app/components/common/Settings/Field/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React, {useState} from "react"; 3 | 4 | //styles 5 | import styles from "./styles.module.scss"; 6 | 7 | //icons 8 | import Minus from "../../../../assets/icons/minus.svg"; 9 | 10 | //interface 11 | interface ComponentProps { 12 | id: number, 13 | value: string, 14 | key: any, 15 | handlerRemove: (e, id: number) => void, 16 | handleChangeValue: (value: string, id: any) => void 17 | } 18 | 19 | const Field: React.FC = (props) => { 20 | const { id, value, handlerRemove, handleChangeValue } = props; 21 | 22 | const [fieldValue, setFieldValue] = useState(value) 23 | 24 | const handleChange = (e, id) => { 25 | setFieldValue(e.target.value); 26 | handleChangeValue(e.target.value, id); 27 | } 28 | 29 | return ( 30 |
31 | handleChange(e, id)}/> 32 | 33 |
34 | ); 35 | }; 36 | 37 | export default Field; 38 | -------------------------------------------------------------------------------- /src/app/components/common/Settings/Field/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../styles/settings/settings.scss'; 2 | 3 | .field { 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-between; 7 | height: 32px; 8 | margin: 0 -12px 8px 0; 9 | &:last-child { 10 | margin-bottom: 0; 11 | } 12 | 13 | input { 14 | font-style: normal; 15 | font-weight: normal; 16 | font-size: 11px; 17 | line-height: 1.45; 18 | letter-spacing: 0.005em; 19 | color: rgba($COLOR-BLACK, 0.8); 20 | 21 | width: 100%; 22 | height: 30px; 23 | border: 1px solid rgba($COLOR-BLACK, 0.1); 24 | box-sizing: border-box; 25 | border-radius: 2px; 26 | margin-right: 8px; 27 | padding: 0 8px; 28 | 29 | transition: border-color 0.15s ease 0s, 30 | box-shadow 0.15s ease 0s; 31 | &:focus, &:focus-visible { 32 | border-color: $COLOR-FIGMA-BLUE; 33 | box-shadow: 0 0 0 1px $COLOR-FIGMA-BLUE; 34 | outline: none; 35 | } 36 | } 37 | 38 | button { 39 | width: 32px; 40 | height: 32px; 41 | border: none; 42 | box-shadow: none; 43 | padding: 0; 44 | margin: 0; 45 | font-size: 32px; 46 | background: transparent; 47 | border-radius: 2px; 48 | color: rgba($COLOR-BLACK,0.8); 49 | cursor: pointer; 50 | transition: all 0.15s ease 0s; 51 | &:hover { 52 | background: rgba($COLOR-BLACK,0.06); 53 | svg { 54 | fill: rgba($COLOR-BLACK,1); 55 | } 56 | } 57 | svg { 58 | fill: rgba($COLOR-BLACK,0.8); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/components/common/Settings/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React, {useEffect, useState} from 'react'; 3 | 4 | //styles 5 | import classes from "./styles.module.scss"; 6 | 7 | //icons 8 | import Plus from "../../../assets/icons/plus.svg"; 9 | import Create from "../../../assets/icons/create.svg"; 10 | 11 | //hooks 12 | import {useFigmaStyles} from "../../../store/figmaStyles/useFigmaStyles"; 13 | import {useUI} from "../../../store/ui/useUI"; 14 | 15 | //types 16 | import {IThemeItem} from "../../../store/figmaStyles/types"; 17 | 18 | //components 19 | import Button from "../../ui/Button/Button"; 20 | import Field from "./Field"; 21 | import Empty from "../../ui/Empty"; 22 | 23 | 24 | const Settings = () => { 25 | 26 | const { styles, themesList, setThemesList, setListToClientStorage } = useFigmaStyles(); 27 | const { toggleModalCreateMode } = useUI(); 28 | 29 | const [disableAddBtn, setDisableAddBtn] = useState(false); 30 | const [disableNewModeBtn, setDisableNewModeBtn] = useState(true); 31 | 32 | useEffect(() => { 33 | if (themesList.length === 0 || themesList[themesList.length-1].value !== '') { 34 | setDisableAddBtn(false); 35 | } else { 36 | setDisableAddBtn(true); 37 | } 38 | 39 | const notModeInStyles = styles.reduce((total: number, item) => { 40 | themesList.map((el) => { 41 | if (item.themeName === el.value) { 42 | total = total + 1 43 | } 44 | }) 45 | 46 | return total 47 | }, 0); 48 | 49 | if (notModeInStyles > 0) { 50 | setDisableNewModeBtn(false) 51 | } else { 52 | setDisableNewModeBtn(true) 53 | } 54 | 55 | }, [themesList]); 56 | 57 | const handlerAddField = (e) => { 58 | e.preventDefault(); 59 | 60 | const list = [ 61 | ...themesList, 62 | { id: Date.now(), value: ''} 63 | ]; 64 | 65 | setThemesList(list); 66 | setListToClientStorage(list); 67 | }; 68 | 69 | const handleRemoveField = (e, id) => { 70 | e.preventDefault(); 71 | 72 | const newArr = themesList.reduce((arr, current) => { 73 | if (!(current.id === id)) { 74 | arr.push(current); 75 | } 76 | return arr; 77 | }, []); 78 | 79 | setThemesList(newArr); 80 | setListToClientStorage(newArr); 81 | }; 82 | 83 | const handleChangeValue = (value: string, id: any) => { 84 | const updatedThemesList = themesList.reduce((arr:IThemeItem[], current:IThemeItem) => { 85 | if (id === current.id) { 86 | arr.push({ 87 | ...current, 88 | value: value 89 | }); 90 | } else { 91 | arr.push(current); 92 | } 93 | 94 | return arr 95 | }, []); 96 | 97 | setThemesList(updatedThemesList); 98 | setListToClientStorage(updatedThemesList); 99 | } 100 | 101 | const FieldList = !!themesList.length && themesList.map((item) => { 102 | return 109 | }); 110 | 111 | const empty = !themesList.length && 112 | 113 | return ( 114 | <> 115 |
116 |
117 |
118 |
119 |

Mode

120 | 121 |
122 |
123 | {FieldList} 124 | {empty} 125 |
126 |
127 | 128 |
129 |
130 | 133 |
134 |
135 | 136 | ); 137 | }; 138 | 139 | export default Settings; 140 | -------------------------------------------------------------------------------- /src/app/components/common/Settings/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .settings { 4 | display: flex; 5 | flex: 1 1 auto; 6 | flex-direction: column; 7 | width: 100%; 8 | height: 100%; 9 | &__header { 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | height: 40px; 14 | padding: 4px 0; 15 | margin: 0 -12px 0 0; 16 | h4 { 17 | font-style: normal; 18 | font-weight: 600; 19 | font-size: 11px; 20 | line-height: 1.45; 21 | letter-spacing: 0.005em; 22 | color: rgba($COLOR-BLACK,0.8); 23 | } 24 | 25 | button { 26 | width: 32px; 27 | height: 32px; 28 | border: none; 29 | box-shadow: none; 30 | padding: 0; 31 | margin: 0; 32 | font-size: 32px; 33 | background: transparent; 34 | border-radius: 3px; 35 | color: rgba($COLOR-BLACK,0.8); 36 | cursor: pointer; 37 | transition: all 0.15s ease 0s; 38 | &:hover { 39 | background: rgba($COLOR-BLACK,0.06); 40 | svg { 41 | fill: rgba($COLOR-BLACK,1); 42 | } 43 | } 44 | svg { 45 | fill: rgba($COLOR-BLACK,0.8); 46 | } 47 | } 48 | } 49 | 50 | &__content { 51 | flex: 1 1 auto; 52 | } 53 | 54 | &__modes { 55 | display: flex; 56 | flex-direction: column; 57 | flex: 1 1 auto; 58 | height: 100%; 59 | } 60 | 61 | &__footer { 62 | height: 48px; 63 | margin: 0 -16px -15px; 64 | padding: 7px 4px 8px; 65 | display: flex; 66 | align-items: center; 67 | justify-content: flex-start; 68 | border-top: 1px solid #E5E5E5; 69 | button { 70 | height: 32px; 71 | margin: 0; 72 | padding: 0; 73 | &:not(:last-child) { 74 | margin-left: 8px; 75 | } 76 | } 77 | } 78 | 79 | 80 | &__list { 81 | display: flex; 82 | flex-direction: column; 83 | flex: 1 1 auto; 84 | height: 100%; 85 | } 86 | } 87 | 88 | 89 | .api { 90 | margin: 0 -16px; 91 | padding: 0 16px 10px; 92 | border-bottom: 1px solid $COLOR-GRAY; 93 | &__content { 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/app/components/ui/Button/Button.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .base { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | height: 30px; 8 | box-shadow: none; 9 | padding: 6px 15px; 10 | margin: 0 5px; 11 | outline: none; 12 | border-radius: 6px; 13 | box-sizing: border-box; 14 | 15 | font-style: normal; 16 | font-weight: 500; 17 | font-size: 11px; 18 | line-height: 1.3; 19 | letter-spacing: 0.01em; 20 | } 21 | 22 | .primary { 23 | background-color: $COLOR-FIGMA-BLUE; 24 | color: $COLOR-WHITE; 25 | border: none; 26 | &:hover { 27 | background-color: darken($COLOR-FIGMA-BLUE, 15%); 28 | cursor: pointer; 29 | } 30 | } 31 | 32 | .secondary { 33 | background-color: $COLOR-WHITE; 34 | color: rgba($COLOR-BLACK, 0.8); 35 | border: 1px solid rgba($COLOR-BLACK, 0.8); 36 | 37 | &:hover { 38 | border-color: darken($COLOR-FIGMA-BLUE, 15%); 39 | color: darken($COLOR-FIGMA-BLUE, 15%); 40 | cursor: pointer; 41 | //text-decoration: underline; 42 | } 43 | } 44 | 45 | .text { 46 | border: none!important; 47 | color: rgba($COLOR-BLACK, 0.8); 48 | &:hover { 49 | color: darken($COLOR-FIGMA-BLUE, 15%); 50 | svg { 51 | path { 52 | fill: darken($COLOR-FIGMA-BLUE, 15%); 53 | } 54 | } 55 | } 56 | svg { 57 | width: 32px; 58 | height: 32px; 59 | fill: rgba($COLOR-BLACK, 0.8); 60 | } 61 | &.disabled { 62 | background-color: transparent!important; 63 | color: rgba($COLOR-BLACK, 0.3); 64 | svg { 65 | path { 66 | fill: rgba($COLOR-BLACK, 0.3); 67 | } 68 | } 69 | } 70 | } 71 | 72 | .disabled { 73 | pointer-events: none; 74 | background-color: #b3b3b3; 75 | color: rgba($COLOR-WHITE, 1); 76 | border: 1px solid #b3b3b3; 77 | &:hover { 78 | background-color: #b3b3b3; 79 | color: rgba($COLOR-WHITE, 1); 80 | border: 1px solid #b3b3b3; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/app/components/ui/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './Button.module.scss'; 4 | 5 | interface ComponentProps { 6 | disabled?: boolean, 7 | onClick: React.MouseEventHandler, 8 | children: JSX.Element | string, 9 | secondary?: boolean, 10 | variant?: string, 11 | } 12 | 13 | const Button = ({ onClick, children, secondary = false, disabled, variant }: ComponentProps) => { 14 | const buttonType = secondary ? styles?.secondary : styles?.primary; 15 | 16 | const cls = [buttonType]; 17 | 18 | if (variant === "text") { 19 | cls.push(styles.text); 20 | } 21 | 22 | return ( 23 | 26 | ); 27 | }; 28 | 29 | export default Button; 30 | -------------------------------------------------------------------------------- /src/app/components/ui/ColorItem/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React from "react"; 3 | 4 | //styles 5 | import classes from "./styles.module.scss"; 6 | 7 | interface ComponentProps { 8 | color: any, 9 | rgba: any, 10 | opacity: number, 11 | label: string, 12 | onClick?: any, 13 | children?: JSX.Element | string, 14 | className?: string 15 | } 16 | 17 | const ColorItem = ({ color, rgba, opacity, label, onClick, className }: ComponentProps) => { 18 | const opacityCls = [classes.opacity]; 19 | const boxCls = [classes.box]; 20 | const colorItem = [classes.item]; 21 | 22 | if (className) { 23 | colorItem.push(className); 24 | } 25 | 26 | if (opacity === 1) { 27 | opacityCls.push(classes.opacity__fill) 28 | } 29 | 30 | if (color === '#ffffff') { 31 | boxCls.push(classes.border) 32 | } 33 | 34 | const handlerClick = (e) => { 35 | e.preventDefault() 36 | if ( typeof onClick === 'function' ) { 37 | if (rgba) { 38 | onClick(rgba) 39 | } 40 | } 41 | }; 42 | 43 | return ( 44 |
48 |
49 | 50 | 51 |
52 |
{label}
53 |
54 | ); 55 | 56 | }; 57 | 58 | export default ColorItem; 59 | -------------------------------------------------------------------------------- /src/app/components/ui/ColorItem/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .item { 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-start; 7 | height: 32px; 8 | width: 100%; 9 | padding: 8px 0; 10 | } 11 | 12 | .box { 13 | position: relative; 14 | margin: 0 8px 0 0; 15 | display: block; 16 | width: 16px; 17 | height: 16px; 18 | background: url("data:image/svg+xml;utf8,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M0%200H3V3H0V0Z%22%20fill%3D%22%23E1E1E1%22/%3E%3Cpath%20d%3D%22M3%200H6V3H3V0Z%22%20fill%3D%22white%22/%3E%3Cpath%20d%3D%22M3%203H6V6H3V3Z%22%20fill%3D%22%23E1E1E1%22/%3E%3Cpath%20d%3D%22M0%203H3V6H0V3Z%22%20fill%3D%22white%22/%3E%3C/svg%3E%0A"); 19 | border-radius: 2px; 20 | overflow: hidden; 21 | } 22 | 23 | .border { 24 | border: 1px solid $COLOR-GRAY; 25 | } 26 | 27 | .color { 28 | width: 8px; 29 | display: block; 30 | position: absolute; 31 | left: 0; 32 | top: 0; 33 | bottom: 0; 34 | z-index: 2; 35 | } 36 | 37 | .opacity { 38 | display: block; 39 | position: absolute; 40 | left: 0; 41 | right: 0; 42 | top: 0; 43 | bottom: 0; 44 | z-index: 1; 45 | opacity: 0.61; 46 | &__fill { 47 | opacity: 1; 48 | } 49 | } 50 | 51 | .title { 52 | font-style: normal; 53 | font-weight: normal; 54 | font-size: 11px; 55 | line-height: 1.45; 56 | letter-spacing: 0.005em; 57 | color: rgba(0, 0, 0, 0.8); 58 | width: calc( 100% - 24px ); 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/app/components/ui/Empty/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React from "react"; 3 | 4 | // styles 5 | import classes from "./styles.module.scss"; 6 | 7 | //icons 8 | import EmptyIcon from "../../../assets/icons/empty.svg"; 9 | 10 | 11 | const Empty = (props) => { 12 | const { text } = props; 13 | 14 | return ( 15 |
16 |
17 |
{text}
18 |
19 | ); 20 | }; 21 | 22 | export default Empty; 23 | -------------------------------------------------------------------------------- /src/app/components/ui/Empty/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .empty { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-direction: column; 8 | height: 100%; 9 | } 10 | 11 | .icon { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | margin-bottom: 16px; 16 | svg { 17 | width: 64px; 18 | height: 64px; 19 | } 20 | } 21 | 22 | .text { 23 | font-style: normal; 24 | font-weight: normal; 25 | font-size: 11px; 26 | line-height: 1.45; 27 | letter-spacing: 0.005em; 28 | color: rgba(0, 0, 0, 0.3); 29 | text-align: center; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/ui/InputField/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | 3 | //styles 4 | import styles from './styles.module.scss'; 5 | 6 | const InputField = (props) => { 7 | 8 | const { type, value, placeholder, callback, className } = props; 9 | 10 | const [fieldValue, setFieldValue] = useState(value); 11 | 12 | 13 | useEffect(() => { 14 | setFieldValue(value) 15 | }, [value]); 16 | 17 | 18 | const handleChangeValue = (e) => { 19 | setFieldValue(e.target.value); 20 | callback(e.target.value); 21 | } 22 | 23 | return ( 24 |
25 | handleChangeValue(e)} 32 | /> 33 |
34 | ); 35 | }; 36 | 37 | export default InputField; 38 | -------------------------------------------------------------------------------- /src/app/components/ui/InputField/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/settings/settings.scss'; 2 | 3 | .field { 4 | input { 5 | font-style: normal; 6 | font-weight: normal; 7 | font-size: 11px; 8 | line-height: 1.45; 9 | letter-spacing: 0.005em; 10 | color: rgba($COLOR-BLACK, 0.8); 11 | 12 | width: 100%; 13 | height: 30px; 14 | border: 1px solid rgba($COLOR-BLACK, 0.1); 15 | box-sizing: border-box; 16 | border-radius: 2px; 17 | margin-right: 0; 18 | padding: 0 8px; 19 | 20 | transition: border-color 0.15s ease 0s, 21 | box-shadow 0.15s ease 0s; 22 | 23 | text-overflow: ellipsis; 24 | white-space: nowrap; 25 | overflow: hidden; 26 | 27 | &:focus, &:focus-visible { 28 | border-color: $COLOR-FIGMA-BLUE; 29 | box-shadow: 0 0 0 1px $COLOR-FIGMA-BLUE; 30 | outline: none; 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/app/components/ui/ModalDialog/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React from "react"; 3 | import { Modal, ModalBody } from "reactstrap"; 4 | 5 | import Close from "../../../assets/icons/close.svg"; 6 | 7 | const ModalDialog = ({ 8 | title, 9 | children, 10 | className, 11 | closeCls, 12 | isOpen, 13 | toggle, 14 | backdrop = true, 15 | }) => { 16 | return ( 17 | 24 |
25 | { 26 | title &&
{title}
27 | } 28 |
29 | 30 |
31 |
32 | 33 |
34 | {children} 35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default ModalDialog; 42 | -------------------------------------------------------------------------------- /src/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./components/app"; 4 | 5 | import "./styles/constructor.scss"; 6 | 7 | ReactDOM.render(, document.getElementById('react-page')); 8 | -------------------------------------------------------------------------------- /src/app/layout/index.tsx: -------------------------------------------------------------------------------- 1 | //core 2 | import React, {useState} from "react"; 3 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; 4 | 5 | //styles 6 | import classes from "./styles/styles.module.scss"; 7 | import tabs from "./styles/tabs.module.scss"; 8 | 9 | //hooks 10 | import {useUI} from "../store/ui/useUI"; 11 | 12 | // components 13 | import DesignTokens from "../components/common/DesignTokens"; 14 | import Settings from "../components/common/Settings"; 15 | import ModalDialog from "../components/ui/ModalDialog"; 16 | import CreateNewMode from "../components/common/CreateNewMode"; 17 | import EditColor from "../components/common/EditColor"; 18 | 19 | //icons 20 | import Notice from "../assets/icons/notice.svg" 21 | 22 | const Layout = () => { 23 | const [tabIndex, setTabIndex] = useState(0); 24 | 25 | const { modalCreateMode, modalEditColor, toggleModalCreateMode, toggleModalEditColor } = useUI(); 26 | 27 | return ( 28 | <> 29 |
30 | 31 | setTabIndex(index)} > 32 |
33 | 34 | Design tokens 35 | Settings 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 | 62 |
63 | 64 |
65 |
66 | 67 | 74 |
75 | 76 |
77 |
78 | 79 | ); 80 | }; 81 | 82 | export default Layout; 83 | -------------------------------------------------------------------------------- /src/app/layout/styles/styles.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/settings/settings.scss'; 2 | 3 | .layout { 4 | display: flex; 5 | flex: 1 1 auto; 6 | flex-direction: column; 7 | position: relative; 8 | padding: 40px 16px 16px; 9 | } 10 | 11 | .header { 12 | position: fixed; 13 | left: 0; 14 | right: 0; 15 | top: 0; 16 | height: 40px; 17 | border-bottom: 1px solid $COLOR-GRAY; 18 | background: #fff; 19 | z-index: 99; 20 | padding: 0 16px; 21 | } 22 | 23 | .main { 24 | display: flex; 25 | flex: 1 1 auto; 26 | width: 100%; 27 | } 28 | 29 | .link { 30 | display: block; 31 | position: absolute; 32 | right: 4px; 33 | top: 50%; 34 | transform: translateY(-50%); 35 | &:hover { 36 | svg { 37 | path { 38 | fill: darken($COLOR-FIGMA-BLUE, 15%); 39 | } 40 | } 41 | } 42 | svg { 43 | width: 32px; 44 | height: 32px; 45 | path { 46 | fill: rgba($COLOR-BLACK, 0.8); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/layout/styles/tabs.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/settings/settings.scss'; 2 | 3 | .react_tabs { 4 | -webkit-tap-highlight-color: transparent; 5 | flex: 1 1 auto; 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | } 10 | 11 | .react_tabs__tab_list { 12 | display: flex; 13 | align-items: center; 14 | justify-content: flex-start; 15 | height: 100%; 16 | } 17 | 18 | .react_tabs__tab { 19 | display: inline-block; 20 | border-bottom: none; 21 | position: relative; 22 | list-style: none; 23 | cursor: pointer; 24 | 25 | font-style: normal; 26 | font-weight: 500; 27 | font-size: 11px; 28 | line-height: 1.45; 29 | letter-spacing: 0.005em; 30 | color: rgba($COLOR-BLACK, 0.3); 31 | 32 | &:not(:last-child) { 33 | margin-right: 16px; 34 | } 35 | } 36 | 37 | .react_tabs__tab__selected { 38 | color: rgba($COLOR-BLACK, 0.8); 39 | font-weight: 600; 40 | } 41 | 42 | .react_tabs__tab__disabled { 43 | color: GrayText; 44 | cursor: default; 45 | } 46 | 47 | .react_tabs__tab:focus { 48 | outline: none; 49 | } 50 | 51 | .react-tabs__tab:focus:after { 52 | 53 | } 54 | 55 | .react_tabs__tab_panel { 56 | display: none; 57 | width: 100%; 58 | } 59 | 60 | .react_tabs__tab_panel__selected { 61 | display: block; 62 | } 63 | -------------------------------------------------------------------------------- /src/app/models/IAction.ts: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux'; 2 | 3 | /** 4 | * https://github.com/acdlite/flux-standard-action 5 | */ 6 | export default interface IAction extends Action { 7 | /* 8 | * The type of an action identifies to the consumer the nature of the action that has occurred. 9 | * "type" is a string constant. 10 | * If two types are the same, they MUST be strictly equivalent (using ===). 11 | */ 12 | readonly type: string; 13 | /* 14 | * (optional) 15 | * The optional payload property MAY be any type of value. It represents the payload of the action. 16 | * Any information about the action that is not the type or status of the action should be part of the payload field. 17 | * 18 | * By convention, if error is true, the payload SHOULD be an error object. This is akin to rejecting a promise with an error object. 19 | */ 20 | readonly payload?: any; 21 | /* 22 | * (optional) 23 | * The optional error property MAY be set to true if the action represents an error. 24 | * An action whose error is true is analogous to a rejected Promise. By convention, the payload SHOULD be an error object. 25 | * If error has any other value besides true, including undefined and null, the action MUST NOT be interpreted as an error. 26 | * 27 | * Example: If there was an error with a request. You can use this flag to change the outcome in the reducer or turn off the loading spinner. 28 | */ 29 | readonly error?: boolean; 30 | /* 31 | * (optional) 32 | * The optional meta property MAY be any type of value. It is intended for any extra information that is not part of the payload. 33 | * 34 | * Example: Some sort of "id" that may not be included on the payload itself. 35 | */ 36 | readonly meta?: any; 37 | } 38 | -------------------------------------------------------------------------------- /src/app/store/figmaStyles/actions.ts: -------------------------------------------------------------------------------- 1 | // Types 2 | import {IFigmaStylesState, types} from './types'; 3 | 4 | export const figmaStylesActions = Object.freeze({ 5 | 6 | // Sync 7 | // setApiKey: (payload: IFigmaStylesState) => { 8 | // return { 9 | // type: types.SET_API_KEY, 10 | // payload 11 | // } 12 | // }, 13 | setData: (payload: IFigmaStylesState) => { 14 | return { 15 | type: types.FIGMA_STYLES_SET_DATA, 16 | payload 17 | } 18 | }, 19 | setTokensData: (payload: IFigmaStylesState) => { 20 | return { 21 | type: types.SET_TOKENS_DATA, 22 | payload 23 | } 24 | }, 25 | setSelectedColor: (payload: IFigmaStylesState) => { 26 | return { 27 | type: types.SET_SELECTED_COLOR, 28 | payload 29 | } 30 | }, 31 | setEditedData: (payload: IFigmaStylesState) => { 32 | return { 33 | type: types.SET_EDITED_DATA, 34 | payload 35 | } 36 | }, 37 | setThemesData: (payload: IFigmaStylesState) => { 38 | return { 39 | type: types.THEMES_LIST_SET_DATA, 40 | payload 41 | } 42 | }, 43 | 44 | startFetching: () => { 45 | return { 46 | type: types.THEMES_LIST_START_FETCHING 47 | } 48 | }, 49 | 50 | stopFetching: () => { 51 | return { 52 | type: types.THEMES_LIST_STOP_FETCHING, 53 | } 54 | }, 55 | 56 | setFetchingError: (error: any) => { 57 | return { 58 | type: types.THEMES_LIST_FETCHING_ERROR, 59 | error: true, 60 | payload: error, 61 | } 62 | }, 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /src/app/store/figmaStyles/reducer.ts: -------------------------------------------------------------------------------- 1 | // Types 2 | import { types, IFigmaStylesState } from './types'; 3 | import IAction from "../../models/IAction"; 4 | 5 | const initialState: IFigmaStylesState = { 6 | styles: [], 7 | themesList: [], 8 | tokens: [], 9 | selectedColor: null, 10 | editedData: null, 11 | error: null, 12 | }; 13 | 14 | export const figmaStylesReducer = (state = initialState, {type, payload}: IAction) => { 15 | switch (type) { 16 | 17 | case types.FIGMA_STYLES_SET_DATA: 18 | return { ...state, styles: payload }; 19 | 20 | case types.SET_TOKENS_DATA: 21 | return { ...state, tokens: payload }; 22 | 23 | case types.SET_SELECTED_COLOR: 24 | return { ...state, selectedColor: payload }; 25 | 26 | case types.SET_EDITED_DATA: 27 | return { ...state, editedData: payload }; 28 | 29 | case types.THEMES_LIST_SET_DATA: 30 | return { ...state, themesList: payload }; 31 | 32 | case types.THEMES_LIST_START_FETCHING: 33 | return { ...state, isFetching: true }; 34 | case types.THEMES_LIST_STOP_FETCHING: 35 | return { ...state, isFetching: false }; 36 | case types.THEMES_LIST_FETCHING_ERROR: 37 | return { ...state, error: payload }; 38 | 39 | default: 40 | return state; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/store/figmaStyles/types.ts: -------------------------------------------------------------------------------- 1 | // actions types 2 | 3 | export enum types { 4 | // Sync 5 | FIGMA_STYLES_SET_DATA = 'FIGMA_STYLES_SET_DATA', 6 | THEMES_LIST_SET_DATA = 'THEMES_LIST_SET_DATA', 7 | THEMES_LIST_START_FETCHING = 'THEMES_LIST_START_FETCHING', 8 | THEMES_LIST_STOP_FETCHING = 'THEMES_LIST_STOP_FETCHING', 9 | THEMES_LIST_FETCHING_ERROR = 'THEMES_LIST_FETCHING_ERROR', 10 | SET_TOKENS_DATA = 'SET_TOKENS_DATA', 11 | SET_PALETTE_DATA = 'SET_PALETTE_DATA', 12 | //SET_API_KEY = 'SET_API_KEY', 13 | 14 | SET_SELECTED_COLOR = 'SET_SELECTED_COLOR', 15 | SET_EDITED_DATA = 'SET_EDITED_DATA', 16 | 17 | // Async 18 | THEMES_LIST_FETCH = 'THEMES_LIST_FETCH', 19 | THEMES_LIST_POST_FETCH = 'THEMES_LIST_POST_FETCH', 20 | } 21 | 22 | // item 23 | export interface IStyleDataItem { 24 | blendMode: string 25 | color: { 26 | r: number, 27 | g: number, 28 | b: number 29 | } 30 | opacity: number, 31 | type: string, 32 | visible: boolean 33 | } 34 | 35 | export interface IFigmaStyle { 36 | description: string, 37 | id: string, 38 | key: string, 39 | name: string 40 | paints: IStyleDataItem[], 41 | remote: boolean, 42 | type: string, 43 | } 44 | 45 | 46 | export interface IThemeItem { 47 | id: number, 48 | value: string, 49 | } 50 | 51 | export interface IPaintStyle { 52 | themeName: string, 53 | data: { 54 | description: string, 55 | id: string, 56 | key: string, 57 | name: string 58 | paints: IStyleDataItem[], 59 | remote: boolean, 60 | type: string, 61 | } 62 | } 63 | 64 | 65 | 66 | export interface IPaletteItem { 67 | group: string, 68 | data: IPaintStyle[] 69 | } 70 | 71 | export interface ITokenDataItem { 72 | description: string, 73 | id: string, 74 | key: string, 75 | name: string 76 | paints: IStyleDataItem[], 77 | remote: boolean, 78 | type: string, 79 | } 80 | 81 | export interface ITokensItem { 82 | themeName: string, 83 | data: ITokenDataItem[] 84 | } 85 | 86 | export interface ISelectedColor { 87 | rgb: { 88 | r: number, 89 | g: number, 90 | b: number, 91 | a: number 92 | } 93 | } 94 | 95 | 96 | // state 97 | export interface IFigmaStylesState { 98 | readonly styles: IPaintStyle[] | null, 99 | readonly themesList: IThemeItem[] | null, 100 | readonly tokens: ITokensItem[] | null, 101 | readonly selectedColor: ISelectedColor | null, 102 | readonly editedData: IFigmaStyle[] | null, 103 | readonly error: any | null, 104 | //readonly apiKey: string, 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/app/store/figmaStyles/useFigmaStyles.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { figmaStylesActions } from "./actions"; 4 | import {IFigmaStyle, IFigmaStylesState, IPaintStyle} from "./types"; 5 | 6 | type storeState = { 7 | figmaStyles: IFigmaStylesState 8 | } 9 | 10 | export const useFigmaStyles = () => { 11 | const dispatch = useDispatch(); 12 | 13 | const { 14 | styles, 15 | themesList, 16 | tokens, 17 | selectedColor, 18 | editedData, 19 | } = useSelector((state: storeState) => state.figmaStyles); 20 | 21 | const setData = (data) => dispatch(figmaStylesActions.setData(data)); 22 | const setTokensData = (data) => dispatch(figmaStylesActions.setTokensData(data)); 23 | const setThemesList = (data) => dispatch(figmaStylesActions.setThemesData(data)); 24 | const setSelectedColor = (data) => dispatch(figmaStylesActions.setSelectedColor(data)); 25 | const setEditedData = (data) => dispatch(figmaStylesActions.setEditedData(data)); 26 | 27 | const setListToClientStorage = (data) => { 28 | parent.postMessage({ pluginMessage: { type: 'update_themes_list', data: data } }, '*'); 29 | }; 30 | 31 | // useEffect(() => { 32 | // if (!themesList.length) { 33 | // setTokensData([]); 34 | // } 35 | // },[themesList]); 36 | 37 | useEffect(() => { 38 | if (!!styles) { 39 | 40 | const allStyles = styles.reduce((arr:IFigmaStyle[], item:IPaintStyle) => { 41 | // @ts-ignore 42 | arr.push(...item.data); 43 | return arr; 44 | }, []); 45 | 46 | if (themesList.length > 1 || (themesList.length === 1 && themesList[0].value.replace(/\s/g, '') !== '')) { 47 | 48 | const themesDataArrNew = themesList.reduce((arr, current) => { 49 | const stylesData = allStyles.reduce((theme:IFigmaStyle[], item:IFigmaStyle) => { 50 | if (item.name.includes(current.value)) { 51 | theme.push(item) 52 | } 53 | return theme; 54 | }, []); 55 | 56 | arr.push({ 57 | themeName: current.value, 58 | data: stylesData 59 | }) 60 | 61 | return arr 62 | }, []); 63 | 64 | const themeGroupedColors = themesDataArrNew.reduce((arr, current) => { 65 | 66 | const helper = {}; 67 | const groupedColors = current.data.reduce((acc, item, index) => { 68 | 69 | const key = item.paints[0].color.r + '-' + item.paints[0].color.g + '-' + item.paints[0].color.b + '-' + item.paints[0].opacity; 70 | 71 | if (!helper[key]) { 72 | helper[key] = Object.assign({}, { 73 | group: `Group ${index}`, 74 | data: [item] 75 | }); 76 | acc.push(helper[key]); 77 | } else { 78 | helper[key].data = [...helper[key].data, item]; 79 | } 80 | 81 | return acc; 82 | }, []); 83 | 84 | arr.push({ 85 | ...current, 86 | data: groupedColors 87 | }); 88 | 89 | return arr 90 | 91 | }, []); 92 | 93 | setTokensData(themeGroupedColors); 94 | } else { 95 | const helper = {}; 96 | const groupedColors = allStyles.reduce((acc, item, index) => { 97 | const key = item.paints[0].color.r + '-' + item.paints[0].color.g + '-' + item.paints[0].color.b + '-' + item.paints[0].opacity; 98 | 99 | if (!helper[key]) { 100 | helper[key] = Object.assign({}, { 101 | group: `Group ${index}`, 102 | data: [item] 103 | }); 104 | acc.push(helper[key]); 105 | } else { 106 | helper[key].data = [...helper[key].data, item]; 107 | } 108 | 109 | return acc; 110 | }, []); 111 | 112 | setTokensData([{ 113 | themeName: "Color Styles", 114 | data: groupedColors 115 | }]); 116 | } 117 | } 118 | }, [styles, themesList]); 119 | 120 | 121 | 122 | return { 123 | styles, 124 | themesList, 125 | selectedColor, 126 | editedData, 127 | tokens, 128 | setData, 129 | setThemesList, 130 | setSelectedColor, 131 | setEditedData, 132 | setTokensData, 133 | setListToClientStorage 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/app/store/middleware.ts: -------------------------------------------------------------------------------- 1 | // Core 2 | import {AnyAction, compose, Dispatch, Middleware } from 'redux'; 3 | import { createLogger } from 'redux-logger'; 4 | import thunk from 'redux-thunk'; 5 | 6 | export const logger: Middleware<{}, any, Dispatch> = createLogger({ 7 | duration: true, 8 | collapsed: true, 9 | colors: { 10 | title: (action) => { 11 | return action.error ? 'firebrick' : 'deepskyblue'; 12 | }, 13 | prevState: () => '#1C5FAF', 14 | action: () => '#149945', 15 | nextState: () => '#A47104', 16 | error: () => '#ff0005', 17 | } 18 | }); 19 | 20 | declare global { 21 | interface Window { 22 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose; 23 | } 24 | } 25 | 26 | const developmentEnvironment = process.env.NODE_ENV === 'development'; 27 | const devtools = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ; 28 | const composeEnhancers = developmentEnvironment && devtools ? devtools : compose; 29 | 30 | const middleware = [thunk]; 31 | 32 | if (developmentEnvironment) { 33 | // @ts-ignore 34 | middleware.push(logger); 35 | } 36 | 37 | export { composeEnhancers, middleware }; 38 | -------------------------------------------------------------------------------- /src/app/store/rootReducer.ts: -------------------------------------------------------------------------------- 1 | // Core 2 | import { combineReducers } from 'redux'; 3 | 4 | // Reducers 5 | import {figmaStylesReducer as figmaStyles} from './figmaStyles/reducer'; 6 | import {uiReducer as ui} from './ui/reducer'; 7 | 8 | export const rootReducer = combineReducers({ 9 | figmaStyles, 10 | ui, 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/store/store.ts: -------------------------------------------------------------------------------- 1 | // Core 2 | import { createStore, applyMiddleware } from 'redux'; 3 | 4 | // Instruments 5 | import { rootReducer } from './rootReducer'; 6 | import { 7 | composeEnhancers, 8 | middleware, 9 | } from './middleware'; 10 | 11 | export const store = createStore( 12 | rootReducer, 13 | composeEnhancers(applyMiddleware(...middleware)) 14 | ); 15 | -------------------------------------------------------------------------------- /src/app/store/ui/actions.ts: -------------------------------------------------------------------------------- 1 | // Types 2 | import {types} from './types'; 3 | 4 | export const uiActions = Object.freeze({ 5 | 6 | // Sync 7 | toggleModalEditColor: () => { 8 | return { 9 | type: types.UI_TOGGLE_MODAL_EDIT_COLOR, 10 | } 11 | }, 12 | toggleModalCreateMode: () => { 13 | return { 14 | type: types.UI_TOGGLE_MODAL_CREATE_MODE, 15 | } 16 | }, 17 | 18 | toggleLoader: () => { 19 | return { 20 | type: types.UI_TOGGLE_LOADER, 21 | } 22 | }, 23 | 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/store/ui/reducer.ts: -------------------------------------------------------------------------------- 1 | // Types 2 | import { types, IUI } from './types'; 3 | import IAction from "../../models/IAction"; 4 | 5 | const initialState: IUI = { 6 | modalEditColor: false, 7 | modalCreateMode: false, 8 | loader: false, 9 | }; 10 | 11 | export const uiReducer = (state = initialState, {type}: IAction) => { 12 | switch (type) { 13 | 14 | case types.UI_TOGGLE_MODAL_EDIT_COLOR: 15 | return { ...state, modalEditColor: !state.modalEditColor }; 16 | 17 | case types.UI_TOGGLE_MODAL_CREATE_MODE: 18 | return { ...state, modalCreateMode: !state.modalCreateMode }; 19 | 20 | case types.UI_TOGGLE_LOADER: 21 | return { ...state, loader: !state.loader }; 22 | 23 | default: 24 | return state; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/app/store/ui/types.ts: -------------------------------------------------------------------------------- 1 | // actions types 2 | export enum types { 3 | // Sync 4 | UI_TOGGLE_MODAL_CREATE_MODE = 'UI_TOGGLE_MODAL_CREATE_MODE', 5 | UI_TOGGLE_MODAL_EDIT_COLOR = 'UI_TOGGLE_MODAL_EDIT_COLOR', 6 | UI_TOGGLE_LOADER = 'UI_TOGGLE_LOADER', 7 | } 8 | 9 | // state 10 | export interface IUI { 11 | readonly modalEditColor: boolean, 12 | readonly modalCreateMode: boolean, 13 | readonly loader: boolean, 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/store/ui/useUI.ts: -------------------------------------------------------------------------------- 1 | import {useDispatch, useSelector} from "react-redux"; 2 | import {IUI} from "./types"; 3 | import {uiActions} from "./actions"; 4 | 5 | 6 | type storeState = { 7 | ui: IUI 8 | } 9 | 10 | export const useUI = () => { 11 | const dispatch = useDispatch(); 12 | 13 | const { modalEditColor, modalCreateMode, loader } = useSelector((state: storeState) => state.ui); 14 | 15 | const toggleModalEditColor = () => dispatch(uiActions.toggleModalEditColor()); 16 | const toggleModalCreateMode = () => dispatch(uiActions.toggleModalCreateMode()); 17 | const toggleLoader = () => dispatch(uiActions.toggleLoader()); 18 | 19 | return { 20 | modalEditColor, 21 | modalCreateMode, 22 | loader, 23 | toggleModalEditColor, 24 | toggleModalCreateMode, 25 | toggleLoader, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/styles/constructor.scss: -------------------------------------------------------------------------------- 1 | // SETTINGS 2 | @import 'settings/settings'; 3 | 4 | // TOOLS 5 | //@import 'tools/media'; 6 | //@import 'tools/icomoon'; 7 | //@import 'tools/justify'; 8 | //@import 'tools/vertical'; 9 | 10 | // GENERIC 11 | @import 'generic/normalize'; 12 | @import 'generic/reset'; 13 | @import 'generic/fonts'; 14 | @import '../assets/fonts/icomoon/style.scss'; 15 | 16 | // ELEMENTS 17 | @import "elements/page"; 18 | 19 | // COMPONENTS 20 | @import "elements/modal"; 21 | @import "elements/chrome-picker"; 22 | -------------------------------------------------------------------------------- /src/app/styles/elements/_chrome-picker.scss: -------------------------------------------------------------------------------- 1 | .chrome-picker { 2 | width: 100%!important; 3 | border-radius: 0!important; 4 | box-shadow: none!important; 5 | box-sizing: initial!important; 6 | font-family: inherit!important; 7 | 8 | > div { 9 | &:nth-child(1) { 10 | border-radius: 0!important; 11 | } 12 | &:nth-child(2) { 13 | padding: 16px 16px 0!important; 14 | } 15 | 16 | > div { 17 | &:nth-child(1) { 18 | > div { 19 | &:nth-child(2) { 20 | > div { 21 | 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > div > div:nth-child(2) { 30 | border-radius: 7px; 31 | } 32 | 33 | .hue-horizontal { 34 | border-radius: 7px; 35 | } 36 | 37 | input { 38 | &:focus, &:focus-visible { 39 | border-color: $COLOR-FIGMA-BLUE!important; 40 | box-shadow: 0 0 0 2px $COLOR-FIGMA-BLUE!important; 41 | outline: none; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/styles/elements/_modal.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | transition: opacity 0.15s linear; 3 | } 4 | 5 | @media (prefers-reduced-motion: reduce) { 6 | .fade { 7 | transition: none; 8 | } 9 | } 10 | 11 | .fade:not(.show) { 12 | opacity: 0; 13 | } 14 | 15 | 16 | .modal-open { 17 | overflow: hidden; 18 | } 19 | 20 | .modal-open .modal { 21 | overflow-x: hidden; 22 | overflow-y: auto; 23 | } 24 | 25 | .modal { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | z-index: 1050; 30 | display: none; 31 | width: 100%; 32 | height: 100%; 33 | overflow: hidden; 34 | outline: 0; 35 | } 36 | 37 | .modal-dialog { 38 | position: relative; 39 | width: auto; 40 | margin: 0.5rem; 41 | pointer-events: none; 42 | } 43 | 44 | .modal.fade .modal-dialog { 45 | //transition: -webkit-transform 0.3s ease-out; 46 | //transition: transform 0.3s ease-out; 47 | //transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; 48 | //-webkit-transform: translate(0, -50px); 49 | //transform: translate(0, -50px); 50 | } 51 | 52 | @media (prefers-reduced-motion: reduce) { 53 | .modal.fade .modal-dialog { 54 | transition: none; 55 | } 56 | } 57 | 58 | .modal.show .modal-dialog { 59 | -webkit-transform: none; 60 | transform: none; 61 | } 62 | 63 | .modal.modal-static .modal-dialog { 64 | //-webkit-transform: scale(1.02); 65 | //transform: scale(1.02); 66 | } 67 | 68 | .modal-dialog-scrollable { 69 | display: -ms-flexbox; 70 | display: flex; 71 | max-height: calc(100% - 1rem); 72 | } 73 | 74 | .modal-dialog-scrollable .modal-content { 75 | max-height: calc(100vh - 1rem); 76 | overflow: hidden; 77 | } 78 | 79 | .modal-dialog-scrollable .modal-header, 80 | .modal-dialog-scrollable .modal-footer { 81 | -ms-flex-negative: 0; 82 | flex-shrink: 0; 83 | } 84 | 85 | .modal-dialog-scrollable .modal-body { 86 | overflow-y: auto; 87 | } 88 | 89 | .modal-dialog-centered { 90 | display: -ms-flexbox; 91 | display: flex; 92 | -ms-flex-align: center; 93 | align-items: center; 94 | min-height: calc(100% - 1rem); 95 | } 96 | 97 | .modal-dialog-centered::before { 98 | display: block; 99 | height: calc(100vh - 1rem); 100 | content: ""; 101 | } 102 | 103 | .modal-dialog-centered.modal-dialog-scrollable { 104 | -ms-flex-direction: column; 105 | flex-direction: column; 106 | -ms-flex-pack: center; 107 | justify-content: center; 108 | height: 100%; 109 | } 110 | 111 | .modal-dialog-centered.modal-dialog-scrollable .modal-content { 112 | max-height: none; 113 | } 114 | 115 | .modal-dialog-centered.modal-dialog-scrollable::before { 116 | content: none; 117 | } 118 | 119 | .modal-content { 120 | position: relative; 121 | display: -ms-flexbox; 122 | display: flex; 123 | -ms-flex-direction: column; 124 | flex-direction: column; 125 | width: 100%; 126 | pointer-events: auto; 127 | background-color: #fff; 128 | background-clip: padding-box; 129 | border: none; 130 | border-radius: 0.3rem; 131 | outline: 0; 132 | } 133 | 134 | .modal-backdrop { 135 | position: fixed; 136 | top: 0; 137 | left: 0; 138 | z-index: 1040; 139 | width: 100vw; 140 | height: 100vh; 141 | background-color: #000; 142 | &.fade { 143 | opacity: 0; 144 | } 145 | &.show { 146 | opacity: 0.3; 147 | } 148 | } 149 | 150 | .modal-header { 151 | display: -ms-flexbox; 152 | display: flex; 153 | -ms-flex-align: start; 154 | align-items: flex-start; 155 | -ms-flex-pack: justify; 156 | justify-content: space-between; 157 | padding: 1rem 1rem; 158 | border-bottom: 1px solid #dee2e6; 159 | border-top-left-radius: calc(0.3rem - 1px); 160 | border-top-right-radius: calc(0.3rem - 1px); 161 | } 162 | 163 | .modal-title { 164 | margin-bottom: 0; 165 | line-height: 1.5; 166 | } 167 | 168 | .modal-body { 169 | position: relative; 170 | -ms-flex: 1 1 auto; 171 | flex: 1 1 auto; 172 | padding: 1rem; 173 | } 174 | 175 | .modal-footer { 176 | display: -ms-flexbox; 177 | display: flex; 178 | -ms-flex-wrap: wrap; 179 | flex-wrap: wrap; 180 | -ms-flex-align: center; 181 | align-items: center; 182 | -ms-flex-pack: end; 183 | justify-content: flex-end; 184 | padding: 0.75rem; 185 | border-top: 1px solid #dee2e6; 186 | border-bottom-right-radius: calc(0.3rem - 1px); 187 | border-bottom-left-radius: calc(0.3rem - 1px); 188 | } 189 | 190 | .modal-footer > * { 191 | margin: 0.25rem; 192 | } 193 | 194 | .modal-scrollbar-measure { 195 | position: absolute; 196 | top: -9999px; 197 | width: 50px; 198 | height: 50px; 199 | overflow: scroll; 200 | } 201 | 202 | 203 | .modal { 204 | .modal-dialog { 205 | display: flex; 206 | align-items: center; 207 | justify-content: center; 208 | vertical-align: middle; 209 | position: relative; 210 | width: 100%; 211 | max-width: 288px; 212 | margin: 0 auto; 213 | } 214 | .modal-content { 215 | //min-height: 490px; 216 | min-height: auto; 217 | color: #000; 218 | padding: 0 16px 16px; 219 | background-color: #fff; 220 | border: 0.5px solid rgba(0, 0, 0, 0.2); 221 | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.15); 222 | border-radius: 2px; 223 | } 224 | .modal-header, 225 | .modal-body { 226 | padding: 0; 227 | border: none; 228 | } 229 | .modal-header { 230 | padding: 12px 16px!important; 231 | margin: 0 -16px; 232 | box-sizing: border-box; 233 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 234 | } 235 | .modal-close { 236 | position: absolute; 237 | top: 4px; 238 | right: 4px; 239 | z-index: 1; 240 | cursor: pointer; 241 | width: 32px; 242 | height: 32px; 243 | font-size: 32px; 244 | background: transparent; 245 | border-radius: 3px; 246 | color: rgba($COLOR-BLACK,0.8); 247 | transition: all 0.15s ease 0s; 248 | &:hover { 249 | background: rgba($COLOR-BLACK,0.06); 250 | svg { 251 | fill: rgba($COLOR-BLACK,1); 252 | } 253 | } 254 | svg { 255 | fill: rgba($COLOR-BLACK,0.8); 256 | } 257 | } 258 | .modal-title { 259 | width: 100%; 260 | margin-bottom: 0; 261 | text-align: left; 262 | padding: 0 32px 0 0; 263 | 264 | font-style: normal; 265 | font-weight: 600; 266 | font-size: 11px; 267 | line-height: 16px; 268 | letter-spacing: 0.005em; 269 | color: rgba($COLOR-BLACK, 0.8); 270 | 271 | } 272 | 273 | .auto-height {} 274 | .align-centered { 275 | .modal-body { 276 | display: flex; 277 | align-items: center; 278 | justify-content: center; 279 | } 280 | .modal-text { 281 | width: 100%; 282 | } 283 | } 284 | } 285 | 286 | 287 | body { 288 | &.modal-open { 289 | #root { 290 | //filter: blur(2px); 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/app/styles/elements/_page.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | height: 100%; 7 | } 8 | 9 | body { 10 | display: flex; 11 | flex-direction: column; 12 | line-height: 1.2; 13 | min-height: 100%; 14 | width: auto; 15 | color: rgba($COLOR-BLACK,0.8); 16 | font-family: $primary_font; 17 | font-weight: normal; 18 | font-style: normal; 19 | font-size: 16px; 20 | direction: ltr; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | overflow-x: hidden; 24 | background: #red; 25 | transition: opacity .2s ease 0s; 26 | &.is-loading { 27 | opacity: 0; 28 | visibility: hidden; 29 | &.is-visible { 30 | opacity: 1; 31 | visibility: visible; 32 | } 33 | } 34 | } 35 | 36 | #react-page, .page { 37 | display: flex; 38 | flex: 1 1 auto; 39 | width: 100%; 40 | flex-direction: column; 41 | } 42 | 43 | 44 | main, 45 | .js-wrapper { 46 | flex: 1 0 auto; 47 | /* @include media('screen', '<1025px', 'portrait') { 48 | flex: 1 0 auto; 49 | display: flex; 50 | flex-direction: column; 51 | }*/ 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/app/styles/generic/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap'); 2 | 3 | -------------------------------------------------------------------------------- /src/app/styles/generic/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Correct the font size and margin on `h1` elements within `section` and 29 | * `article` contexts in Chrome, Firefox, and Safari. 30 | */ 31 | 32 | h1 { 33 | font-size: 2em; 34 | margin: 0.67em 0; 35 | } 36 | 37 | /* Grouping content 38 | ========================================================================== */ 39 | 40 | /** 41 | * 1. Add the correct box sizing in Firefox. 42 | * 2. Show the overflow in Edge and IE. 43 | */ 44 | 45 | hr { 46 | box-sizing: content-box; /* 1 */ 47 | height: 0; /* 1 */ 48 | overflow: visible; /* 2 */ 49 | } 50 | 51 | /** 52 | * 1. Correct the inheritance and scaling of font size in all browsers. 53 | * 2. Correct the odd `em` font sizing in all browsers. 54 | */ 55 | 56 | pre { 57 | font-family: monospace, monospace; /* 1 */ 58 | font-size: 1em; /* 2 */ 59 | } 60 | 61 | /* Text-level semantics 62 | ========================================================================== */ 63 | 64 | /** 65 | * Remove the gray background on active links in IE 10. 66 | */ 67 | 68 | a { 69 | background-color: transparent; 70 | } 71 | 72 | /** 73 | * 1. Remove the bottom border in Chrome 57- 74 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 75 | */ 76 | 77 | abbr[title] { 78 | border-bottom: none; /* 1 */ 79 | text-decoration: underline; /* 2 */ 80 | text-decoration: underline dotted; /* 2 */ 81 | } 82 | 83 | /** 84 | * Add the correct font weight in Chrome, Edge, and Safari. 85 | */ 86 | 87 | b, 88 | strong { 89 | font-weight: bolder; 90 | } 91 | 92 | /** 93 | * 1. Correct the inheritance and scaling of font size in all browsers. 94 | * 2. Correct the odd `em` font sizing in all browsers. 95 | */ 96 | 97 | code, 98 | kbd, 99 | samp { 100 | font-family: monospace, monospace; /* 1 */ 101 | font-size: 1em; /* 2 */ 102 | } 103 | 104 | /** 105 | * Add the correct font size in all browsers. 106 | */ 107 | 108 | small { 109 | font-size: 80%; 110 | } 111 | 112 | /** 113 | * Prevent `sub` and `sup` elements from affecting the line height in 114 | * all browsers. 115 | */ 116 | 117 | sub, 118 | sup { 119 | font-size: 75%; 120 | line-height: 0; 121 | position: relative; 122 | vertical-align: baseline; 123 | } 124 | 125 | sub { 126 | bottom: -0.25em; 127 | } 128 | 129 | sup { 130 | top: -0.5em; 131 | } 132 | 133 | /* Embedded content 134 | ========================================================================== */ 135 | 136 | /** 137 | * Remove the border on images inside links in IE 10. 138 | */ 139 | 140 | img { 141 | border-style: none; 142 | } 143 | 144 | /* Forms 145 | ========================================================================== */ 146 | 147 | /** 148 | * 1. Change the font styles in all browsers. 149 | * 2. Remove the margin in Firefox and Safari. 150 | */ 151 | 152 | button, 153 | input, 154 | optgroup, 155 | select, 156 | textarea { 157 | font-family: inherit; /* 1 */ 158 | font-size: 100%; /* 1 */ 159 | line-height: 1.15; /* 1 */ 160 | margin: 0; /* 2 */ 161 | } 162 | 163 | /** 164 | * Show the overflow in IE. 165 | * 1. Show the overflow in Edge. 166 | */ 167 | 168 | button, 169 | input { /* 1 */ 170 | overflow: visible; 171 | } 172 | 173 | /** 174 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 175 | * 1. Remove the inheritance of text transform in Firefox. 176 | */ 177 | 178 | button, 179 | select { /* 1 */ 180 | text-transform: none; 181 | } 182 | 183 | /** 184 | * Correct the inability to style clickable types in iOS and Safari. 185 | */ 186 | 187 | button, 188 | [type="button"], 189 | [type="reset"], 190 | [type="submit"] { 191 | -webkit-appearance: button; 192 | } 193 | 194 | /** 195 | * Remove the inner border and padding in Firefox. 196 | */ 197 | 198 | button::-moz-focus-inner, 199 | [type="button"]::-moz-focus-inner, 200 | [type="reset"]::-moz-focus-inner, 201 | [type="submit"]::-moz-focus-inner { 202 | border-style: none; 203 | padding: 0; 204 | } 205 | 206 | /** 207 | * Restore the focus styles unset by the previous rule. 208 | */ 209 | 210 | button:-moz-focusring, 211 | [type="button"]:-moz-focusring, 212 | [type="reset"]:-moz-focusring, 213 | [type="submit"]:-moz-focusring { 214 | outline: 1px dotted ButtonText; 215 | } 216 | 217 | /** 218 | * Correct the padding in Firefox. 219 | */ 220 | 221 | fieldset { 222 | padding: 0.35em 0.75em 0.625em; 223 | } 224 | 225 | /** 226 | * 1. Correct the text wrapping in Edge and IE. 227 | * 2. Correct the color inheritance from `fieldset` elements in IE. 228 | * 3. Remove the padding so developers are not caught out when they zero out 229 | * `fieldset` elements in all browsers. 230 | */ 231 | 232 | legend { 233 | box-sizing: border-box; /* 1 */ 234 | color: inherit; /* 2 */ 235 | display: table; /* 1 */ 236 | max-width: 100%; /* 1 */ 237 | padding: 0; /* 3 */ 238 | white-space: normal; /* 1 */ 239 | } 240 | 241 | /** 242 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 243 | */ 244 | 245 | progress { 246 | vertical-align: baseline; 247 | } 248 | 249 | /** 250 | * Remove the default vertical scrollbar in IE 10+. 251 | */ 252 | 253 | textarea { 254 | overflow: auto; 255 | } 256 | 257 | /** 258 | * 1. Add the correct box sizing in IE 10. 259 | * 2. Remove the padding in IE 10. 260 | */ 261 | 262 | [type="checkbox"], 263 | [type="radio"] { 264 | box-sizing: border-box; /* 1 */ 265 | padding: 0; /* 2 */ 266 | } 267 | 268 | /** 269 | * Correct the cursor style of increment and decrement buttons in Chrome. 270 | */ 271 | 272 | [type="number"]::-webkit-inner-spin-button, 273 | [type="number"]::-webkit-outer-spin-button { 274 | height: auto; 275 | } 276 | 277 | /** 278 | * 1. Correct the odd appearance in Chrome and Safari. 279 | * 2. Correct the outline style in Safari. 280 | */ 281 | 282 | [type="search"] { 283 | -webkit-appearance: textfield; /* 1 */ 284 | outline-offset: -2px; /* 2 */ 285 | } 286 | 287 | /** 288 | * Remove the inner padding in Chrome and Safari on macOS. 289 | */ 290 | 291 | [type="search"]::-webkit-search-decoration { 292 | -webkit-appearance: none; 293 | } 294 | 295 | /** 296 | * 1. Correct the inability to style clickable types in iOS and Safari. 297 | * 2. Change font properties to `inherit` in Safari. 298 | */ 299 | 300 | ::-webkit-file-upload-button { 301 | -webkit-appearance: button; /* 1 */ 302 | font: inherit; /* 2 */ 303 | } 304 | 305 | /* Interactive 306 | ========================================================================== */ 307 | 308 | /* 309 | * Add the correct display in Edge, IE 10+, and Firefox. 310 | */ 311 | 312 | details { 313 | display: block; 314 | } 315 | 316 | /* 317 | * Add the correct display in all browsers. 318 | */ 319 | 320 | summary { 321 | display: list-item; 322 | } 323 | 324 | /* Misc 325 | ========================================================================== */ 326 | 327 | /** 328 | * Add the correct display in IE 10+. 329 | */ 330 | 331 | template { 332 | display: none; 333 | } 334 | 335 | /** 336 | * Add the correct display in IE 10. 337 | */ 338 | 339 | [hidden] { 340 | display: none; 341 | } 342 | -------------------------------------------------------------------------------- /src/app/styles/generic/_reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | abbr, address, cite, code, 4 | del, dfn, em, img, ins, kbd, q, samp, 5 | small, strong, sub, sup, var, 6 | b, i, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, figcaption, figure, 11 | footer, header, hgroup, menu, nav, section, summary, 12 | time, mark, audio, video { 13 | margin: 0; 14 | padding: 0; 15 | border: 0; 16 | outline: 0; 17 | background: transparent; 18 | } 19 | 20 | body { 21 | line-height:1; 22 | } 23 | 24 | ol, ul { 25 | list-style: none; 26 | } 27 | 28 | p { 29 | margin: 0; 30 | } 31 | 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { 38 | content: ''; 39 | content: none; 40 | } 41 | 42 | table { 43 | border-collapse: collapse; 44 | border-spacing: 0; 45 | } -------------------------------------------------------------------------------- /src/app/styles/settings/_settings.scss: -------------------------------------------------------------------------------- 1 | // colors 2 | $COLOR-FIGMA-BLUE: #18a0fb; 3 | $COLOR-FIGMA-RED: #FF3333; 4 | $COLOR-WHITE: #ffffff; 5 | $COLOR-BLACK: #000000; 6 | $COLOR-GRAY: #E5E5E5; 7 | $COLOR-GRAY-400: #C4C4C4; 8 | $COLOR-LIGHT-GREY: $COLOR-GRAY; 9 | $COLOR-TEXT-PRIMARY: #212121; 10 | 11 | $FIGMA-UI-WIDTH: 342px; 12 | $FIGMA-UI-HEIGHT: 444px; 13 | 14 | // fonts 15 | $primary_font: 'Inter', medium-content-sans-serif-font, -apple-system, sans-serif; 16 | $secondary_font: 'Inter', medium-content-sans-serif-font, -apple-system, sans-serif; 17 | //$icomoon-font-path: "../fonts/icomoon/fonts" !default; 18 | -------------------------------------------------------------------------------- /src/plugin/controller.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | if(typeof String.prototype.replaceAll === "undefined") {String.prototype.replaceAll = function(match, replace) { return this.replace(new RegExp(match, 'g'), () => replace);}} 3 | 4 | figma.showUI(__html__, {width: 342, height: 444}); 5 | 6 | const getFigmaLocalPaintStyles = () => { 7 | // @ts-ignore 8 | let styles = figma.getLocalPaintStyles(); 9 | // @ts-ignore 10 | let paintsStyles = figma.getLocalPaintStyles().reduce((acc, current) => { 11 | acc.push({ 12 | description: current.description, 13 | id: current.id, 14 | key: current.key, 15 | name: current.name, 16 | paints: current.paints, 17 | remote: current.remote, 18 | type: current.type, 19 | }); 20 | 21 | return acc; 22 | }, []); 23 | 24 | return paintsStyles 25 | } 26 | 27 | 28 | const getAllStylesData = () => { 29 | const stylePaints = getFigmaLocalPaintStyles(); 30 | 31 | /* array with all themes */ 32 | const helperThemes = {}; 33 | const themesKeysArr = stylePaints.reduce((arr, current) => { 34 | const key = current.name.replaceAll(' ', '/').split('/')[0]; 35 | 36 | if (!helperThemes.hasOwnProperty(key)) { 37 | helperThemes[key] = Object.assign({}, { 38 | [key]: current.name.replaceAll(' ', '/').split('/')[0], 39 | }); 40 | arr.push(current.name.replaceAll(' ', '/').split('/')[0]); 41 | } 42 | return arr; 43 | }, []); 44 | /* END array with all themes */ 45 | 46 | const themesDataArr = themesKeysArr.reduce((arr:any[], current:string) => { 47 | const theme = stylePaints.reduce((theme, item) => { 48 | if (item.paints[0].type === "SOLID") { 49 | if (item.name.replaceAll(' ', '/').split('/')[0] === current) { 50 | theme.push(item) 51 | } 52 | } 53 | 54 | return theme 55 | }, []); 56 | 57 | if (theme.length > 0) { 58 | arr.push({ 59 | themeName: theme[0].name.replaceAll(' ', '/').split('/')[0], 60 | data: theme 61 | }); 62 | } 63 | 64 | return arr 65 | }, []); 66 | 67 | return themesDataArr; 68 | } 69 | 70 | let paintStylesData = getAllStylesData(); 71 | 72 | // send api key to plugin 73 | figma.clientStorage.getAsync(`themesList_${figma.currentPage.id}`).then( 74 | data => { 75 | figma.ui.postMessage({ 76 | type: 'themes_list', 77 | message: !!data ? data : "[]", 78 | }); 79 | }, 80 | () => { 81 | figma.ui.postMessage({ 82 | type: 'themes_list', 83 | message: [], 84 | }); 85 | } 86 | ); 87 | 88 | figma.ui.postMessage({ 89 | type: 'styles', 90 | message: [...paintStylesData], 91 | }); 92 | 93 | 94 | figma.on("selectionchange", () => { console.log("selectionchange") }) 95 | figma.on("currentpagechange", () => { console.log("currentpagechange") }) 96 | 97 | 98 | figma.ui.onmessage = (msg) => { 99 | 100 | if (msg.type === 'update_themes_list') { 101 | figma.clientStorage.setAsync(`themesList_${figma.currentPage.id}`, JSON.stringify(msg.data)).then( 102 | () => {}, 103 | error => { console.log('error: ', error) } 104 | ) 105 | } 106 | 107 | if (msg.type === 'update_paint_styles') { 108 | const { color, styles } = msg.data; 109 | 110 | styles.map((item) => { 111 | // @ts-ignore 112 | figma.getStyleById(item.id).paints = [ 113 | { 114 | ...item.paints[0], 115 | opacity: color.a, 116 | color: { 117 | r: color.r, 118 | g: color.g, 119 | b: color.b, 120 | } 121 | } 122 | ]; 123 | }); 124 | 125 | figma.ui.postMessage({ 126 | type: 'styles', 127 | message: [...getAllStylesData()], 128 | }); 129 | 130 | figma.notify(`Updated colors`); 131 | } 132 | 133 | if (msg.type === 'create_new_mode') { 134 | const { name, oldName, data } = msg.data; 135 | 136 | const baseColorsArr = data.reduce((arr, item) => { 137 | arr.push(...item.data) 138 | return arr 139 | }, []); 140 | 141 | baseColorsArr.map((item) => { 142 | const newName = item.name.replaceAll(oldName, name); 143 | const style = figma.createPaintStyle() 144 | style.name = newName; 145 | style.paints = item.paints; 146 | }); 147 | 148 | figma.notify(`Created new mode "${name}"`); 149 | 150 | figma.ui.postMessage({ 151 | type: 'styles', 152 | message: [...getAllStylesData()], 153 | }); 154 | 155 | } 156 | 157 | //figma.closePlugin(); 158 | }; 159 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "outDir": "dist", 5 | "jsx": "react", 6 | "allowJs": true, 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "experimentalDecorators": true, 10 | "removeComments": true, 11 | "noImplicitAny": false, 12 | "moduleResolution": "node", 13 | "allowSyntheticDefaultImports": true, 14 | "typeRoots": ["./node_modules/@types", "./node_modules/@figma", "./typings"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typings/SCSSTypes.ts: -------------------------------------------------------------------------------- 1 | /* CSS modules */ 2 | declare module '*.scss'; 3 | declare module '*.module.css'; 4 | declare module '*.module.scss'; 5 | declare module '*.module.sass'; 6 | -------------------------------------------------------------------------------- /typings/SVGTypes.ts: -------------------------------------------------------------------------------- 1 | /* SVG */ 2 | declare module '*.svg'; 3 | --------------------------------------------------------------------------------