mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-12 13:38:32 +00:00
Small fixes site (#1408)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {useFloating, offset, shift} from '@floating-ui/vue';
|
||||
|
||||
const reference = ref(null);
|
||||
const tooltip = ref(null);
|
||||
const middleware = ref([shift(), offset(8)]);
|
||||
const { floatingStyles, update } = useFloating(reference, tooltip, {
|
||||
middleware,
|
||||
transform: false
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
update()
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="reference" class="reference">
|
||||
<slot/>
|
||||
</span>
|
||||
<div ref="tooltip" class="tooltip" :style="floatingStyles">
|
||||
{{title}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reference:hover + .tooltip{
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
transform: scale(0.9);
|
||||
font-weight: 400;
|
||||
background: var(--vp-c-brand-dark);
|
||||
color: white;
|
||||
z-index: 10;
|
||||
white-space: nowrap;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: cubic-bezier(0.19, 1, 0.22, 1) .2s;
|
||||
transition-property: opacity, transform;
|
||||
}
|
||||
</style>
|
||||
@@ -3,19 +3,9 @@ import { ref } from 'vue';
|
||||
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import getSVGIcon from '../../utils/getSVGIcon';
|
||||
import downloadData from '../../utils/downloadData';
|
||||
|
||||
const allowedAttrs = [
|
||||
'xmlns',
|
||||
'width',
|
||||
'height',
|
||||
'viewBox',
|
||||
'fill',
|
||||
'stroke',
|
||||
'stroke-width',
|
||||
'stroke-linecap',
|
||||
'stroke-linejoin',
|
||||
'class',
|
||||
]
|
||||
const downloadText = 'Download!'
|
||||
const copiedText = 'Copied!'
|
||||
const confettiText = ref(copiedText)
|
||||
@@ -28,24 +18,6 @@ const { size } = useIconStyleContext()
|
||||
|
||||
const { animate, confetti } = useConfetti()
|
||||
|
||||
function getSVGIcon() {
|
||||
const svg = document.querySelector('#previewer svg')
|
||||
if (!svg) return
|
||||
|
||||
const clonedSvg = svg.cloneNode(true) as SVGElement
|
||||
|
||||
// Filter out attributes that are not allowed in SVGs
|
||||
for (const attr of Array.from(clonedSvg.attributes)) {
|
||||
if (!allowedAttrs.includes(attr.name)) {
|
||||
clonedSvg.removeAttribute(attr.name)
|
||||
}
|
||||
}
|
||||
|
||||
const svgString = new XMLSerializer().serializeToString(clonedSvg)
|
||||
|
||||
return svgString
|
||||
}
|
||||
|
||||
function copySVG() {
|
||||
confettiText.value = copiedText
|
||||
const svgString = getSVGIcon()
|
||||
@@ -70,11 +42,7 @@ function downloadSVG() {
|
||||
confettiText.value = downloadText
|
||||
const svgString = getSVGIcon()
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = `${props.name}.svg`;
|
||||
link.href = `data:image/svg+xml;base64,${btoa(svgString)}`
|
||||
link.click();
|
||||
|
||||
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`)
|
||||
confetti()
|
||||
}
|
||||
|
||||
@@ -91,12 +59,7 @@ function downloadPNG() {
|
||||
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
||||
image.onload = function() {
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = `${props.name}.png`;
|
||||
link.href = canvas.toDataURL('image/png')
|
||||
link.click();
|
||||
|
||||
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'))
|
||||
confetti()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,5 +43,6 @@ function setActiveIcon(name: string) {
|
||||
|
||||
.icon {
|
||||
aspect-ratio: 1/1;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { useRouter } from 'vitepress';
|
||||
import getSVGIcon from '../../utils/getSVGIcon';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import Tooltip from '../base/Tooltip.vue';
|
||||
|
||||
const downloadText = 'Download!'
|
||||
const copiedText = 'Copied!'
|
||||
|
||||
export type IconNode = [elementName: string, attrs: Record<string, string>][]
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
// tags: string[];
|
||||
// categories: string[];
|
||||
iconNode: IconNode;
|
||||
active: boolean;
|
||||
customizable?: boolean;
|
||||
@@ -20,40 +24,65 @@ const emit = defineEmits(['setActiveIcon'])
|
||||
|
||||
const { go } = useRouter()
|
||||
const showOverlay = useMediaQuery('(min-width: 860px)');
|
||||
const { animate, confetti, confettiText } = useConfetti()
|
||||
|
||||
|
||||
const icon = createLucideIcon(props.name, props.iconNode)
|
||||
|
||||
function navigateToIcon() {
|
||||
async function navigateToIcon(event) {
|
||||
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault()
|
||||
const svgString = getSVGIcon(event.target.firstChild, {
|
||||
class: `lucide lucide-${props.name}`,
|
||||
})
|
||||
|
||||
await navigator.clipboard.writeText(svgString)
|
||||
|
||||
confettiText.value = copiedText
|
||||
confetti()
|
||||
return
|
||||
}
|
||||
|
||||
if(props.overlayMode && showOverlay.value) {
|
||||
event.preventDefault()
|
||||
window.history.pushState({}, '', `/icons/${props.name}`)
|
||||
emit('setActiveIcon', props.name)
|
||||
}
|
||||
else {
|
||||
event.preventDefault()
|
||||
go(`/icons/${props.name}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="icon-button"
|
||||
@click="navigateToIcon"
|
||||
:class="{ 'active' : active }"
|
||||
:data-title="name"
|
||||
:aria-label="name"
|
||||
:href="`/icons/${props.name}`"
|
||||
>
|
||||
<KeepAlive>
|
||||
<component
|
||||
v-if="!hideIcon"
|
||||
:is="icon"
|
||||
class="lucide-icon"
|
||||
:class="{ customizable }"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</button>
|
||||
<Tooltip :title="name">
|
||||
<a
|
||||
class="icon-button confetti-button vp-raw"
|
||||
@click="navigateToIcon"
|
||||
:class="{ active, animate }"
|
||||
:aria-label="name"
|
||||
:href="`/icons/${props.name}`"
|
||||
:data-confetti-text="confettiText"
|
||||
ref="ref"
|
||||
>
|
||||
<KeepAlive>
|
||||
<component
|
||||
v-if="!hideIcon"
|
||||
:is="icon"
|
||||
class="lucide-icon"
|
||||
:class="{
|
||||
customizable,
|
||||
}"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
<style src="./confetti.css" />
|
||||
|
||||
<style scoped>
|
||||
.icon-button {
|
||||
display: inline-block;
|
||||
@@ -72,35 +101,13 @@ function navigateToIcon() {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.icon-button:hover:before {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 48px) scale(1);
|
||||
.confetti-button:before,
|
||||
.confetti-button:after {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.icon-button:before {
|
||||
content: attr(data-title);
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-left: 27px;
|
||||
transform: translate(-50%, 48px) scale(0.9);
|
||||
font-weight: 400;
|
||||
position: absolute;
|
||||
background: var(--vp-c-brand-dark);
|
||||
color: white;
|
||||
z-index: 10;
|
||||
white-space: nowrap;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: cubic-bezier(0.19, 1, 0.22, 1) .2s;
|
||||
transition-property: opacity, transform;
|
||||
/* max-width: calc((32px * 2) + 56px); */
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
.confetti-button:before {
|
||||
line-height: 80px;
|
||||
}
|
||||
|
||||
.icon-button:active {
|
||||
@@ -139,6 +146,7 @@ function navigateToIcon() {
|
||||
|
||||
.lucide-icon {
|
||||
margin: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
.lucide-icon.customizable {
|
||||
will-change: width, height, stroke-width, stroke;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ref } from "vue";
|
||||
|
||||
export default function useConfetti() {
|
||||
const animate = ref(false)
|
||||
const confettiText = ref('confetti!')
|
||||
|
||||
function confetti() {
|
||||
animate.value = true;
|
||||
@@ -13,6 +14,7 @@ export default function useConfetti() {
|
||||
|
||||
return {
|
||||
animate,
|
||||
confetti
|
||||
confetti,
|
||||
confettiText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export default function downloadData(filename:string, data:string) {
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = data
|
||||
link.click();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
const allowedAttrs = [
|
||||
'xmlns',
|
||||
'width',
|
||||
'height',
|
||||
'viewBox',
|
||||
'fill',
|
||||
'stroke',
|
||||
'stroke-width',
|
||||
'stroke-linecap',
|
||||
'stroke-linejoin',
|
||||
'class',
|
||||
]
|
||||
|
||||
export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) {
|
||||
const svg = element ?? document.querySelector('#previewer svg')
|
||||
if (!svg) return
|
||||
|
||||
const clonedSvg = svg.cloneNode(true) as SVGElement
|
||||
|
||||
// Filter out attributes that are not allowed in SVGs
|
||||
for (const attr of Array.from(clonedSvg.attributes)) {
|
||||
if (!allowedAttrs.includes(attr.name)) {
|
||||
clonedSvg.removeAttribute(attr.name)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(attrs ?? {})) {
|
||||
clonedSvg.setAttribute(key, value)
|
||||
}
|
||||
|
||||
const svgString = new XMLSerializer().serializeToString(clonedSvg)
|
||||
|
||||
return svgString
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export default Icon;
|
||||
import Icon from './Icon';
|
||||
|
||||
const App = () => {
|
||||
return <Icon name="home" />;
|
||||
return <Icon name="Home" />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"vitepress": "1.0.0-beta.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/vue": "^1.0.1",
|
||||
"@headlessui/vue": "^1.7.13",
|
||||
"@vueuse/components": "^10.1.0",
|
||||
"@vueuse/core": "^10.1.0",
|
||||
|
||||
Generated
+23
@@ -57,6 +57,9 @@ importers:
|
||||
|
||||
docs:
|
||||
dependencies:
|
||||
'@floating-ui/vue':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(vue@3.2.47)
|
||||
'@headlessui/vue':
|
||||
specifier: ^1.7.13
|
||||
version: 1.7.13(vue@3.2.47)
|
||||
@@ -4351,6 +4354,26 @@ packages:
|
||||
resolution: {integrity: sha512-72tKC61RPkrBZ+uzj+TtL3bVAmxahZKv/TfElbrY9sBoZvr4/+4gw7hnJYiln5xBJHiJ4KTcjUy+6D1qUuWlTA==}
|
||||
dev: true
|
||||
|
||||
/@floating-ui/core@1.3.1:
|
||||
resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==}
|
||||
dev: false
|
||||
|
||||
/@floating-ui/dom@1.4.2:
|
||||
resolution: {integrity: sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==}
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.3.1
|
||||
dev: false
|
||||
|
||||
/@floating-ui/vue@1.0.1(vue@3.2.47):
|
||||
resolution: {integrity: sha512-HZmmNWaztKYKOQxXvMzJYCYtfgG07cL/bPQvZ92AFG3Ktw71bvvLKXsZDAyIxGpqRo9WiTPsYknnSuLV2H/riA==}
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.4.2
|
||||
vue-demi: 0.14.5(vue@3.2.47)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@gar/promisify@1.1.3:
|
||||
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
|
||||
dev: true
|
||||
|
||||
Reference in New Issue
Block a user