├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── README.md ├── assets ├── color-check.gif ├── cover.png ├── grids 3.gif ├── grids-overview.png ├── grids1.gif ├── grids2.gif ├── spacing.gif └── theme-swap.gif ├── dist ├── code.js ├── ui.html └── ui.js ├── figma.d.ts ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── code.ts ├── helper │ ├── callback.ts │ ├── color-check.ts │ ├── force-auto-layout.ts │ ├── layout-grid.ts │ ├── spacing-check.ts │ ├── spacing-default.ts │ ├── spacing-slider.ts │ └── theme-swap.ts ├── react │ ├── layoutGridControllers │ │ ├── layoutGridForm.tsx │ │ └── sliderHorizontal.tsx │ ├── sliderHorizontal.tsx │ ├── sliderItemSpacing.tsx │ └── sliderVertical.tsx ├── theme-styles │ ├── colors.js │ ├── effects.js │ └── layoutGrids.js ├── ui.css ├── ui.html └── ui.tsx ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=75c62a2b26062ff1aab8844d7e4c5590599c9acc 2 | @TheSufferfest:registry=https://npm.pkg.github.com -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "[javascript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DESIGNR Figma Plugin 2 | 3 | ![image](./assets/cover.png) 4 | 5 | This is a figma plugin that helps us achieve consistent color & spacing. 6 | 7 | # 🎁 Installation 8 | 9 | 1. Go to [latest release](https://github.com/aboutjax/designr-figma-plugin/releases/latest) and download the source code. 10 | 2. Unzip the `designr-figma-plugin.zip` file and place the unzipped folder in a safe directory anywhere on your machine. 11 | 3. Right click anywhere in Figma canvas → Plugins → Manage Plugins 12 | 4. At the right side of the screen, tap "+ Create your own plugin". 13 | 5. Tap "Link existing plugin" and choose the `manifest.json` file inside the folder you unzipped. 14 | 6. Open the plugin by these two methods: 15 | - right clicking anywhere on canvas → Plugins → Development → Designr Helper. 16 | - `cmd + /` to open Figma menu and search "helper". 17 | 18 | # 🚀 Features 19 | 20 | ## Check Colors 21 | 22 | ![color check](./assets/color-check.gif) 23 | 24 | **What?** Checks all the colours in the selected frame that aren't using the intended functional colors defined from our design library. 25 | 26 | **Why?** Because it's okay to design quickly with the color picker (we're all guity of this, don't lie). But this tool should help us clean up the mess afterwards. 27 | 28 | ## Convert to Dark / Light Theme 29 | 30 | ![theme swap](./assets/theme-swap.gif) 31 | 32 | **What?** One click function to convert the entire frame to Dark / Light theme function colours 33 | 34 | **Why?** Because doing it one by one under the native Figma "Selection Color" is laborious. 35 | 36 | ## Auto Layout Spacings 37 | 38 | ![auto layout spacing](./assets/spacing.gif) 39 | 40 | Adjust auto layout spacings with spacing defined from our tokens. Visible when you have a Frame with Auto Layout enabled selected. 41 | 42 | **What?** A shortcut for applying spacing values in the Auto Layout properties. 43 | 44 | **Why?** Gets us thinking in sizes (ie. S, M, L ) instead of pixel values (ie. 16px, 24px, 32px) 45 | 46 | **Note** There’s a weird Figma glitch: After you change a slider value, your cursor needs to go back to Figma canvas to update the frame to the most up to date value. Not sure why, it’s a weird Figma plugin issue I couldn’t fix. 47 | 48 | ## Layout Grids shortcut 49 | 50 | ![Layout Grids shortcut](./assets/grids-overview.png) 51 | **What?** A shortcut for applying layout grids based on our spacing values. Inspired by Google Chrome's box model visualisation, this should look familar. 52 | 53 | **Why?** Figma's layout grids doesn't let us apply multiple styles at a time. This is a workaround. 54 | 55 | # 💬 To Contribute 56 | 57 | If you want to contribute, run this plugin locally on your machine and watch for code changes. 58 | 59 | 1. `npm install` to install all the dependencies 60 | 2. `npx webpack` to watch the `./src` folder and compile any code changes. 61 | 3. To see the changes after each compile, bring the Figma desktop app to the foreground, and use `command + option + p` shortcut which tells Figma to re-run the last plugin. 62 | -------------------------------------------------------------------------------- /assets/color-check.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/color-check.gif -------------------------------------------------------------------------------- /assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/cover.png -------------------------------------------------------------------------------- /assets/grids 3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/grids 3.gif -------------------------------------------------------------------------------- /assets/grids-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/grids-overview.png -------------------------------------------------------------------------------- /assets/grids1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/grids1.gif -------------------------------------------------------------------------------- /assets/grids2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/grids2.gif -------------------------------------------------------------------------------- /assets/spacing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/spacing.gif -------------------------------------------------------------------------------- /assets/theme-swap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aboutjax/designr-figma-plugin/9ddfc3b1a710853de63938d061dec5d3e6db94ae/assets/theme-swap.gif -------------------------------------------------------------------------------- /figma.d.ts: -------------------------------------------------------------------------------- 1 | // Figma Plugin API version 1, update 14 2 | 3 | declare global { 4 | // Global variable with Figma's plugin API. 5 | const figma: PluginAPI; 6 | const __html__: string; 7 | 8 | interface PluginAPI { 9 | readonly apiVersion: "1.0.0"; 10 | readonly command: string; 11 | readonly viewport: ViewportAPI; 12 | closePlugin(message?: string): void; 13 | 14 | notify(message: string, options?: NotificationOptions): NotificationHandler; 15 | 16 | showUI(html: string, options?: ShowUIOptions): void; 17 | readonly ui: UIAPI; 18 | 19 | readonly clientStorage: ClientStorageAPI; 20 | 21 | getNodeById(id: string): BaseNode | null; 22 | getStyleById(id: string): BaseStyle | null; 23 | 24 | readonly root: DocumentNode; 25 | currentPage: PageNode; 26 | 27 | on( 28 | type: "selectionchange" | "currentpagechange" | "close", 29 | callback: () => void 30 | ): void; 31 | once( 32 | type: "selectionchange" | "currentpagechange" | "close", 33 | callback: () => void 34 | ): void; 35 | off( 36 | type: "selectionchange" | "currentpagechange" | "close", 37 | callback: () => void 38 | ): void; 39 | 40 | readonly mixed: unique symbol; 41 | 42 | createRectangle(): RectangleNode; 43 | createLine(): LineNode; 44 | createEllipse(): EllipseNode; 45 | createPolygon(): PolygonNode; 46 | createStar(): StarNode; 47 | createVector(): VectorNode; 48 | createText(): TextNode; 49 | createFrame(): FrameNode; 50 | createComponent(): ComponentNode; 51 | createPage(): PageNode; 52 | createSlice(): SliceNode; 53 | /** 54 | * [DEPRECATED]: This API often fails to create a valid boolean operation. Use figma.union, figma.subtract, figma.intersect and figma.exclude instead. 55 | */ 56 | createBooleanOperation(): BooleanOperationNode; 57 | 58 | createPaintStyle(): PaintStyle; 59 | createTextStyle(): TextStyle; 60 | createEffectStyle(): EffectStyle; 61 | createGridStyle(): GridStyle; 62 | 63 | // The styles are returned in the same order as displayed in the UI. Only 64 | // local styles are returned. Never styles from team library. 65 | getLocalPaintStyles(): PaintStyle[]; 66 | getLocalTextStyles(): TextStyle[]; 67 | getLocalEffectStyles(): EffectStyle[]; 68 | getLocalGridStyles(): GridStyle[]; 69 | 70 | importComponentByKeyAsync(key: string): Promise; 71 | importStyleByKeyAsync(key: string): Promise; 72 | 73 | listAvailableFontsAsync(): Promise; 74 | loadFontAsync(fontName: FontName): Promise; 75 | readonly hasMissingFont: boolean; 76 | 77 | createNodeFromSvg(svg: string): FrameNode; 78 | 79 | createImage(data: Uint8Array): Image; 80 | getImageByHash(hash: string): Image; 81 | 82 | group( 83 | nodes: ReadonlyArray, 84 | parent: BaseNode & ChildrenMixin, 85 | index?: number 86 | ): GroupNode; 87 | flatten( 88 | nodes: ReadonlyArray, 89 | parent?: BaseNode & ChildrenMixin, 90 | index?: number 91 | ): VectorNode; 92 | 93 | union( 94 | nodes: ReadonlyArray, 95 | parent: BaseNode & ChildrenMixin, 96 | index?: number 97 | ): BooleanOperationNode; 98 | subtract( 99 | nodes: ReadonlyArray, 100 | parent: BaseNode & ChildrenMixin, 101 | index?: number 102 | ): BooleanOperationNode; 103 | intersect( 104 | nodes: ReadonlyArray, 105 | parent: BaseNode & ChildrenMixin, 106 | index?: number 107 | ): BooleanOperationNode; 108 | exclude( 109 | nodes: ReadonlyArray, 110 | parent: BaseNode & ChildrenMixin, 111 | index?: number 112 | ): BooleanOperationNode; 113 | } 114 | 115 | interface ClientStorageAPI { 116 | getAsync(key: string): Promise; 117 | setAsync(key: string, value: any): Promise; 118 | } 119 | 120 | interface NotificationOptions { 121 | timeout?: number; 122 | } 123 | 124 | interface NotificationHandler { 125 | cancel: () => void; 126 | } 127 | 128 | interface ShowUIOptions { 129 | visible?: boolean; 130 | width?: number; 131 | height?: number; 132 | } 133 | 134 | interface UIPostMessageOptions { 135 | origin?: string; 136 | } 137 | 138 | interface OnMessageProperties { 139 | origin: string; 140 | } 141 | 142 | type MessageEventHandler = ( 143 | pluginMessage: any, 144 | props: OnMessageProperties 145 | ) => void; 146 | 147 | interface UIAPI { 148 | show(): void; 149 | hide(): void; 150 | resize(width: number, height: number): void; 151 | close(): void; 152 | 153 | postMessage(pluginMessage: any, options?: UIPostMessageOptions): void; 154 | onmessage: MessageEventHandler | undefined; 155 | on(type: "message", callback: MessageEventHandler): void; 156 | once(type: "message", callback: MessageEventHandler): void; 157 | off(type: "message", callback: MessageEventHandler): void; 158 | } 159 | 160 | interface ViewportAPI { 161 | center: Vector; 162 | zoom: number; 163 | scrollAndZoomIntoView(nodes: ReadonlyArray): void; 164 | readonly bounds: Rect; 165 | } 166 | 167 | //////////////////////////////////////////////////////////////////////////////// 168 | // Datatypes 169 | 170 | type Transform = [[number, number, number], [number, number, number]]; 171 | 172 | interface Vector { 173 | readonly x: number; 174 | readonly y: number; 175 | } 176 | 177 | interface Rect { 178 | readonly x: number; 179 | readonly y: number; 180 | readonly width: number; 181 | readonly height: number; 182 | } 183 | 184 | interface RGB { 185 | readonly r: number; 186 | readonly g: number; 187 | readonly b: number; 188 | } 189 | 190 | interface RGBA { 191 | readonly r: number; 192 | readonly g: number; 193 | readonly b: number; 194 | readonly a: number; 195 | } 196 | 197 | interface FontName { 198 | readonly family: string; 199 | readonly style: string; 200 | } 201 | 202 | type TextCase = "ORIGINAL" | "UPPER" | "LOWER" | "TITLE"; 203 | 204 | type TextDecoration = "NONE" | "UNDERLINE" | "STRIKETHROUGH"; 205 | 206 | interface ArcData { 207 | readonly startingAngle: number; 208 | readonly endingAngle: number; 209 | readonly innerRadius: number; 210 | } 211 | 212 | interface ShadowEffect { 213 | readonly type: "DROP_SHADOW" | "INNER_SHADOW"; 214 | readonly color: RGBA; 215 | readonly offset: Vector; 216 | readonly radius: number; 217 | readonly visible: boolean; 218 | readonly blendMode: BlendMode; 219 | } 220 | 221 | interface BlurEffect { 222 | readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR"; 223 | readonly radius: number; 224 | readonly visible: boolean; 225 | } 226 | 227 | type Effect = ShadowEffect | BlurEffect; 228 | 229 | type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE"; 230 | 231 | interface Constraints { 232 | readonly horizontal: ConstraintType; 233 | readonly vertical: ConstraintType; 234 | } 235 | 236 | interface ColorStop { 237 | readonly position: number; 238 | readonly color: RGBA; 239 | } 240 | 241 | interface ImageFilters { 242 | readonly exposure?: number; 243 | readonly contrast?: number; 244 | readonly saturation?: number; 245 | readonly temperature?: number; 246 | readonly tint?: number; 247 | readonly highlights?: number; 248 | readonly shadows?: number; 249 | } 250 | 251 | interface SolidPaint { 252 | readonly type: "SOLID"; 253 | readonly color: RGB; 254 | 255 | readonly visible?: boolean; 256 | readonly opacity?: number; 257 | readonly blendMode?: BlendMode; 258 | } 259 | 260 | interface GradientPaint { 261 | readonly type: 262 | | "GRADIENT_LINEAR" 263 | | "GRADIENT_RADIAL" 264 | | "GRADIENT_ANGULAR" 265 | | "GRADIENT_DIAMOND"; 266 | readonly gradientTransform: Transform; 267 | readonly gradientStops: ReadonlyArray; 268 | 269 | readonly visible?: boolean; 270 | readonly opacity?: number; 271 | readonly blendMode?: BlendMode; 272 | } 273 | 274 | interface ImagePaint { 275 | readonly type: "IMAGE"; 276 | readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE"; 277 | readonly imageHash: string | null; 278 | readonly imageTransform?: Transform; // setting for "CROP" 279 | readonly scalingFactor?: number; // setting for "TILE" 280 | readonly filters?: ImageFilters; 281 | 282 | readonly visible?: boolean; 283 | readonly opacity?: number; 284 | readonly blendMode?: BlendMode; 285 | } 286 | 287 | type Paint = SolidPaint | GradientPaint | ImagePaint; 288 | 289 | interface Guide { 290 | readonly axis: "X" | "Y"; 291 | readonly offset: number; 292 | } 293 | 294 | interface RowsColsLayoutGrid { 295 | readonly pattern: "ROWS" | "COLUMNS"; 296 | readonly alignment: "MIN" | "MAX" | "STRETCH" | "CENTER"; 297 | readonly gutterSize: number; 298 | 299 | readonly count: number; // Infinity when "Auto" is set in the UI 300 | readonly sectionSize?: number; // Not set for alignment: "STRETCH" 301 | readonly offset?: number; // Not set for alignment: "CENTER" 302 | 303 | readonly visible?: boolean; 304 | readonly color?: RGBA; 305 | } 306 | 307 | interface GridLayoutGrid { 308 | readonly pattern: "GRID"; 309 | readonly sectionSize: number; 310 | 311 | readonly visible?: boolean; 312 | readonly color?: RGBA; 313 | } 314 | 315 | type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid; 316 | 317 | interface ExportSettingsConstraints { 318 | readonly type: "SCALE" | "WIDTH" | "HEIGHT"; 319 | readonly value: number; 320 | } 321 | 322 | interface ExportSettingsImage { 323 | readonly format: "JPG" | "PNG"; 324 | readonly contentsOnly?: boolean; // defaults to true 325 | readonly suffix?: string; 326 | readonly constraint?: ExportSettingsConstraints; 327 | } 328 | 329 | interface ExportSettingsSVG { 330 | readonly format: "SVG"; 331 | readonly contentsOnly?: boolean; // defaults to true 332 | readonly suffix?: string; 333 | readonly svgOutlineText?: boolean; // defaults to true 334 | readonly svgIdAttribute?: boolean; // defaults to false 335 | readonly svgSimplifyStroke?: boolean; // defaults to true 336 | } 337 | 338 | interface ExportSettingsPDF { 339 | readonly format: "PDF"; 340 | readonly contentsOnly?: boolean; // defaults to true 341 | readonly suffix?: string; 342 | } 343 | 344 | type ExportSettings = 345 | | ExportSettingsImage 346 | | ExportSettingsSVG 347 | | ExportSettingsPDF; 348 | 349 | type WindingRule = "NONZERO" | "EVENODD"; 350 | 351 | interface VectorVertex { 352 | readonly x: number; 353 | readonly y: number; 354 | readonly strokeCap?: StrokeCap; 355 | readonly strokeJoin?: StrokeJoin; 356 | readonly cornerRadius?: number; 357 | readonly handleMirroring?: HandleMirroring; 358 | } 359 | 360 | interface VectorSegment { 361 | readonly start: number; 362 | readonly end: number; 363 | readonly tangentStart?: Vector; // Defaults to { x: 0, y: 0 } 364 | readonly tangentEnd?: Vector; // Defaults to { x: 0, y: 0 } 365 | } 366 | 367 | interface VectorRegion { 368 | readonly windingRule: WindingRule; 369 | readonly loops: ReadonlyArray>; 370 | } 371 | 372 | interface VectorNetwork { 373 | readonly vertices: ReadonlyArray; 374 | readonly segments: ReadonlyArray; 375 | readonly regions?: ReadonlyArray; // Defaults to [] 376 | } 377 | 378 | interface VectorPath { 379 | readonly windingRule: WindingRule | "NONE"; 380 | readonly data: string; 381 | } 382 | 383 | type VectorPaths = ReadonlyArray; 384 | 385 | interface LetterSpacing { 386 | readonly value: number; 387 | readonly unit: "PIXELS" | "PERCENT"; 388 | } 389 | 390 | type LineHeight = 391 | | { 392 | readonly value: number; 393 | readonly unit: "PIXELS" | "PERCENT"; 394 | } 395 | | { 396 | readonly unit: "AUTO"; 397 | }; 398 | 399 | type BlendMode = 400 | | "PASS_THROUGH" 401 | | "NORMAL" 402 | | "DARKEN" 403 | | "MULTIPLY" 404 | | "LINEAR_BURN" 405 | | "COLOR_BURN" 406 | | "LIGHTEN" 407 | | "SCREEN" 408 | | "LINEAR_DODGE" 409 | | "COLOR_DODGE" 410 | | "OVERLAY" 411 | | "SOFT_LIGHT" 412 | | "HARD_LIGHT" 413 | | "DIFFERENCE" 414 | | "EXCLUSION" 415 | | "HUE" 416 | | "SATURATION" 417 | | "COLOR" 418 | | "LUMINOSITY"; 419 | 420 | interface Font { 421 | fontName: FontName; 422 | } 423 | 424 | type Reaction = { action: Action; trigger: Trigger }; 425 | 426 | type Action = 427 | | { readonly type: "BACK" | "CLOSE" } 428 | | { readonly type: "URL"; url: string } 429 | | { 430 | readonly type: "NODE"; 431 | readonly destinationId: string | null; 432 | readonly navigation: Navigation; 433 | readonly transition: Transition | null; 434 | readonly preserveScrollPosition: boolean; 435 | 436 | // Only present if navigation == "OVERLAY" and the destination uses 437 | // overlay position type "RELATIVE" 438 | readonly overlayRelativePosition?: Vector; 439 | }; 440 | 441 | interface SimpleTransition { 442 | readonly type: "DISSOLVE" | "SMART_ANIMATE"; 443 | readonly easing: Easing; 444 | readonly duration: number; 445 | } 446 | 447 | interface DirectionalTransition { 448 | readonly type: "MOVE_IN" | "MOVE_OUT" | "PUSH" | "SLIDE_IN" | "SLIDE_OUT"; 449 | readonly direction: "LEFT" | "RIGHT" | "TOP" | "BOTTOM"; 450 | readonly matchLayers: boolean; 451 | 452 | readonly easing: Easing; 453 | readonly duration: number; 454 | } 455 | 456 | type Transition = SimpleTransition | DirectionalTransition; 457 | 458 | type Trigger = 459 | | { readonly type: "ON_CLICK" | "ON_HOVER" | "ON_PRESS" | "ON_DRAG" } 460 | | { readonly type: "AFTER_TIMEOUT"; readonly timeout: number } 461 | | { 462 | readonly type: 463 | | "MOUSE_ENTER" 464 | | "MOUSE_LEAVE" 465 | | "MOUSE_UP" 466 | | "MOUSE_DOWN"; 467 | readonly delay: number; 468 | }; 469 | 470 | type Navigation = "NAVIGATE" | "SWAP" | "OVERLAY"; 471 | 472 | interface Easing { 473 | readonly type: "EASE_IN" | "EASE_OUT" | "EASE_IN_AND_OUT" | "LINEAR"; 474 | } 475 | 476 | type OverflowDirection = "NONE" | "HORIZONTAL" | "VERTICAL" | "BOTH"; 477 | 478 | type OverlayPositionType = 479 | | "CENTER" 480 | | "TOP_LEFT" 481 | | "TOP_CENTER" 482 | | "TOP_RIGHT" 483 | | "BOTTOM_LEFT" 484 | | "BOTTOM_CENTER" 485 | | "BOTTOM_RIGHT" 486 | | "MANUAL"; 487 | 488 | type OverlayBackground = 489 | | { readonly type: "NONE" } 490 | | { readonly type: "SOLID_COLOR"; readonly color: RGBA }; 491 | 492 | type OverlayBackgroundInteraction = "NONE" | "CLOSE_ON_CLICK_OUTSIDE"; 493 | 494 | //////////////////////////////////////////////////////////////////////////////// 495 | // Mixins 496 | 497 | interface BaseNodeMixin { 498 | readonly id: string; 499 | readonly parent: (BaseNode & ChildrenMixin) | null; 500 | name: string; // Note: setting this also sets `autoRename` to false on TextNodes 501 | readonly removed: boolean; 502 | toString(): string; 503 | remove(): void; 504 | 505 | getPluginData(key: string): string; 506 | setPluginData(key: string, value: string): void; 507 | 508 | // Namespace is a string that must be at least 3 alphanumeric characters, and should 509 | // be a name related to your plugin. Other plugins will be able to read this data. 510 | getSharedPluginData(namespace: string, key: string): string; 511 | setSharedPluginData(namespace: string, key: string, value: string): void; 512 | setRelaunchData(data: { 513 | [command: string]: /* description */ string; 514 | }): void; 515 | } 516 | 517 | interface SceneNodeMixin { 518 | visible: boolean; 519 | locked: boolean; 520 | } 521 | 522 | interface ChildrenMixin { 523 | readonly children: ReadonlyArray; 524 | 525 | appendChild(child: SceneNode): void; 526 | insertChild(index: number, child: SceneNode): void; 527 | 528 | findChildren(callback?: (node: SceneNode) => boolean): SceneNode[]; 529 | findChild(callback: (node: SceneNode) => boolean): SceneNode | null; 530 | 531 | /** 532 | * If you only need to search immediate children, it is much faster 533 | * to call node.children.filter(callback) or node.findChildren(callback) 534 | */ 535 | findAll(callback?: (node: SceneNode) => boolean): SceneNode[]; 536 | 537 | /** 538 | * If you only need to search immediate children, it is much faster 539 | * to call node.children.find(callback) or node.findChild(callback) 540 | */ 541 | findOne(callback: (node: SceneNode) => boolean): SceneNode | null; 542 | } 543 | 544 | interface ConstraintMixin { 545 | constraints: Constraints; 546 | } 547 | 548 | interface LayoutMixin { 549 | readonly absoluteTransform: Transform; 550 | relativeTransform: Transform; 551 | x: number; 552 | y: number; 553 | rotation: number; // In degrees 554 | 555 | readonly width: number; 556 | readonly height: number; 557 | constrainProportions: boolean; 558 | 559 | layoutAlign: "MIN" | "CENTER" | "MAX" | "STRETCH"; // applicable only inside auto-layout frames 560 | 561 | resize(width: number, height: number): void; 562 | resizeWithoutConstraints(width: number, height: number): void; 563 | } 564 | 565 | interface BlendMixin { 566 | opacity: number; 567 | blendMode: BlendMode; 568 | isMask: boolean; 569 | effects: ReadonlyArray; 570 | effectStyleId: string; 571 | } 572 | 573 | interface ContainerMixin { 574 | expanded: boolean; 575 | backgrounds: ReadonlyArray; // DEPRECATED: use 'fills' instead 576 | backgroundStyleId: string; // DEPRECATED: use 'fillStyleId' instead 577 | } 578 | 579 | type StrokeCap = 580 | | "NONE" 581 | | "ROUND" 582 | | "SQUARE" 583 | | "ARROW_LINES" 584 | | "ARROW_EQUILATERAL"; 585 | type StrokeJoin = "MITER" | "BEVEL" | "ROUND"; 586 | type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH"; 587 | 588 | interface GeometryMixin { 589 | fills: ReadonlyArray | PluginAPI["mixed"]; 590 | strokes: ReadonlyArray; 591 | strokeWeight: number; 592 | strokeMiterLimit: number; 593 | strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE"; 594 | strokeCap: StrokeCap | PluginAPI["mixed"]; 595 | strokeJoin: StrokeJoin | PluginAPI["mixed"]; 596 | dashPattern: ReadonlyArray; 597 | fillStyleId: string | PluginAPI["mixed"]; 598 | strokeStyleId: string; 599 | outlineStroke(): VectorNode | null; 600 | } 601 | 602 | interface CornerMixin { 603 | cornerRadius: number | PluginAPI["mixed"]; 604 | cornerSmoothing: number; 605 | } 606 | 607 | interface RectangleCornerMixin { 608 | topLeftRadius: number; 609 | topRightRadius: number; 610 | bottomLeftRadius: number; 611 | bottomRightRadius: number; 612 | } 613 | 614 | interface ExportMixin { 615 | exportSettings: ReadonlyArray; 616 | exportAsync(settings?: ExportSettings): Promise; // Defaults to PNG format 617 | } 618 | 619 | interface ReactionMixin { 620 | readonly reactions: ReadonlyArray; 621 | } 622 | 623 | interface DefaultShapeMixin 624 | extends BaseNodeMixin, 625 | SceneNodeMixin, 626 | ReactionMixin, 627 | BlendMixin, 628 | GeometryMixin, 629 | LayoutMixin, 630 | ExportMixin {} 631 | 632 | interface DefaultFrameMixin 633 | extends BaseNodeMixin, 634 | SceneNodeMixin, 635 | ReactionMixin, 636 | ChildrenMixin, 637 | ContainerMixin, 638 | GeometryMixin, 639 | CornerMixin, 640 | RectangleCornerMixin, 641 | BlendMixin, 642 | ConstraintMixin, 643 | LayoutMixin, 644 | ExportMixin { 645 | layoutMode: "NONE" | "HORIZONTAL" | "VERTICAL"; 646 | counterAxisSizingMode: "FIXED" | "AUTO"; // applicable only if layoutMode != "NONE" 647 | horizontalPadding: number; // applicable only if layoutMode != "NONE" 648 | verticalPadding: number; // applicable only if layoutMode != "NONE" 649 | itemSpacing: number; // applicable only if layoutMode != "NONE" 650 | 651 | layoutGrids: ReadonlyArray; 652 | gridStyleId: string; 653 | clipsContent: boolean; 654 | guides: ReadonlyArray; 655 | 656 | overflowDirection: OverflowDirection; 657 | numberOfFixedChildren: number; 658 | 659 | readonly overlayPositionType: OverlayPositionType; 660 | readonly overlayBackground: OverlayBackground; 661 | readonly overlayBackgroundInteraction: OverlayBackgroundInteraction; 662 | } 663 | 664 | //////////////////////////////////////////////////////////////////////////////// 665 | // Nodes 666 | 667 | interface DocumentNode extends BaseNodeMixin { 668 | readonly type: "DOCUMENT"; 669 | 670 | readonly children: ReadonlyArray; 671 | 672 | appendChild(child: PageNode): void; 673 | insertChild(index: number, child: PageNode): void; 674 | findChildren(callback?: (node: PageNode) => boolean): Array; 675 | findChild(callback: (node: PageNode) => boolean): PageNode | null; 676 | 677 | /** 678 | * If you only need to search immediate children, it is much faster 679 | * to call node.children.filter(callback) or node.findChildren(callback) 680 | */ 681 | findAll( 682 | callback?: (node: PageNode | SceneNode) => boolean 683 | ): Array; 684 | 685 | /** 686 | * If you only need to search immediate children, it is much faster 687 | * to call node.children.find(callback) or node.findChild(callback) 688 | */ 689 | findOne( 690 | callback: (node: PageNode | SceneNode) => boolean 691 | ): PageNode | SceneNode | null; 692 | } 693 | 694 | interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin { 695 | readonly type: "PAGE"; 696 | clone(): PageNode; 697 | 698 | guides: ReadonlyArray; 699 | selection: ReadonlyArray; 700 | selectedTextRange: { node: TextNode; start: number; end: number } | null; 701 | 702 | backgrounds: ReadonlyArray; 703 | 704 | readonly prototypeStartNode: 705 | | FrameNode 706 | | GroupNode 707 | | ComponentNode 708 | | InstanceNode 709 | | null; 710 | } 711 | 712 | interface FrameNode extends DefaultFrameMixin { 713 | readonly type: "FRAME"; 714 | clone(): FrameNode; 715 | } 716 | 717 | interface GroupNode 718 | extends BaseNodeMixin, 719 | SceneNodeMixin, 720 | ReactionMixin, 721 | ChildrenMixin, 722 | ContainerMixin, 723 | BlendMixin, 724 | LayoutMixin, 725 | ExportMixin { 726 | readonly type: "GROUP"; 727 | clone(): GroupNode; 728 | } 729 | 730 | interface SliceNode 731 | extends BaseNodeMixin, 732 | SceneNodeMixin, 733 | LayoutMixin, 734 | ExportMixin { 735 | readonly type: "SLICE"; 736 | clone(): SliceNode; 737 | } 738 | 739 | interface RectangleNode 740 | extends DefaultShapeMixin, 741 | ConstraintMixin, 742 | CornerMixin, 743 | RectangleCornerMixin { 744 | readonly type: "RECTANGLE"; 745 | clone(): RectangleNode; 746 | } 747 | 748 | interface LineNode extends DefaultShapeMixin, ConstraintMixin { 749 | readonly type: "LINE"; 750 | clone(): LineNode; 751 | } 752 | 753 | interface EllipseNode 754 | extends DefaultShapeMixin, 755 | ConstraintMixin, 756 | CornerMixin { 757 | readonly type: "ELLIPSE"; 758 | clone(): EllipseNode; 759 | arcData: ArcData; 760 | } 761 | 762 | interface PolygonNode 763 | extends DefaultShapeMixin, 764 | ConstraintMixin, 765 | CornerMixin { 766 | readonly type: "POLYGON"; 767 | clone(): PolygonNode; 768 | pointCount: number; 769 | } 770 | 771 | interface StarNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 772 | readonly type: "STAR"; 773 | clone(): StarNode; 774 | pointCount: number; 775 | innerRadius: number; 776 | } 777 | 778 | interface VectorNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 779 | readonly type: "VECTOR"; 780 | clone(): VectorNode; 781 | vectorNetwork: VectorNetwork; 782 | vectorPaths: VectorPaths; 783 | handleMirroring: HandleMirroring | PluginAPI["mixed"]; 784 | } 785 | 786 | interface TextNode extends DefaultShapeMixin, ConstraintMixin { 787 | readonly type: "TEXT"; 788 | clone(): TextNode; 789 | readonly hasMissingFont: boolean; 790 | textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED"; 791 | textAlignVertical: "TOP" | "CENTER" | "BOTTOM"; 792 | textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT"; 793 | paragraphIndent: number; 794 | paragraphSpacing: number; 795 | autoRename: boolean; 796 | 797 | textStyleId: string | PluginAPI["mixed"]; 798 | fontSize: number | PluginAPI["mixed"]; 799 | fontName: FontName | PluginAPI["mixed"]; 800 | textCase: TextCase | PluginAPI["mixed"]; 801 | textDecoration: TextDecoration | PluginAPI["mixed"]; 802 | letterSpacing: LetterSpacing | PluginAPI["mixed"]; 803 | lineHeight: LineHeight | PluginAPI["mixed"]; 804 | 805 | characters: string; 806 | insertCharacters( 807 | start: number, 808 | characters: string, 809 | useStyle?: "BEFORE" | "AFTER" 810 | ): void; 811 | deleteCharacters(start: number, end: number): void; 812 | 813 | getRangeFontSize(start: number, end: number): number | PluginAPI["mixed"]; 814 | setRangeFontSize(start: number, end: number, value: number): void; 815 | getRangeFontName(start: number, end: number): FontName | PluginAPI["mixed"]; 816 | setRangeFontName(start: number, end: number, value: FontName): void; 817 | getRangeTextCase(start: number, end: number): TextCase | PluginAPI["mixed"]; 818 | setRangeTextCase(start: number, end: number, value: TextCase): void; 819 | getRangeTextDecoration( 820 | start: number, 821 | end: number 822 | ): TextDecoration | PluginAPI["mixed"]; 823 | setRangeTextDecoration( 824 | start: number, 825 | end: number, 826 | value: TextDecoration 827 | ): void; 828 | getRangeLetterSpacing( 829 | start: number, 830 | end: number 831 | ): LetterSpacing | PluginAPI["mixed"]; 832 | setRangeLetterSpacing( 833 | start: number, 834 | end: number, 835 | value: LetterSpacing 836 | ): void; 837 | getRangeLineHeight( 838 | start: number, 839 | end: number 840 | ): LineHeight | PluginAPI["mixed"]; 841 | setRangeLineHeight(start: number, end: number, value: LineHeight): void; 842 | getRangeFills(start: number, end: number): Paint[] | PluginAPI["mixed"]; 843 | setRangeFills(start: number, end: number, value: Paint[]): void; 844 | getRangeTextStyleId( 845 | start: number, 846 | end: number 847 | ): string | PluginAPI["mixed"]; 848 | setRangeTextStyleId(start: number, end: number, value: string): void; 849 | getRangeFillStyleId( 850 | start: number, 851 | end: number 852 | ): string | PluginAPI["mixed"]; 853 | setRangeFillStyleId(start: number, end: number, value: string): void; 854 | } 855 | 856 | interface ComponentNode extends DefaultFrameMixin { 857 | readonly type: "COMPONENT"; 858 | clone(): ComponentNode; 859 | 860 | createInstance(): InstanceNode; 861 | description: string; 862 | readonly remote: boolean; 863 | readonly key: string; // The key to use with "importComponentByKeyAsync" 864 | } 865 | 866 | interface InstanceNode extends DefaultFrameMixin { 867 | readonly type: "INSTANCE"; 868 | clone(): InstanceNode; 869 | masterComponent: ComponentNode; 870 | scaleFactor: number; 871 | } 872 | 873 | interface BooleanOperationNode 874 | extends DefaultShapeMixin, 875 | ChildrenMixin, 876 | CornerMixin { 877 | readonly type: "BOOLEAN_OPERATION"; 878 | clone(): BooleanOperationNode; 879 | booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE"; 880 | 881 | expanded: boolean; 882 | } 883 | 884 | type BaseNode = DocumentNode | PageNode | SceneNode; 885 | 886 | type SceneNode = 887 | | SliceNode 888 | | FrameNode 889 | | GroupNode 890 | | ComponentNode 891 | | InstanceNode 892 | | BooleanOperationNode 893 | | VectorNode 894 | | StarNode 895 | | LineNode 896 | | EllipseNode 897 | | PolygonNode 898 | | RectangleNode 899 | | TextNode; 900 | 901 | type NodeType = 902 | | "DOCUMENT" 903 | | "PAGE" 904 | | "SLICE" 905 | | "FRAME" 906 | | "GROUP" 907 | | "COMPONENT" 908 | | "INSTANCE" 909 | | "BOOLEAN_OPERATION" 910 | | "VECTOR" 911 | | "STAR" 912 | | "LINE" 913 | | "ELLIPSE" 914 | | "POLYGON" 915 | | "RECTANGLE" 916 | | "TEXT"; 917 | 918 | //////////////////////////////////////////////////////////////////////////////// 919 | // Styles 920 | type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID"; 921 | 922 | interface BaseStyle { 923 | readonly id: string; 924 | readonly type: StyleType; 925 | name: string; 926 | description: string; 927 | remote: boolean; 928 | readonly key: string; // The key to use with "importStyleByKeyAsync" 929 | remove(): void; 930 | } 931 | 932 | interface PaintStyle extends BaseStyle { 933 | type: "PAINT"; 934 | paints: ReadonlyArray; 935 | } 936 | 937 | interface TextStyle extends BaseStyle { 938 | type: "TEXT"; 939 | fontSize: number; 940 | textDecoration: TextDecoration; 941 | fontName: FontName; 942 | letterSpacing: LetterSpacing; 943 | lineHeight: LineHeight; 944 | paragraphIndent: number; 945 | paragraphSpacing: number; 946 | textCase: TextCase; 947 | } 948 | 949 | interface EffectStyle extends BaseStyle { 950 | type: "EFFECT"; 951 | effects: ReadonlyArray; 952 | } 953 | 954 | interface GridStyle extends BaseStyle { 955 | type: "GRID"; 956 | layoutGrids: ReadonlyArray; 957 | } 958 | 959 | //////////////////////////////////////////////////////////////////////////////// 960 | // Other 961 | 962 | interface Image { 963 | readonly hash: string; 964 | getBytesAsync(): Promise; 965 | } 966 | } // declare global 967 | 968 | export {}; 969 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DESIGNR Helper", 3 | "id": "999024744861366798", 4 | "api": "1.0.0", 5 | "main": "dist/code.js", 6 | "ui": "dist/ui.html", 7 | "editorType": ["figma"] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "code.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "npx webpack" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@figma/plugin-typings": "^1.28.0", 14 | "@types/lodash": "^4.14.150", 15 | "@types/styled-components": "^5.1.0", 16 | "css-loader": "^3.1.0", 17 | "csstype": "^2.6.10", 18 | "html-webpack-inline-source-plugin": "0.0.10", 19 | "html-webpack-plugin": "^3.2.0", 20 | "react": "^16.13.1", 21 | "react-dom": "^16.13.1", 22 | "style-loader": "^0.23.1", 23 | "styled-components": "^5.1.0", 24 | "ts-loader": "^6.0.4", 25 | "typescript": "^3.5.3", 26 | "url-loader": "^2.1.0", 27 | "webpack": "^4.38.0", 28 | "webpack-cli": "^4.2.0" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^16.9.34", 32 | "@types/react-dom": "^16.9.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/code.ts: -------------------------------------------------------------------------------- 1 | // This plugin will open a modal to prompt the user to enter a number, and 2 | // it will then create that many rectangles on the screen. 3 | 4 | // This file holds the main code for the plugins. It has access to the *document*. 5 | // You can access browser APIs in the