Compare commits

...

11 Commits

Author SHA1 Message Date
Eric Fennis
95a7d8648f v0.14.1 2021-02-27 11:31:24 +01:00
Eric Fennis
b07eb8c00d Merge branch 'master' of github.com:lucide-icons/lucide into hotfix/fix-react-component 2021-02-27 11:23:36 +01:00
Eric Fennis
40cb396a6c Fix proptypes 2021-02-27 11:21:35 +01:00
Eric Fennis
87ab0bfb62 Figma plugin! (#227)
* initialize figma plugin

* Add icons to view

* Make the plugin work

* Fix search hook

* Finialize figma plugin

* Finish up icons

* remove unused code

* Add cover and icon
2021-02-26 16:35:18 +01:00
Yashu Mittal
c4f50417d5 maintain 1px border spacing in languages icon (#232) 2021-02-26 13:32:07 +01:00
Lucide Bot
0ca1b98689 📦 Bump version to 0.14.0 2021-02-22 19:46:47 +00:00
Eric Fennis
7ce35bac34 Merge branch 'master' of github.com:ericfennis/featherity 2021-02-22 20:36:44 +01:00
Eric Fennis
5ec34cb249 Merge branch 'master' of github.com:lucide-icons/lucide 2021-02-22 20:32:02 +01:00
Eric Fennis
313b46ecc1 update tags 2021-02-08 22:17:56 +01:00
Eric Fennis
367f89ef1f add tags 2021-02-08 21:55:48 +01:00
Eric Fennis
32ba19d591 Add script to add more tags 2021-02-08 21:18:43 +01:00
26 changed files with 4664 additions and 20 deletions

View File

@@ -9,10 +9,10 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="5" y1="8" x2="11" y2="14" />
<path d="M5 8l6 6" />
<path d="M4 14l6-6 2-3" />
<line x1="2" y1="5" x2="14" y2="5" />
<line x1="7" y1="2" x2="8" y2="2" />
<path d="M22 21l-5-10-5 10" />
<line x1="14" y1="17" x2="20" y2="17" />
<path d="M2 5h12" />
<path d="M7 2h1" />
<path d="M22 22l-5-10-5 10" />
<path d="M14 18h6" />
</svg>

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 363 B

View File

@@ -1,7 +1,7 @@
{
"name": "lucide",
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
"version": "0.13.0",
"version": "0.14.0",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -0,0 +1,25 @@
# Lucide Figma plugin
A Figma plugin for using Lucide Icons
## Local development
1. Install the dependencies
```sh
yarn
```
2. Build the plugin
```sh
yarn watch
```
3. Open the [Figma desktop app](https://www.figma.com/downloads/)
4. Go to `Menu > Plugins > Development > New Plugin...`
5. Choose `lucide/packages/lucide-figma/manifest.json`
6. Run the plugin by going to `Menu > Plugins > Development > Lucide Icons`

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

675
packages/lucide-figma/figma.d.ts vendored Normal file
View File

@@ -0,0 +1,675 @@
// 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>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,7 @@
{
"name": "Lucide Icons",
"id": "939567362549682242",
"api": "1.0.0",
"ui": "build/ui.html",
"main": "build/main.js"
}

View File

@@ -0,0 +1,26 @@
{
"name": "lucide-figma",
"version": "0.13.0",
"license": "ISC",
"main": "build/ui.js",
"scripts": {
"build": "webpack --mode=production",
"watch": "webpack --mode=development --watch"
},
"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.13.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"
}
}

View File

@@ -0,0 +1,44 @@
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

@@ -0,0 +1,24 @@
import { createElement, forwardRef } from 'react'
const SearchIcon = forwardRef((props: any, ref) => createElement(
'svg',
{
xmlns: "http://www.w3.org/2000/svg",
width: 32,
height: 32,
clipRule: 'evenodd',
fillRule: 'evenodd',
ref,
...props,
},
[
createElement(
'path', {
d: 'm20 15c0 2.7614-2.2386 5-5 5s-5-2.2386-5-5 2.2386-5 5-5 5 2.2386 5 5zm-1.1256 4.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.7072z',
key: 'path'
}
)
]
))
export default SearchIcon

View File

@@ -0,0 +1,43 @@
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,21 @@
/**
* Converts string to camelcase
*
* @param {string} string
*/
export const toCamelCase = (string: string) =>
string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) =>
p2 ? p2.toUpperCase() : p1.toLowerCase(),
);
/**
* Converts string to PascalCase
*
* @param {string} string
*/
export const toPascalCase = (string: string) => {
const camelCase = toCamelCase(string);
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
};

View File

@@ -0,0 +1,9 @@
figma.showUI(__html__, { width: 300, height: 400 })
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]
}

View File

@@ -0,0 +1,8 @@
export default {
space: [0, 4, 8, 12, 16],
fontSizes: [12, 14, 16],
colors: {
blue: '#18a0fb',
},
radii: [0, 2],
}

View File

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

View File

@@ -0,0 +1,10 @@
@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

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

View File

@@ -0,0 +1,82 @@
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,12 @@
{
"compilerOptions": {
"esModuleInterop": true,
"lib": ["dom", "esnext"],
"jsx": "react",
"jsxFactory": "jsx",
"resolveJsonModule": true,
"strict": true,
"allowJs": true,
},
"exclude": ["node_modules"],
}

View File

@@ -0,0 +1,48 @@
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,7 +1,7 @@
{
"name": "lucide-react",
"description": "Lucide React package, Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
"version": "0.13.0",
"version": "0.14.1",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",

View File

@@ -22,7 +22,7 @@ export default (iconName, [tag, attrs, children]) => {
Component.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
strokeWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
Component.displayName = `${iconName}`;

View File

@@ -1,6 +1,6 @@
{
"name": "lucide-vue",
"version": "0.13.0-beta.1",
"version": "0.14.0",
"author": "Eric Fennis",
"description": "Lucide Vue Package",
"license": "ISC",

View File

@@ -22,6 +22,13 @@ export const toPascalCase = string => {
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
};
/**
* Converts string to PascalCase
*
* @param {string} string
*/
export const toKebabCase = string => string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
/**
* Resets the file contents.
*

View File

@@ -1,20 +1,19 @@
import { useEffect, useMemo, useState } from 'react';
import { useDebounce } from './useDebounce';
interface Icon {
name: string;
tags: string[],
}
function useSearch(icons: Array<any>, query:string) {
function useSearch(icons: Icon[], query:string) {
if(!query) return icons;
const searchString = query.toLowerCase()
return icons.filter(({ name, tags }) => {
const icon = { name, tags };
return Object.keys(icon).some(
key => String(icon[key])
.toLowerCase()
.includes(searchString)
return icons.filter(({ name, tags }: Icon) => [name, ...tags].some(
(item:string) => item
.toLowerCase()
.includes(searchString)
)
});
);
}
export default useSearch;