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
This commit is contained in:
44
packages/lucide-figma/src/components/icon-button.tsx
Normal file
44
packages/lucide-figma/src/components/icon-button.tsx
Normal 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
|
||||
24
packages/lucide-figma/src/components/search-icon.tsx
Normal file
24
packages/lucide-figma/src/components/search-icon.tsx
Normal 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
|
||||
43
packages/lucide-figma/src/components/search-input.tsx
Normal file
43
packages/lucide-figma/src/components/search-input.tsx
Normal 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
|
||||
21
packages/lucide-figma/src/helpers/naming.ts
Normal file
21
packages/lucide-figma/src/helpers/naming.ts
Normal 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);
|
||||
};
|
||||
9
packages/lucide-figma/src/main.ts
Normal file
9
packages/lucide-figma/src/main.ts
Normal 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]
|
||||
}
|
||||
8
packages/lucide-figma/src/theme.ts
Normal file
8
packages/lucide-figma/src/theme.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
space: [0, 4, 8, 12, 16],
|
||||
fontSizes: [12, 14, 16],
|
||||
colors: {
|
||||
blue: '#18a0fb',
|
||||
},
|
||||
radii: [0, 2],
|
||||
}
|
||||
4
packages/lucide-figma/src/types/lucide-react.ts
Normal file
4
packages/lucide-figma/src/types/lucide-react.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module 'lucide-react';
|
||||
declare module 'lucide';
|
||||
declare module 'lucide/icons';
|
||||
declare module 'lucide/build/icons';
|
||||
10
packages/lucide-figma/src/ui.css
Normal file
10
packages/lucide-figma/src/ui.css
Normal 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');
|
||||
}
|
||||
1
packages/lucide-figma/src/ui.html
Normal file
1
packages/lucide-figma/src/ui.html
Normal file
@@ -0,0 +1 @@
|
||||
<div id="root"></div>
|
||||
82
packages/lucide-figma/src/ui.tsx
Normal file
82
packages/lucide-figma/src/ui.tsx
Normal 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'))
|
||||
Reference in New Issue
Block a user