Changes
2 changed files (+212/-42)
-
-
@@ -1,5 +1,17 @@import { svg } from "lit-element"; export const CloseIcon = ({ onClick = () => {} }) => svg` <svg @click=${onClick} title="close icon" width="14" height="14" viewBox="0 0 20 20" fill="none"> <path d="M1 19L19 1M19 19L1 1" stroke="#B3B3B3" stroke-width="2"/> </svg> `; export const CopyIcon = ({ onClick = () => {} }) => svg` <svg @click=${onClick} title="copy icon" width="14" height="14" viewBox="0 0 30 30" fill="none"> <path d="M21 25.5C21 24.9477 20.5523 24.5 20 24.5C19.4477 24.5 19 24.9477 19 25.5H21ZM13 2H25V0H13V2ZM28 5V21H30V5H28ZM25 24H13V26H25V24ZM10 21V5H8V21H10ZM13 24C11.3431 24 10 22.6569 10 21H8C8 23.7614 10.2386 26 13 26V24ZM28 21C28 22.6569 26.6569 24 25 24V26C27.7614 26 30 23.7614 30 21H28ZM25 2C26.6569 2 28 3.34315 28 5H30C30 2.23858 27.7614 0 25 0V2ZM13 0C10.2386 0 8 2.23858 8 5H10C10 3.34315 11.3431 2 13 2V0ZM16.5 28H5V30H16.5V28ZM2 25V10H0V25H2ZM5 28C3.34315 28 2 26.6569 2 25H0C0 27.7614 2.23858 30 5 30V28ZM5 7H8V5H5V7ZM2 10C2 8.34315 3.34315 7 5 7V5C2.23858 5 0 7.23858 0 10H2ZM16.5 30C18.9853 30 21 27.9853 21 25.5H19C19 26.8807 17.8807 28 16.5 28V30Z" fill="#B3B3B3"/> </svg> `; export const HorizontalPaddingIcon = () => svg` <svg title="horizontal padding" width="14" height="14" viewBox="0 0 29 28" fill="none"> <rect x="7" y="8" width="14" height="14" stroke="#B3B3B3" stroke-width="2"/>
-
-
-
@@ -1,18 +1,28 @@import { css, html } from "lit-element"; import * as Figma from "figma-js"; import * as copy from "copy-to-clipboard"; import { BorderRadiusIcon, HorizontalPaddingIcon, VerticalPaddingIcon, CloseIcon, CopyIcon, } from "./Icons"; export interface FigmaNode { type Color = { a: number; r: number; g: number; b: number; }; export type FigmaNode = Figma.Node & { name: string; backgroundColor: { a: number; r: number; g: number; b: number; }; characters: string; background: { color: Color }[]; backgroundColor: Color; fills: { color: Color }[]; absoluteBoundingBox: { height: number; width: number;
-
@@ -30,60 +40,208 @@ textAlignHorizontal: string;textAlignVertical: string; }; type: "TEXT" | "INSTANCE" | "FRAME" | "VECTOR" | "RECTANGLE"; }; export type InspectorViewProps = { node: FigmaNode; onClose: () => void; }; /** * Figma returns rbg in values from 0 to 1. We multiply by 255 and truncate them * https://www.figma.com/plugin-docs/api/RGB/ */ const rgbToHex = ({ r, g, b }: { r: number; g: number; b: number }) => "#" + ( (1 << 24) + (Math.trunc(255 * r) << 16) + (Math.trunc(255 * g) << 8) + Math.trunc(255 * b) ) .toString(16) .slice(1); class NodeStyles { background; borderRadius; color; fontFamily; fontSize; fontWeight; height; horizontalPadding; lineHeight; verticalPadding; width; hasStyles = false; hasPadding = false; constructor(node: FigmaNode) { this.height = `${Math.trunc(node.absoluteBoundingBox.height)}px`; this.width = `${Math.trunc(node.absoluteBoundingBox.width)}px`; if (node.horizontalPadding || node.verticalPadding) { this.hasPadding = true; this.horizontalPadding = `${node.horizontalPadding}px`; this.verticalPadding = `${node.verticalPadding}px`; } if (node.style) { this.fontFamily = node.style.fontFamily; this.fontWeight = node.style.fontWeight; this.fontSize = `${node.style.fontSize}px`; this.lineHeight = `${Math.trunc(node.style.lineHeightPx)}px`; } if (node.rectangleCornerRadii) { this.borderRadius = node.rectangleCornerRadii.filter( (radius) => radius === node.cornerRadius ).length < 4 ? `${node.rectangleCornerRadii.join("px, ")}px` : `${node.cornerRadius}px`; } if (node.backgroundColor || node.backgroundColor) { const colors = node.backgroundColor || node.background?.[0].color || node.fills?.[0].color; this.background = rgbToHex(colors); } if (node.fills && node.fills.length > 0) { this.color = rgbToHex(node.fills[0].color); } this.hasStyles = !!( node.style || node.rectangleCornerRadii || this.background || this.color ); } getStyles() { return [ this.height && `height: ${this.height};`, this.width && `width: ${this.width};`, this.fontFamily && `font-family: ${this.fontFamily};`, this.fontSize && `font-size: ${this.fontSize};`, this.fontWeight && `font-weight: ${this.fontWeight};`, this.lineHeight && `line-height: ${this.lineHeight};`, this.borderRadius && `border-radius: ${this.borderRadius};`, this.background && `background: ${this.background};`, this.color && `color: ${this.color};`, ].filter(Boolean) as string[]; } copyStyleSheet() { copy(this.getStyles().join("\n")); } } export const View = (node: FigmaNode) => { export const View = ({ node, onClose }: InspectorViewProps) => { if (!node) { return null; } let borderRadius; if (node.rectangleCornerRadii) { borderRadius = node.rectangleCornerRadii.filter((radius) => radius === node.cornerRadius) .length < 4 ? node.rectangleCornerRadii.join("px, ") : node.cornerRadius; } const nodeStyles = new NodeStyles(node); return html` <div class="inspector-view"> <div class="inspector-section"> <div class="inspector-section title-section"> <h4>${node.name}</h4> ${CloseIcon({ onClick: onClose })} </div> <div class="inspector-section"> <h4>Properties</h4> <p class="inspector-property">W: ${node.absoluteBoundingBox.width}px</p> <p class="inspector-property"> H: ${node.absoluteBoundingBox.height}px </p> ${borderRadius && html`<p class="inspector-property"> ${BorderRadiusIcon()} ${borderRadius}px </p>`} <p class="inspector-property">W: ${nodeStyles.width}</p> <p class="inspector-property">H: ${nodeStyles.height}</p> ${nodeStyles.borderRadius ? html`<p class="inspector-property"> ${BorderRadiusIcon()} ${nodeStyles.borderRadius} </p>` : null} </div> ${(node.horizontalPadding || node.verticalPadding) && html`<div class="inspector-section"> <h4>Layout</h4> <p class="inspector-property"> ${HorizontalPaddingIcon()} ${node.horizontalPadding}px </p> <p class="inspector-property"> ${VerticalPaddingIcon()} ${node.verticalPadding}px </p> </div>`} ${node.style && html`<div class="inspector-section"> <h4>Text</h4> <p>Font-family: ${node.style.fontFamily}</p> <p>Font size: ${node.style.fontSize}</p> <p>Font weight: ${node.style.fontWeight}</p> </div>`} ${nodeStyles.hasPadding ? html`<div class="inspector-section"> <h4>Layout</h4> ${nodeStyles.horizontalPadding && html`<p class="inspector-property"> ${HorizontalPaddingIcon()} ${nodeStyles.horizontalPadding} </p>`} ${nodeStyles.verticalPadding && html`<p class="inspector-property"> ${VerticalPaddingIcon()} ${nodeStyles.verticalPadding} </p>`} </div>` : null} ${node.characters ? html`<div class="inspector-section"> <div class="title-section"> <h4>Content</h4> ${CopyIcon({ onClick: () => copy(node.characters) })} </div> <p class="node-content node-code">${node.characters}</p> </div>` : null} ${nodeStyles.hasStyles ? StylesSection(nodeStyles) : null} </div> `; }; export const StylesSection = (nodeStyles: NodeStyles) => { const onClick = () => nodeStyles.copyStyleSheet(); const styles = nodeStyles.getStyles(); return html`<div class="inspector-section"> <div class="title-section style-section"> <h4>CSS</h4> ${CopyIcon({ onClick })} </div> <div class="node-code"> ${styles.map( (style) => html`<p class="css-property" @click=${() => copy(style)}>${style}</p>` )} </div> </div> `; }; export const styles = css` .css-property:hover { cursor: pointer; background-color: #e8e8e8; } .style-section { padding: 1rem 0; } .title-section { display: flex; align-items: center; } .title-section svg { cursor: pointer; margin-left: auto; } .node-content { cursor: text; user-select: text; } .node-code { padding: 0.5rem; background: #f3f3f3; font-family: monospace; } .inspector-view { height: 100vh; width: 300px;
-