mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-15 14:08:49 +00:00
Improve Loading in figma plugin (#866)
* Add skeleton * improve icon loading
This commit is contained in:
@@ -66,3 +66,24 @@ export const getIcons = () => new Promise<LucideIcons>(async (resolve, reject)=>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
type EventCallback = (lucideIcons: LucideIcons) => void
|
||||
|
||||
export const iconFetchListener = (callback: EventCallback) => {
|
||||
fetchIcons()
|
||||
|
||||
const handleEvent = (event: MessageEvent) => {
|
||||
if (event.type === 'message' && event?.data?.pluginMessage.type === 'cachedIcons') {
|
||||
|
||||
const lucideIcons = event?.data?.pluginMessage?.cachedIcons
|
||||
callback(lucideIcons)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleEvent)
|
||||
|
||||
const removeListener = () => {
|
||||
window.removeEventListener('message', handleEvent)
|
||||
}
|
||||
return removeListener
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
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);
|
||||
|
||||
@@ -6,9 +6,10 @@ interface SearchInputProps extends React.HTMLProps<HTMLDivElement> {
|
||||
value: string,
|
||||
iconCount: number,
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
function SearchInput({ value, onChange, iconCount, className, ...props }: SearchInputProps) {
|
||||
function SearchInput({ value, onChange, placeholder, className, ...props }: SearchInputProps) {
|
||||
return (
|
||||
<div
|
||||
className="search-input"
|
||||
@@ -20,7 +21,7 @@ function SearchInput({ value, onChange, iconCount, className, ...props }: Search
|
||||
type="search"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={`Search ${iconCount} icons`}
|
||||
placeholder={placeholder}
|
||||
className="input__field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
@keyframes load {
|
||||
to {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
--block: rgba(0, 0, 0, 0.06);
|
||||
--loader: hsl(0 0% 89%);
|
||||
|
||||
background: linear-gradient(
|
||||
-75deg,
|
||||
transparent 40%,
|
||||
var(--loader),
|
||||
transparent 60%
|
||||
) 0 0 / 200% 100%,
|
||||
var(--block);
|
||||
|
||||
border-radius: calc(var(--padding) * 0.5);
|
||||
animation: load 2s infinite linear;
|
||||
background-attachment: fixed;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import './Skeleton.scss'
|
||||
|
||||
const Skeleton = () => {
|
||||
return (
|
||||
<>
|
||||
{Array.from({length: 48 }, () => (
|
||||
<div className="skeleton"/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Skeleton
|
||||
@@ -6,7 +6,7 @@ type Views = typeof views
|
||||
|
||||
import useSearch, { Icon } from '../hooks/useSearch'
|
||||
|
||||
import { getIcons } from '../api/fetchIcons'
|
||||
import { getIcons, iconFetchListener, LucideIcons } from '../api/fetchIcons'
|
||||
import './interface.scss'
|
||||
import Menu from '../components/Menu'
|
||||
|
||||
@@ -19,21 +19,19 @@ function App() {
|
||||
|
||||
const searchResults = useMemo(() => useSearch(icons, tags, query), [icons, query])
|
||||
|
||||
const getLatestIcons = async () => {
|
||||
const lucideIcons = await getIcons()
|
||||
const handleFetchResponse = async (lucideIcons: LucideIcons) => {
|
||||
const icons = Object.entries(lucideIcons.iconNodes)
|
||||
|
||||
setIcons(Object.entries(lucideIcons.iconNodes))
|
||||
setIcons(icons)
|
||||
setTags(lucideIcons.tags)
|
||||
setVersion(lucideIcons.version)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getLatestIcons()
|
||||
}, [])
|
||||
const removeListener = iconFetchListener(handleFetchResponse)
|
||||
|
||||
if(!icons.length) {
|
||||
return null
|
||||
}
|
||||
return removeListener
|
||||
}, [])
|
||||
|
||||
const View = views?.[page as keyof Views] ?? views.icons
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import IconButton from '../components/IconButton'
|
||||
import SearchInput from '../components/SearchInput'
|
||||
import createIconComponent from '../helpers/createIconComponent'
|
||||
import { Icon } from '../hooks/useSearch'
|
||||
import Skeleton from '../components/Skeleton/Skeleton'
|
||||
|
||||
interface PageProps {
|
||||
query: string
|
||||
@@ -24,16 +25,22 @@ const Icons = ({
|
||||
value={query}
|
||||
iconCount={icons.length}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={icons.length ? `Search ${icons.length} icons`: 'Loading icons ..'}
|
||||
/>
|
||||
<main>
|
||||
<div className='icon-grid'>
|
||||
{searchResults.map(([name, iconNode] :any) => (
|
||||
<IconButton
|
||||
name={name}
|
||||
key={name}
|
||||
component={createIconComponent(name, iconNode)}
|
||||
/>
|
||||
))}
|
||||
{icons.length ? (
|
||||
searchResults.map(([name, iconNode]: any) => (
|
||||
<IconButton
|
||||
name={name}
|
||||
key={name}
|
||||
component={createIconComponent(name, iconNode)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
|
||||
</div>
|
||||
<footer>
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user