Compare commits

...

11 Commits

Author SHA1 Message Date
Yashu Mittal
6c31ab914a add timer off icon (#309) 2021-11-17 20:18:26 +01:00
SMAH1
f591c86aba Refactoring (#436) 2021-11-17 20:18:12 +01:00
Eric Fennis
8cab99a2ee New Figma Plugin setup (#434)
* Update changes

* Add new ui files

* add new script files

* process

* fix build

* try to fix the worker

* try to implement rollup for figma plugin

* bump flutter package version

* add vite for figma-plugin

* add vite

* add vite

* Fix ext output

* make plugin work!

* Add Folder open icon

Co-authored-by: Jan <72730682+JanTrichter@users.noreply.github.com>

* add new fetcher

* Build new api

* add plugin fetcher

* fix fetch icons

* Fix search

* Fix site build

* update yarn.lock

Co-authored-by: Jan <72730682+JanTrichter@users.noreply.github.com>
2021-11-17 20:12:45 +01:00
Brandon Duffany
ac25cdca38 Add wrap-text icon (#435)
* Add wrap text icon

* Optimize

* Add tags for wrap-text
2021-11-15 07:52:30 +01:00
dependabot[bot]
dca5f0f5a6 Bump dns-packet from 1.3.1 to 1.3.4 (#442)
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

---
updated-dependencies:
- dependency-name: dns-packet
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-14 16:09:34 +01:00
dependabot[bot]
dc988cdf45 Bump tar from 6.1.0 to 6.1.11 (#441)
Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.11.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.11)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-14 16:07:11 +01:00
dependabot[bot]
326267521d Bump url-parse from 1.5.1 to 1.5.3 (#440)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.3)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-14 16:07:02 +01:00
dependabot[bot]
e6253d2455 Bump jszip from 3.6.0 to 3.7.1 (#439)
Bumps [jszip](https://github.com/Stuk/jszip) from 3.6.0 to 3.7.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/master/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.6.0...v3.7.1)

---
updated-dependencies:
- dependency-name: jszip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-14 16:06:52 +01:00
SMAH1
fa55b04fa0 Use 'angular build workflow' in Windows (#433) 2021-11-14 15:50:03 +01:00
SMAH1
eded6e0db2 Support Angular version 11 or higher (#432) 2021-11-14 15:48:50 +01:00
Eric Fennis
1f9a3b6a8d Add folder-open icon (#429)
* bump flutter package version

* Add Folder open icon

Co-authored-by: Jan <72730682+JanTrichter@users.noreply.github.com>

Co-authored-by: Jan <72730682+JanTrichter@users.noreply.github.com>
2021-11-09 20:32:31 +01:00
46 changed files with 3271 additions and 6781 deletions

View File

@@ -10,5 +10,5 @@
stroke-linejoin="round"
>
<polyline points="17 18 11 12 17 6" />
<path d="M7 6V18" />
<path d="M7 6v12" />
</svg>

Before

Width:  |  Height:  |  Size: 272 B

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -10,5 +10,5 @@
stroke-linejoin="round"
>
<polyline points="7 18 13 12 7 6" />
<path d="M17 6V18" />
<path d="M17 6v12" />
</svg>

Before

Width:  |  Height:  |  Size: 271 B

After

Width:  |  Height:  |  Size: 271 B

13
icons/folder-open.svg Normal file
View File

@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M6 17l2-5h14l-3 8a2 2 0 01-2 1H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h7a2 2 0 012 2v4" />
</svg>

After

Width:  |  Height:  |  Size: 305 B

16
icons/timer-off.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10 2h4" />
<path d="M7.43 7.433A8 8 0 0118.566 18.57M4.582 11A8 8 0 0015 21.419" />
<path d="M2 2l20 20" />
<path d="M12 12v-2" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -13,9 +13,9 @@
<path d="M15 16v-2" />
<path d="M8 9h2" />
<path d="M20 9h2" />
<path d="M17.8 11.8 19 13" />
<path d="M17.8 11.8L19 13" />
<path d="M15 9h0" />
<path d="M17.8 6.2 19 5" />
<path d="M3 21 12 12" />
<path d="M12.2 6.2 11 5" />
<path d="M17.8 6.2L19 5" />
<path d="M3 21l9-9" />
<path d="M12.2 6.2L11 5" />
</svg>

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 441 B

16
icons/wrap-text.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="3" y1="6" x2="21" y2="6" />
<path d="M3 12h15a3 3 0 110 6h-4" />
<polyline points="16 16 14 18 16 20" />
<line x1="3" y1="18" x2="10" y2="18" />
</svg>

After

Width:  |  Height:  |  Size: 371 B

View File

@@ -46,6 +46,9 @@ testem.log
.DS_Store
Thumbs.db
#npm-yarn
# npm-yarn
package-lock.json
src/createElement.js
# angular cache
.angular/cache

View File

@@ -23,7 +23,7 @@
],
"scripts": {
"build": "yarn clean && yarn build:icons && yarn build:ng",
"clean": "rm -rf dist && rm -rf ./src/icons/*.ts",
"clean": "npx shx rm -rf dist && npx shx rm -rf ./src/icons/*.ts",
"build:icons": "yarn --cwd ../../ build:icons --output=../packages/lucide-angular/src --templateSrc=../packages/lucide-angular/scripts/exportTemplate --iconFileExtention=.ts --exportFileName=index.ts",
"build:ng": "ng build --prod",
"test:headless": "ng test --no-watch --no-progress --browsers=ChromeHeadlessCI",
@@ -33,35 +33,31 @@
"postinstall": "ngcc"
},
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^11.2.6",
"@angular/core": "^11.2.6"
"tslib": "^2.3.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.5",
"@angular/cli": "~11.2.5",
"@angular/common": "~11.2.6",
"@angular/compiler": "~11.2.6",
"@angular/compiler-cli": "~11.2.6",
"@angular/core": "~11.2.6",
"@angular/platform-browser": "~11.2.6",
"@angular/platform-browser-dynamic": "~11.2.6",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.1.0",
"@angular/cli": "~11.2.15",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/compiler-cli": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"ng-packagr": "^11.2.4",
"@types/jasmine": "~3.10.2",
"@types/node": "^16.11.7",
"codelyzer": "^6.0.2",
"jasmine-core": "~3.10.1",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.3.8",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"ng-packagr": "^11.0.0",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.7.0",
"protractor": "~7.0.0",
"puppeteer": "^8.0.0",
"ts-node": "~8.3.0",
"ts-node": "~10.4.0",
"tslint": "~6.1.0",
"typescript": "~4.1.5"
}

View File

@@ -1,675 +0,0 @@
// Global variable with Figma's plugin API.
declare const figma: PluginAPI
declare const __html__: string
interface PluginAPI {
readonly currentPage: PageNode
// Root of the current Figma document.
readonly root: DocumentNode
// API for accessing viewport information.
readonly viewport: ViewportAPI
// call this once your plugin is finished executing.
closePlugin(): void
// Command that the user chose through menu when launching the plugin.
readonly command: string
// Finds a node by its id. If not found, returns null.
getNodeById(id: string): BaseNode | null
// Finds a style by its id. If not found, returns null.
getStyleById(id: string): BaseStyle | null
// Access browser APIs and/or show UI to the user.
showUI(html: string, options?: ShowUIOptions): void
readonly ui: UIAPI
// Lets you store persistent data on the user's local machine
readonly clientStorage: ClientStorageAPI
// This value is returned when a property is in a "mixed" state.
// In order to check if a property is in a mixed state, always
// compare directly to this value. I.e.
// `if (node.cornerRadius === figma.mixed) { ... }`
mixed: symbol
// Creates new nodes. Nodes will start off inserted
// into the current page.
// To move them elsewhere use `appendChild` or `insertChild`
createRectangle(): RectangleNode
createLine(): LineNode
createEllipse(): EllipseNode
createPolygon(): PolygonNode
createStar(): StarNode
createVector(): VectorNode
createText(): TextNode
createBooleanOperation(): BooleanOperationNode
createFrame(): FrameNode
createComponent(): ComponentNode
createPage(): PageNode
createSlice(): SliceNode
// Creates styles. A style's id can be assigned to
// node properties like textStyleId, fillStyleId, etc.
createPaintStyle(): PaintStyle
createTextStyle(): TextStyle
createEffectStyle(): EffectStyle
createGridStyle(): GridStyle
// These let you insert stuff from the team library if you have the key
importComponentByKeyAsync(key: string): Promise<ComponentNode>
importStyleByKeyAsync(key: string): Promise<BaseStyle>
// Return all fonts currently supported for use with the "fontName" property
listAvailableFontsAsync(): Promise<Font[]>
// You must await the promise returned here before being able to use "fontName"
loadFontAsync(fontName: FontName): Promise<void>
// Creates node from an SVG string.
createNodeFromSvg(svg: string): FrameNode
// Creates an Image object using the provided file contents.
createImage(data: Uint8Array): Image
// Groups every node in `nodes` under a new group.
group(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): FrameNode
// Flattens every node in `nodes` into a single vector network.
flatten(nodes: ReadonlyArray<BaseNode>, parent?: BaseNode & ChildrenMixin, index?: number): VectorNode
}
interface ClientStorageAPI {
// This stores information in the browser, not on the server. It's similar to localStorage, but is
// asynchronous, and allows storing objects, arrays, strings, numbers, booleans, null, undefined and Uint8Arrays.
getAsync(key: string): Promise<any | undefined>
setAsync(key: string, value: any): Promise<void>
}
type ShowUIOptions = {
visible?: boolean, // defaults to true
width?: number, // defaults to 300
height?: number, // defaults to 200
}
interface UIAPI {
show(): void
hide(): void
resize(width: number, height: number): void
close(): void
// Sends a message to the iframe.
postMessage(pluginMessage: any): void
// Registers a callback for messages sent by the iframe.
onmessage: ((pluginMessage: any) => void) | undefined
}
interface ViewportAPI {
center: { x: number, y: number }
// 1.0 means 100% zoom, 0.5 means 50% zoom.
zoom: number
// Adjust the viewport such that it shows the provided nodes.
scrollAndZoomIntoView(nodes: ReadonlyArray<BaseNode>)
}
// manifest.json format
interface ManifestJson {
// Name of the plugin.
name: string
// Version of the runtime that the plugin uses, e.g. '0.5.0'.
version: string
// The file name that contains the plugin code.
script: string
// The file name that contains the html code made available in script.
html?: string
// Shell command to be executed before the contents of the `html` and `script` files are read.
build?: string
// Menu items to show up in UI.
menu?: ManifestMenuItem[]
}
type ManifestMenuItem =
// Clickable menu item.
{ name: string, command: string } |
// Separator
{ separator: true } |
// Submenu
{ name: string, menu: ManifestMenuItem[] }
////////////////////////////////////////////////////////////////////////////////
// Values
// These are the top two rows of a 3x3 matrix. This is enough to represent
// translation, rotation, and skew.
type Transform = [
[number, number, number],
[number, number, number]
]
interface Vector {
readonly x: number
readonly y: number
}
interface RGB {
readonly r: number
readonly g: number
readonly b: number
}
interface RGBA {
readonly r: number
readonly g: number
readonly b: number
readonly a: number
}
interface FontName {
readonly family: string
readonly style: string
}
interface ArcData {
readonly startingAngle: number
readonly endingAngle: number
readonly innerRadius: number
}
interface ShadowEffect {
readonly type: "DROP_SHADOW" | "INNER_SHADOW"
readonly color: RGBA
readonly offset: Vector
readonly radius: number
readonly visible: boolean
readonly blendMode: BlendMode
}
interface BlurEffect {
readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR"
readonly radius: number
readonly visible: boolean
}
type Effect = ShadowEffect | BlurEffect
type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE"
interface Constraints {
readonly horizontal: ConstraintType
readonly vertical: ConstraintType
}
interface ColorStop {
readonly position: number
readonly color: RGBA
}
interface SolidPaint {
readonly type: "SOLID"
readonly color: RGB
readonly visible?: boolean
readonly opacity?: number
}
interface GradientPaint {
readonly type: "GRADIENT_LINEAR" | "GRADIENT_RADIAL" | "GRADIENT_ANGULAR" | "GRADIENT_DIAMOND"
readonly gradientTransform: Transform
readonly gradientStops: ReadonlyArray<ColorStop>
readonly visible?: boolean
readonly opacity?: number
}
interface ImagePaint {
readonly type: "IMAGE"
readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE"
readonly image: Image | null
readonly imageTransform?: Transform // setting for "CROP"
readonly scalingFactor?: number // setting for "TILE"
readonly visible?: boolean
readonly opacity?: number
}
type Paint = SolidPaint | GradientPaint | ImagePaint
interface Guide {
readonly axis: "X" | "Y"
readonly offset: number
}
interface RowsColsLayoutGrid {
readonly pattern: "ROWS" | "COLUMNS"
readonly alignment: "MIN" | "STRETCH" | "CENTER"
readonly gutterSize: number
readonly count: number // Infinity when "Auto" is set in the UI
readonly sectionSize?: number // Not set for alignment: "STRETCH"
readonly offset?: number // Not set for alignment: "CENTER"
readonly visible?: boolean
readonly color?: RGBA
}
interface GridLayoutGrid {
readonly pattern: "GRID"
readonly sectionSize: number
readonly visible?: boolean
readonly color?: RGBA
}
type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid
interface ExportSettingsImage {
format: "JPG" | "PNG"
contentsOnly?: boolean // defaults to true
suffix?: string
constraint?: { // defaults to unscaled ({ type: "SCALE", value: 1 })
type: "SCALE" | "WIDTH" | "HEIGHT"
value: number
}
}
interface ExportSettingsSVG {
format: "SVG"
contentsOnly?: boolean // defaults to true
suffix?: string
svgOutlineText?: boolean // defaults to true
svgIdAttribute?: boolean // defaults to false
svgSimplifyStroke?: boolean // defaults to true
}
interface ExportSettingsPDF {
format: "PDF"
contentsOnly?: boolean // defaults to true
suffix?: string
}
type ExportSettings = ExportSettingsImage | ExportSettingsSVG | ExportSettingsPDF
type WindingRule = "NONZERO" | "EVENODD"
interface VectorVertex {
readonly x: number
readonly y: number
readonly strokeCap?: StrokeCap
readonly strokeJoin?: StrokeJoin
readonly cornerRadius?: number
readonly handleMirroring?: HandleMirroring
}
interface VectorSegment {
readonly start: number
readonly end: number
readonly tangentStart?: Vector // Defaults to { x: 0, y: 0 }
readonly tangentEnd?: Vector // Defaults to { x: 0, y: 0 }
}
interface VectorRegion {
readonly windingRule: WindingRule
readonly loops: ReadonlyArray<ReadonlyArray<number>>
}
interface VectorNetwork {
readonly vertices: ReadonlyArray<VectorVertex>
readonly segments: ReadonlyArray<VectorSegment>
readonly regions?: ReadonlyArray<VectorRegion> // Defaults to []
}
interface VectorPath {
// Similar to the svg fill-rule
// "NONE" means an open path won't have a fill
readonly windingRule: WindingRule | "NONE"
readonly data: string
}
type VectorPaths = ReadonlyArray<VectorPath>
interface NumberWithUnits {
readonly value: number
readonly units: "PIXELS" | "PERCENT"
}
type BlendMode =
"PASS_THROUGH" |
"NORMAL" |
"DARKEN" |
"MULTIPLY" |
"LINEAR_BURN" |
"COLOR_BURN" |
"LIGHTEN" |
"SCREEN" |
"LINEAR_DODGE" |
"COLOR_DODGE" |
"OVERLAY" |
"SOFT_LIGHT" |
"HARD_LIGHT" |
"DIFFERENCE" |
"EXCLUSION" |
"HUE" |
"SATURATION" |
"COLOR" |
"LUMINOSITY"
interface Font {
fontName: FontName
}
////////////////////////////////////////////////////////////////////////////////
// Mixins
interface BaseNodeMixin {
readonly id: string
readonly parent: (BaseNode & ChildrenMixin) | null
name: string
visible: boolean
locked: boolean
removed: boolean
toString(): string
remove(): void
// Attach custom data to a node. Only your plugin will be able to read this.
getPluginData(key: string): string
setPluginData(key: string, value: string): void
// Attach custom data to a node. All plugins will be able to read this.
// Namespace is a string that must be at least 3 alphanumeric characters, and should
// be a name related to your plugin. This is a mandatory argument to avoid multiple
// multiple plugins adding keys like "data" and colliding with each other. Other
// plugins will still be able to read shared plugin data as long as they know the
// namespace you use.
getSharedPluginData(namespace: string, key: string): string
setSharedPluginData(namespace: string, key: string, value: string): void
}
interface ChildrenMixin {
// Sorted back-to-front. I.e. the top-most child is last in this array.
readonly children: ReadonlyArray<BaseNode>
// Adds to the end of the .children array. I.e. visually on top of all other
// children.
appendChild(child: BaseNode): void
insertChild(index: number, child: BaseNode): void
findAll(callback?: (node: BaseNode) => boolean): ReadonlyArray<BaseNode>
findOne(callback: (node: BaseNode) => boolean): BaseNode | null
}
interface LayoutMixin {
readonly absoluteTransform: Transform
relativeTransform: Transform
x: number // The same as "relativeTransform[0][2]"
y: number // The same as "relativeTransform[1][2]"
rotation: number // The angle of the x axis of "relativeTransform" in degrees. Returns values from -180 to 180.
readonly size: Vector
readonly width: number // The same as "size.x"
readonly height: number // The same as "size.y"
// Resizes the node. If children of the node has constraints, it applies those constraints
// width and height must be >= 0.01
resize(width: number, height: number): void
// Resizes the node. Children of the node are never resized, even if those children have
// constraints. width and height must be >= 0.01
resizeWithoutConstraints(width: number, height: number): void
constraints: Constraints
}
interface BlendMixin {
opacity: number
blendMode: BlendMode
isMask: boolean
effects: ReadonlyArray<Effect>
effectStyleId: string
}
interface FrameMixin {
backgrounds: ReadonlyArray<Paint>
layoutGrids: ReadonlyArray<LayoutGrid>
clipsContent: boolean
guides: ReadonlyArray<Guide>
gridStyleId: string
backgroundStyleId: string
}
type StrokeCap = "NONE" | "ROUND" | "SQUARE" | "ARROW_LINES" | "ARROW_EQUILATERAL"
type StrokeJoin = "MITER" | "BEVEL" | "ROUND"
type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH"
interface GeometryMixin {
fills: ReadonlyArray<Paint> | symbol // This can return figma.mixed on TEXT nodes
strokes: ReadonlyArray<Paint>
strokeWeight: number
strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE"
strokeCap: StrokeCap | symbol // This can return figma.mixed on VECTOR nodes if vertices have different strokeCap values
strokeJoin: StrokeJoin | symbol // This can return figma.mixed on VECTOR nodes if vertices have different strokeJoin values
dashPattern: ReadonlyArray<number>
fillStyleId: string | symbol // This can return figma.mixed on TEXT nodes
strokeStyleId: string
}
interface CornerMixin {
// This can return figma.mixed on VECTOR nodes if vertices have different cornerRadius values,
// and on RECTANGLE nodes if node.topLeftRadius etc has different values
cornerRadius: number | symbol
cornerSmoothing: number
}
interface ExportMixin {
exportSettings: ExportSettings[]
exportAsync(settings?: ExportSettings): Promise<Uint8Array> // Defaults to PNG format
}
////////////////////////////////////////////////////////////////////////////////
// Nodes
interface DocumentNode extends BaseNodeMixin, ChildrenMixin {
readonly type: "DOCUMENT"
clone(): DocumentNode // Note: this always throws an error
}
interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
readonly type: "PAGE"
clone(): PageNode // cloned node starts off inserted into current page
guides: ReadonlyArray<Guide>
selection: ReadonlyArray<BaseNode>
}
interface FrameNode extends BaseNodeMixin, BlendMixin, ChildrenMixin, FrameMixin, LayoutMixin, ExportMixin {
readonly type: "FRAME" | "GROUP"
clone(): FrameNode // cloned node starts off inserted into current page
}
interface SliceNode extends BaseNodeMixin, LayoutMixin, ExportMixin {
readonly type: "SLICE"
clone(): SliceNode // cloned node starts off inserted into current page
}
interface RectangleNode extends BaseNodeMixin, BlendMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "RECTANGLE"
clone(): RectangleNode // cloned node starts off inserted into current page
topLeftRadius: number
topRightRadius: number
bottomLeftRadius: number
bottomRightRadius: number
}
interface LineNode extends BaseNodeMixin, BlendMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "LINE"
clone(): LineNode // cloned node starts off inserted into current page
}
interface EllipseNode extends BaseNodeMixin, BlendMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "ELLIPSE"
clone(): EllipseNode // cloned node starts off inserted into current page
arcData: ArcData
}
interface PolygonNode extends BaseNodeMixin, BlendMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "POLYGON"
clone(): PolygonNode // cloned node starts off inserted into current page
pointCount: number
}
interface StarNode extends BaseNodeMixin, BlendMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "STAR"
clone(): StarNode // cloned node starts off inserted into current page
pointCount: number
// This is a percentage value from 0 to 1
innerRadius: number
}
interface VectorNode extends BaseNodeMixin, BlendMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "VECTOR"
clone(): VectorNode // cloned node starts off inserted into current page
vectorNetwork: VectorNetwork
vectorPaths: VectorPaths
handleMirroring: HandleMirroring | symbol // This can return figma.mixed if vertices have different handleMirroring values
}
interface TextNode extends BaseNodeMixin, BlendMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "TEXT"
clone(): TextNode // cloned node starts off inserted into current page
characters: string
textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED"
textAlignVertical: "TOP" | "CENTER" | "BOTTOM"
textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT"
paragraphIndent: number
paragraphSpacing: number
autoRename: boolean
// These properties can all return figma.mixed if the text has multiple values for the property
textStyleId: string | symbol
fontSize: number | symbol
fontName: FontName | symbol
textCase: "ORIGINAL" | "UPPER" | "LOWER" | "TITLE" | symbol
textDecoration: "NONE" | "UNDERLINE" | "STRIKETHROUGH" | symbol
letterSpacing: NumberWithUnits | symbol
lineHeight: NumberWithUnits | symbol
}
interface ComponentNode extends BaseNodeMixin, BlendMixin, ChildrenMixin, FrameMixin, LayoutMixin, ExportMixin {
readonly type: "COMPONENT"
clone(): ComponentNode // cloned node starts off inserted into current page
createInstance(): InstanceNode // instance starts off inserted into current page
description: string
readonly remote: boolean
readonly key: string // The key to use with "importComponentByKeyAsync"
}
interface InstanceNode extends BaseNodeMixin, BlendMixin, ChildrenMixin, FrameMixin, LayoutMixin, ExportMixin {
readonly type: "INSTANCE"
clone(): InstanceNode // cloned node starts off inserted into current page
masterComponent: ComponentNode
}
interface BooleanOperationNode extends BaseNodeMixin, BlendMixin, ChildrenMixin, CornerMixin, GeometryMixin, LayoutMixin, ExportMixin {
readonly type: "BOOLEAN_OPERATION"
clone(): BooleanOperationNode // cloned node starts off inserted into current page
booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE"
}
type BaseNode =
DocumentNode |
PageNode |
SliceNode |
FrameNode |
ComponentNode |
InstanceNode |
BooleanOperationNode |
VectorNode |
StarNode |
LineNode |
EllipseNode |
PolygonNode |
RectangleNode |
TextNode
type NodeType =
"DOCUMENT" |
"PAGE" |
"SLICE" |
"FRAME" |
"GROUP" |
"COMPONENT" |
"INSTANCE" |
"BOOLEAN_OPERATION" |
"VECTOR" |
"STAR" |
"LINE" |
"ELLIPSE" |
"POLYGON" |
"RECTANGLE" |
"TEXT"
////////////////////////////////////////////////////////////////////////////////
// Styles
type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID"
interface BaseStyle {
// The string to uniquely identify a style by
readonly id: string
readonly type: StyleType
name: string // Note: setting this also sets "autoRename" to false on TextNodes
description: string
remote: boolean
readonly key: string // The key to use with "importStyleByKeyAsync"
remove(): void
}
interface PaintStyle extends BaseStyle {
type: "PAINT"
paints: ReadonlyArray<Paint>
}
interface TextStyle extends BaseStyle {
type: "TEXT"
fontSize: number
textDecoration: "NONE" | "UNDERLINE" | "STRIKETHROUGH"
fontName: FontName
letterSpacing: NumberWithUnits
lineHeight: NumberWithUnits
paragraphIndent: number
paragraphSpacing: number
textCase: "ORIGINAL" | "UPPER" | "LOWER" | "TITLE"
}
interface EffectStyle extends BaseStyle {
type: "EFFECT"
effects: ReadonlyArray<Paint>
}
interface GridStyle extends BaseStyle {
type: "GRID"
layoutGrids: ReadonlyArray<LayoutGrid>
}
////////////////////////////////////////////////////////////////////////////////
// Other
interface Image {
// Returns a unique hash for the image
readonly hash: string
// The contents of the image file
getBytesAsync(): Promise<Uint8Array>
}

View File

@@ -2,7 +2,16 @@
"name": "Lucide Icons",
"id": "939567362549682242",
"api": "1.0.0",
"ui": "build/ui.html",
"main": "build/main.js",
"editorType": ["figma"]
"editorType": ["figma"],
"main": "dist/assets/main.js",
"ui": {
"interface": "dist/src/interface/interface.html",
"worker": "dist/src/worker/worker.html"
},
"parameterOnly": false,
"parameters": [{
"name": "Icon",
"key": "icon-name",
"description": "Enter the name of the icon you want to insert."
}]
}

View File

@@ -1,27 +1,30 @@
{
"name": "lucide-figma",
"version": "0.15.11",
"license": "ISC",
"private": true,
"main": "build/ui.js",
"license": "ISC",
"main": "src/main.js",
"scripts": {
"build": "webpack --mode=production",
"watch": "webpack --mode=development --watch"
"dev": "vite",
"build": "tsc && rm -rf dist && yarn build:main && yarn build:worker && yarn build:interface",
"build:main": "INPUT=main vite build",
"build:worker": "INPUT=worker vite build",
"build:interface": "INPUT=interface vite build",
"serve": "vite preview"
},
"dependencies": {
"@emotion/core": "^10.0.14",
"@types/react": "^16.8.23",
"@types/react-dom": "^16.8.4",
"css-loader": "^3.0.0",
"html-webpack-inline-source-plugin": "^0.0.10",
"html-webpack-plugin": "^3.2.0",
"lucide-react": "0.16.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.4",
"typescript": "^3.5.2",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.5"
"minimist": "^1.2.5",
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"@figma/plugin-typings": "^1.36.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react": "^1.0.0",
"typescript": "^4.3.2",
"vite": "^2.6.4",
"vite-plugin-singlefile": "^0.5.1"
}
}

View File

@@ -0,0 +1,68 @@
import iconNodeToSvg from "../helpers/iconNodeToSvg"
export type IconNode = any[]
export type IconName = string
export type Tag = string[]
export interface Tags {
[key:string]: Tag
}
export interface LucideIcons {
version: string
iconNodes: { [key: IconName]: IconNode }
tags: Tags,
svgs: { [key: IconName]: string }
}
export const fetchIcons = async (cachedIcons? : LucideIcons): Promise<LucideIcons> => {
const response = await fetch('https://unpkg.com/lucide-static@latest/package.json')
const packageJson = await response.json();
if(cachedIcons && cachedIcons?.version === packageJson.version) {
return cachedIcons
}
const iconNodesResponse = await fetch(`https://unpkg.com/lucide-static@${packageJson.version}/icon-nodes.json`)
const tagsResponse = await fetch('https://unpkg.com/lucide-static@latest/tags.json')
const iconNodes = await iconNodesResponse.json();
const tags = await tagsResponse.json();
const svgs = Object.keys(iconNodes).reduce((acc : { [key:string]: string}, iconName) => {
acc[iconName] = iconNodeToSvg(iconName, iconNodes[iconName])
return acc
}, {})
const lucideIcons: LucideIcons = {
version: packageJson.version,
tags,
iconNodes,
svgs
}
parent.postMessage({
pluginMessage: {
type: "setCachedIcons",
lucideIcons
}
}, "*")
return lucideIcons
}
export const getIcons = () => new Promise<LucideIcons>(async (resolve, reject)=> {
parent.postMessage({
pluginMessage: {
type: "getCachedIcons",
}
}, "*")
window.onmessage = async (event) => {
if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') {
const lucideIcons = await fetchIcons(event?.data?.pluginMessage?.cachedIcons)
resolve(lucideIcons)
}
}
});

View File

@@ -0,0 +1,16 @@
.icon-button {
padding: 8px;
color: var(--color-black);
background: transparent;
border: 0;
border-radius: 2px;
appearance: none;
outline: 0;
&:hover {
background: rgba(0, 0, 0, 0.06);
}
&:focus,
&:active {
box-shadow: inset 0 0 0 2px var(--color-blue);
}
}

View File

@@ -0,0 +1,32 @@
import { renderToString } from 'react-dom/server'
import { FC } from 'react';
import './IconButton.scss'
interface IconButtonProps {
name: string,
component: FC,
}
function IconButton({ name, component: IconComponent }: IconButtonProps) {
const onIconClick = () => {
const svg = renderToString(<IconComponent/>);
parent.postMessage({ pluginMessage: {
type: 'drawIcon',
icon: { name, svg }
}}, '*')
}
return (
<button
key={name}
aria-label={name}
onClick={onIconClick}
className='icon-button'
>
<IconComponent />
</button>
)
}
export default IconButton

View File

@@ -0,0 +1 @@
export { default } from './IconButton'

View File

@@ -0,0 +1,17 @@
.search-input {
.icon {
position: absolute;
top: 0;
left: 0;
padding: 4px;
}
input {
width: 100%;
height: 40px;
padding: 0 16px 0 36px;
font-family: inherit;
font-size: 11px;
border: 0;
outline: 0;
}
}

View File

@@ -0,0 +1,30 @@
import "./SearchInput.scss"
import { ChangeEvent } from "react"
import SearchIcon from "../icons/SearchIcon"
interface SearchInputProps extends React.HTMLProps<HTMLDivElement> {
value: string,
iconCount: number,
onChange: (event: ChangeEvent<HTMLInputElement>) => void
}
function SearchInput({ value, onChange, iconCount, className, ...props }: SearchInputProps) {
return (
<div
className="search-input"
{...props}
>
<SearchIcon className='icon'/>
<input
autoFocus
type="search"
value={value}
onChange={onChange}
placeholder={`Search ${iconCount} icons`}
className="input__field"
/>
</div>
)
}
export default SearchInput

View File

@@ -0,0 +1 @@
export { default } from './SearchInput'

View File

@@ -1,44 +0,0 @@
import { jsx } from '@emotion/core'
import theme from '../theme'
import { renderToString } from 'react-dom/server'
import { FC } from 'react';
interface IconButtonProps {
name: string,
component: FC,
}
function IconButton({ name, component: IconComponent }: IconButtonProps) {
const onIconclick = () => {
const svg = renderToString(<IconComponent/>);
parent.postMessage({ pluginMessage: { name, svg }}, '*')
}
return (
<button
key={name}
aria-label={name}
onClick={onIconclick}
css={{
padding: theme.space[2],
color: '#333',
background: 'transparent',
border: 0,
borderRadius: theme.radii[1],
appearance: 'none',
outline: 0,
'&:hover': {
background: 'rgba(0, 0, 0, 0.06)',
},
'&:focus, &:active': {
boxShadow: `inset 0 0 0 2px ${theme.colors.blue}`,
},
}}
>
<IconComponent />
</button>
)
}
export default IconButton

View File

@@ -1,43 +0,0 @@
import { jsx } from '@emotion/core'
import theme from '../theme'
import SearchIcon from './search-icon'
interface SearchInputProps extends React.HTMLProps<HTMLDivElement> {
value: string,
iconCount: number,
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
}
function SearchInput({ value, onChange, iconCount, ...props }: SearchInputProps) {
return (
<div css={{ position: 'relative' }} {...props}>
<div
css={{
position: 'absolute',
top: 0,
left: 0,
padding: theme.space[1],
}}
>
<SearchIcon fill="#333" />
</div>
<input
autoFocus
type="search"
value={value}
onChange={onChange}
placeholder={`Search ${iconCount} icons`}
css={{
width: '100%',
height: 40,
padding: `0 ${theme.space[4]} 0 36px`,
fontFamily: 'inherit',
fontSize: theme.fontSizes[0],
border: 0,
outline: 0,
}}
/>
</div>
)
}
export default SearchInput

View File

@@ -0,0 +1,13 @@
import { Tags } from "../api/fetchIcons";
import { Icon } from "../hooks/useSearch";
export default (icons: Icon[], tags: Tags ,query:string) =>
icons.filter(([name]: Icon) => {
const iconTags = tags && tags[name] ? tags[name] : []
return [name, ...iconTags].some(
(item:string) => item
.toLowerCase()
.includes(query)
)
})

View File

@@ -0,0 +1,11 @@
import { createReactComponent } from "lucide-react";
import { createElement } from "react";
import { renderToString } from "react-dom/server";
import { IconNode } from "../api/fetchIcons";
const iconNodeToSvg = (iconName: string, iconNode : IconNode) => {
const IconComponent = createReactComponent(iconName, iconNode)
return renderToString(createElement(IconComponent));
}
export default iconNodeToSvg

View File

@@ -0,0 +1,17 @@
import { IconName, IconNode, Tags } from "../api/fetchIcons";
import filterIcons from "../helpers/filterIcons";
export type Icon = [
name: IconName,
iconNode: IconNode
]
function useSearch(icons: Icon[], tags: Tags ,query: string) {
if(!query) return icons;
const searchString = query.toLowerCase()
return filterIcons(icons, tags, searchString);
}
export default useSearch;

View File

@@ -0,0 +1 @@
<svg fill="none" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m18.8744 19.5815c-1.0453.8849-2.3975 1.4185-3.8744 1.4185-3.3137 0-6-2.6863-6-6s2.6863-6 6-6 6 2.6863 6 6c0 1.4769-.5336 2.8291-1.4185 3.8744l4.2721 4.272-.7072.7072zm1.1256-4.5815c0 2.7614-2.2386 5-5 5s-5-2.2386-5-5 2.2386-5 5-5 5 2.2386 5 5z" fill="#000" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1 @@
<svg fill="none" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m18.3972 18.6046c-.7793.625-1.7687.9988-2.8455.9988-2.5138 0-4.5517-2.0378-4.5517-4.5517 0-2.5138 2.0379-4.5517 4.5517-4.5517 2.5139 0 4.5517 2.0379 4.5517 4.5517 0 1.0769-.3739 2.0664-.999 2.8458l3.2491 3.2492-.7071.7071zm.7062-3.5529c0 1.9616-1.5901 3.5517-3.5517 3.5517-1.9615 0-3.5517-1.5901-3.5517-3.5517 0-1.9615 1.5902-3.5517 3.5517-3.5517 1.9616 0 3.5517 1.5902 3.5517 3.5517z" fill="#000" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@@ -0,0 +1,3 @@
<script type="module" src="./interface.tsx"></script>
<div id="root"></div>

View File

@@ -0,0 +1,49 @@
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.9')
format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.9')
format('woff');
}
:root {
--color-blue: #18a0fb;
--color-black: #333;
}
body {
font-family: Inter, sans-serif;
font-size: 11px;
margin: 0;
}
.search-input {
position: sticky;
top: 0;
border-bottom: 1px solid #e5e5e5;
backface-visibility: hidden;
}
main {
padding-bottom: 8px;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-gap: 8px;
}
footer {
margin-top: 8px;
padding: 8px;
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
.footer-link {
color: inherit;
}
}

View File

@@ -0,0 +1,67 @@
import { useEffect, useMemo, useState } from 'react'
import { createReactComponent } from 'lucide-react'
import ReactDOM from 'react-dom'
import IconButton from '../components/IconButton'
import SearchInput from '../components/SearchInput'
import useSearch, { Icon } from '../hooks/useSearch'
import { getIcons } from '../api/fetchIcons'
import './interface.scss'
function App() {
const [query, setQuery] = useState('')
const [icons, setIcons] = useState<Icon[]>([])
const [tags, setTags] = useState({})
const [version, setVersion ] = useState('')
const searchResults = useMemo(() => useSearch(icons, tags, query), [icons, query])
const getLatestIcons = async () => {
const lucideIcons = await getIcons()
setIcons(Object.entries(lucideIcons.iconNodes))
setTags(lucideIcons.tags)
setVersion(lucideIcons.version)
}
useEffect(() => {
getLatestIcons()
}, [])
if(!icons.length) {
return null
}
return (
<div>
<SearchInput
value={query}
iconCount={icons.length}
onChange={(event) => setQuery(event.target.value)}
/>
<main>
<div className='icon-grid'>
{searchResults.map(([name, iconNode] :any) => (
<IconButton
name={name}
key={name}
component={createReactComponent(name, iconNode)}
/>
))}
</div>
<footer>
<a
href="https://lucide.dev"
target="_blank"
className='footer-link'
>
Lucide v{version}
</a>
</footer>
</main>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))

View File

@@ -1,9 +1,97 @@
figma.showUI(__html__, { width: 300, height: 400 })
import type { LucideIcons } from "./api/fetchIcons";
import filterIcons from "./helpers/filterIcons";
figma.ui.onmessage = ({name, svg}) => {
const icon = figma.createNodeFromSvg(svg)
icon.name = name
icon.x = figma.viewport.center.x
icon.y = figma.viewport.center.y
figma.currentPage.selection = [icon]
figma.showUI(__uiFiles__.worker, { visible: false })
let cachedIcons: LucideIcons
const setResults = ({result, query, lucideIcons} : { result: SuggestionResults, query: string, lucideIcons: LucideIcons }) => {
const icons = Object.entries(lucideIcons.iconNodes);
const suggestions = filterIcons(icons, lucideIcons.tags, query.toLowerCase()).map(([name]) => ({
name,
icon: lucideIcons.svgs[name]
}))
result.setSuggestions(suggestions)
}
figma.parameters.on('input', async ({ parameters, key, query, result }) => {
if (key === 'icon-name') {
console.log('typ tpy', query);
cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`)
console.log('cachedIcons', cachedIcons);
if(cachedIcons && cachedIcons.iconNodes && cachedIcons.tags) {
setResults({result, query, lucideIcons: cachedIcons})
}
}
})
const drawIcon = ({icon: {name, svg}}: any) => {
const min = 0
const max = 100
const randomPosition = () => Math.floor(Math.random() * (max - min + 1) + min)
const icon = figma.createNodeFromSvg(svg)
icon.name = name
icon.x = Math.round(figma.viewport.center.x + randomPosition())
icon.y = Math.round(figma.viewport.center.y + randomPosition())
figma.currentPage.selection = [icon]
// lock children
icon.children.forEach((vectorNode, key) => {
icon.children[key].locked = true
});
}
const setCachedIcons = async (pluginMessage: any) => {
if(pluginMessage.lucideIcons) {
await figma.clientStorage.setAsync(`lucide-icons`, pluginMessage.lucideIcons)
}
}
const getCachedIcons = async () => {
cachedIcons = await figma.clientStorage.getAsync(`lucide-icons`)
const response = { type: 'cachedIcons' }
if(cachedIcons) {
Object.assign(response, { cachedIcons })
}
figma.ui.postMessage(response)
}
getCachedIcons()
figma.ui.onmessage = (event) => {
console.log(event, 'main');
switch (event.type) {
case "drawIcon":
drawIcon(event)
break;
case "getCachedIcons":
getCachedIcons()
break;
case "setCachedIcons":
setCachedIcons(event)
break;
case "close":
figma.closePlugin()
break;
default:
break;
}
}
figma.on('run', event => {
if(event.parameters) {
figma.ui.postMessage({ type: 'getSvg', iconName: event.parameters['icon-name'], cachedIcons })
} else {
figma.showUI(__uiFiles__.interface, { width: 300, height: 400 })
}
})

View File

@@ -1,4 +0,0 @@
declare module 'lucide-react';
declare module 'lucide';
declare module 'lucide/icons';
declare module 'lucide/build/icons';

View File

@@ -1,10 +0,0 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.9')
format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.9')
format('woff');
}

View File

@@ -1 +0,0 @@
<div id="root"></div>

View File

@@ -1,82 +0,0 @@
import { Global, jsx } from '@emotion/core'
import { version } from '../package.json'
import React, { useMemo } from 'react'
import ReactDOM from 'react-dom'
import IconButton from './components/icon-button'
import SearchInput from './components/search-input'
import theme from './theme'
import './ui.css'
import tags from '../../../tags.json'
import * as iconComponents from 'lucide-react'
import { toPascalCase } from './helpers/naming';
import useSearch from '../../../site/src/lib/useSearch';
declare var ICONS: [];
function App() {
const [query, setQuery] = React.useState('')
const icons = ICONS.map(name => {
const componentName = toPascalCase(name);
return {
name,
tags: tags[name] || [],
component: iconComponents[componentName] || null
}
}).filter(({component}) => !!component)
const searchResults = useMemo(() => useSearch(icons, query), [icons, query])
return (
<div>
<Global
styles={{ body: { margin: 0, fontFamily: 'Inter, sans-serif' } }}
/>
<SearchInput
value={query}
iconCount={icons.length}
onChange={event => setQuery(event.target.value)}
css={{
position: 'sticky',
top: 0,
borderBottom: '1px solid #e5e5e5',
backfaceVisibility: 'hidden'
}}
/>
<div css={{ padding: theme.space[2] }}>
<div
css={{
display: 'grid',
gridTemplateColumns: 'repeat(6, 1fr)',
gridGap: theme.space[1],
}}
>
{searchResults.map(({name, component: Icon} :any) => (
<IconButton
name={name}
key={name}
component={Icon}
/>
))}
</div>
<div
css={{
marginTop: theme.space[2],
padding: theme.space[2],
fontSize: theme.fontSizes[0],
color: 'rgba(0, 0, 0, 0.5)',
}}
>
<a
href="https://lucide.dev"
target="_blank"
css={{ color: 'inherit' }}
>
Lucide v{version}
</a>
</div>
</div>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))

View File

@@ -0,0 +1 @@
<script type="module" src="./worker.ts"></script>

View File

@@ -0,0 +1,60 @@
import { fetchIcons, LucideIcons } from "../api/fetchIcons"
import { createReactComponent } from 'lucide-react'
import { renderToString } from 'react-dom/server'
import { createElement } from "react"
const getLatestIcons = async ({ cachedIcons }: any) => {
const lucideIcons = await fetchIcons(cachedIcons)
parent.postMessage({
pluginMessage: {
type: "latestIcons",
lucideIcons,
}
}, "*")
}
const getSvg = async ({ cachedIcons, iconName }: { cachedIcons: LucideIcons, iconName: string }) => {
if (!cachedIcons) {
return;
}
const iconNode = cachedIcons.iconNodes[iconName];
if (iconNode) {
const IconComponent = createReactComponent(iconName, iconNode)
const svg = renderToString(createElement(IconComponent));
parent.postMessage({ pluginMessage: {
type: 'drawIcon',
icon: { name, svg }
}}, '*')
parent.postMessage({ pluginMessage: {
type: 'close',
}}, '*')
}
}
window.onmessage = async (event) => {
if (!event?.data?.pluginMessage) {
return
}
const { pluginMessage } = event.data
switch (pluginMessage.type) {
case "getLatestIcons":
getLatestIcons(pluginMessage)
break;
case "getSvg":
getSvg(pluginMessage)
break;
default:
break;
}
}
console.log('Hello world!')

View File

@@ -1,12 +1,24 @@
{
"compilerOptions": {
"esModuleInterop": true,
"lib": ["dom", "esnext"],
"jsx": "react",
"jsxFactory": "jsx",
"resolveJsonModule": true,
"strict": true,
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": false,
"noEmit": true,
"jsx": "react-jsx",
"typeRoots": [
"./node_modules/@types",
"../../node_modules/@figma"
],
},
"exclude": ["node_modules"],
"include": ["src"]
}

View File

@@ -0,0 +1,40 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteSingleFile } from "vite-plugin-singlefile"
import { resolve } from 'path'
const entries = {
main: resolve(__dirname, 'src/main.ts'),
interface: resolve(__dirname, './src/interface/interface.html'),
worker: resolve(__dirname, './src/worker/worker.html'),
}
const input = {};
if(process.env['INPUT']) {
const entry = process.env['INPUT'];
input[entry] = entries[entry]
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), viteSingleFile()],
build: {
target: "esnext",
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
cssCodeSplit: false,
brotliSize: false,
emptyOutDir: false,
rollupOptions: {
input,
inlineDynamicImports: true,
output: {
manualChunks: (chunk) => "all.js",
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
},
},
},
})

View File

@@ -1,48 +0,0 @@
const fs = require('fs');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const webpack = require('webpack');
module.exports = (env, argv) => ({
// This is necessary because Figma's 'eval' works differently than normal eval
devtool: argv.mode === 'production' ? false : 'inline-source-map',
entry: {
ui: './src/ui.tsx',
main: './src/main.ts',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new webpack.DefinePlugin({
ICONS: JSON.stringify(
fs.readdirSync(path.join(process.cwd(), '../../icons')).map(name => name.split('.')[0]),
),
}),
new HtmlWebpackPlugin({
template: './src/ui.html',
filename: 'ui.html',
inlineSource: '.(js)$',
chunks: ['ui'],
}),
new HtmlWebpackInlineSourcePlugin(),
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,16 @@
import path from "path";
import {appendFile, readSvgDirectory, resetFile, toPascalCase, writeFile} from "../../../scripts/helpers";
import path from 'path';
import {
appendFile,
readSvgDirectory,
resetFile,
toPascalCase,
writeFile,
} from '../../../scripts/helpers';
const srcDirectory=path.join(__dirname, "../dist");
const srcDirectory = path.join(__dirname, '../dist');
// Declare type definitions
const typeDefinitions=`\
const typeDefinitions = `\
/// <reference types="react" />
import { SVGAttributes } from 'react'
@@ -15,6 +21,8 @@ export interface LucideProps extends Partial<React.SVGProps<SVGSVGElement>> {
size?: string | number
}
export declare const createReactComponent: (iconName: string, iconNode: any[]) => (props: LucideProps) => JSX.Element;
export type Icon = React.FC<LucideProps>;
// Generated icons

View File

@@ -1 +1,2 @@
export * from './icons';
export { default as createReactComponent } from './createReactComponent';

View File

@@ -10,9 +10,11 @@ const TYPES_FILE_NAME = 'lucide.d.ts';
// Generates header of d.ts file include some types and functions
const typeDefinitions = `\
declare module 'lucide'
export interface SVGProps extends Partial<SVGElement> ${JSON.stringify(defaultAttributes, null, 2)}
export declare type IconNode = readonly [tag: string, attrs:SVGProps, children?:IconNode];
export declare type IconNodeChild = readonly [string, object];
export declare type IconNode = readonly [tag: string, attrs: SVGProps, children?: IconNodeChild[]];
export declare type CustomAttrs = { [attr:string]: any }
export type Icons = { [key: string]: IconNode }

View File

@@ -2074,6 +2074,12 @@
"air",
"blow"
],
"wrap-text": [
"words",
"lines",
"break",
"paragraph"
],
"wrench": [
"tool",
"settings",

4815
yarn.lock

File diff suppressed because it is too large Load Diff