Fixes Button menu

This commit is contained in:
Eric Fennis
2023-05-09 10:10:42 +02:00
parent 2c3ae629e1
commit 6af24bf878
6 changed files with 179 additions and 227 deletions
@@ -9,21 +9,29 @@ import {
} from '@headlessui/vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import { chevronUp } from '../../../data/iconNodes'
import { useStorage } from '@vueuse/core'
const props = defineProps<{
interface Props {
options: {
text: string
onClick?: () => void
}[],
callOptionOnClick?: boolean
buttonClass?: string
}>()
id: string
popoverPosition?: 'top' | 'bottom'
}
const props = withDefaults(defineProps<Props>(), {
callOptionOnClick: false,
popoverPosition: 'bottom'
})
const emit = defineEmits(['click', 'optionClick'])
const buttonRef = ref(null)
const selectedOption = ref(props.options[0].text)
const selectedOption = useStorage(props.id, props.options[0].text)
const selectionOptionAction = computed(() => props.options.find(option => option.text === selectedOption.value).onClick)
function onClick(event) {
@@ -51,7 +59,7 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
<template>
<Listbox v-model="selectedOption">
<div class="menu">
<div class="menu" >
<div class="button-wrapper">
<VPButton
v-bind="$attrs"
@@ -62,9 +70,15 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
:class="[props.buttonClass]"
ref="buttonRef"
/>
<ListboxButton :as="VPButton" :text="''" theme="alt" class="arrow-up-button"/>
<ListboxButton
:as="VPButton"
:text="''"
theme="alt"
class="arrow-up-button"
:class="popoverPosition"
/>
</div>
<ListboxOptions class="menu-items">
<ListboxOptions class="menu-items" :class="popoverPosition">
<ListboxOption
as="button"
class="menu-item"
@@ -84,10 +98,10 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
position: relative;
}
.menu-items {
--menu-offset: 44px;
position: absolute;
display: flex;
flex-direction: column;
bottom: 44px;
border-radius: 12px;
padding: 12px;
min-width: 128px;
@@ -97,6 +111,8 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
transition: background-color 0.5s;
max-height: calc(100vh - var(--vp-nav-height));
overflow-y: auto;
z-index: 90;
right: 0;
}
.menu-item {
@@ -155,4 +171,20 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
.dark .arrow-up-button::before {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%0A%3E%3Cpolyline points='18 15 12 9 6 15' /%3E%3C/svg%3E%0A");
}
.menu-items.bottom {
top: var(--menu-offset);
}
.menu-items.top {
bottom: var(--menu-offset);
}
.arrow-up-button.top::before {
transform: rotate(0deg);
}
.arrow-up-button.bottom::before {
transform: rotate(180deg);
}
</style>
@@ -6,6 +6,7 @@ import { useIconStyleContext } from '../../composables/useIconStyle';
const props = defineProps<{
name: string
popoverPosition?: 'top' | 'bottom'
}>()
const { size, color, strokeWidth } = useIconStyleContext()
const animate = ref(false)
@@ -53,6 +54,7 @@ function copyVue() {
navigator.clipboard.writeText(code)
}
// TODO: This function should be moved to a composable, together with the styling
function confetti(e) {
animate.value = true;
@@ -65,9 +67,12 @@ function confetti(e) {
<template>
<ButtonMenu
:buttonClass="`confetti-button ${animate ? 'animate' : ''}`"
id="copy-code-button"
callOptionOnClick
@click="confetti"
@optionClick="confetti"
data-confetti-text="Copied!"
:popoverPosition="popoverPosition"
:options="[
{ text: 'Copy JSX' , onClick: copyJSX },
{ text: 'Copy Vue' , onClick: copyVue },
@@ -76,107 +81,4 @@ function confetti(e) {
/>
</template>
<style>
.confetti-button {
cursor: pointer;
position: relative;
--confetti-color: var(--vp-c-brand);
--text-color: 0 0 0;
}
.dark .confetti-button {
--confetti-color: var(--vp-c-brand-dark);
--text-color: 255 255 255;
}
.confetti-button:before,
.confetti-button:after {
position: absolute;
content: "";
display: block;
width: 140%;
height: 100%;
left: -20%;
z-index: -1000;
transition: all ease-in-out 0.5s;
background-repeat: no-repeat;
}
.confetti-button:before {
content: "Copied!";
letter-spacing: 1px;
font-weight: bold;
transform: rotate(-10deg);
color: rgb(var(--text-color) / 1);
display: none;
top: -80%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 20%, var(--confetti-color) 20%, transparent 30%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
10% 10%, 18% 18%;
}
.confetti-button:after {
display: none;
bottom: -75%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
}
.confetti-button.animate:before {
display: block;
animation: topBubbles ease-in-out 1s forwards;
}
.confetti-button.animate:after {
display: block;
animation: bottomBubbles ease-in-out 1s forwards;
}
@keyframes topBubbles {
0% {
color: rgb(var(--text-color) / 0);
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
40% 90%, 55% 90%, 70% 90%;
}
30% {
color: rgb(var(--text-color) / 1);
}
50% {
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
50% 50%, 65% 20%, 90% 30%;
}
100% {
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%,
50% 40%, 65% 10%, 90% 20%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
color: rgb(var(--text-color) / 0);
}
}
@keyframes bottomBubbles {
0% {
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
70% -10%, 70% 0%;
}
50% {
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
105% 0%;
}
100% {
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
110% 10%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
}
}
</style>
<style src="./confetti.css" />
@@ -15,15 +15,26 @@ const allowedAttrs = [
'stroke-linejoin',
'class',
]
const downloadText = 'Download!'
const copiedText = 'Copied!'
const confettiText = ref(copiedText)
const props = defineProps<{
name: string
popoverPosition?: 'top' | 'bottom'
}>()
const { size } = useIconStyleContext()
const animate = ref(false)
function confetti() {
animate.value = true;
setTimeout(function () {
animate.value = false;
}, 1000);
}
function getSVGIcon() {
const svg = document.querySelector('#previewer svg')
if (!svg) return
@@ -43,29 +54,39 @@ function getSVGIcon() {
}
function copySVG() {
confettiText.value = copiedText
const svgString = getSVGIcon()
navigator.clipboard.writeText(svgString)
confetti()
}
function copyDataUrl() {
confettiText.value = copiedText
const svgString = getSVGIcon()
// Create SVG data url
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`
navigator.clipboard.writeText(dataUrl)
confetti()
}
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();
confetti()
}
function downloadPNG() {
confettiText.value = downloadText
const svgString = getSVGIcon()
const canvas = document.createElement('canvas');
@@ -82,24 +103,21 @@ function downloadPNG() {
link.download = `${props.name}.png`;
link.href = canvas.toDataURL('image/png')
link.click();
confetti()
}
}
function confetti(e) {
animate.value = true;
setTimeout(function () {
animate.value = false;
}, 1000);
}
</script>
<template>
<ButtonMenu
:buttonClass="`confetti-button ${animate ? 'animate' : ''}`"
callOptionOnClick
@click="confetti"
@optionClick="confetti"
id="copy-svg-button"
:data-confetti-text="confettiText"
:popoverPosition="popoverPosition"
:options="[
{ text: 'Copy SVG' , onClick: copySVG },
{ text: 'Copy Data URL' , onClick: copyDataUrl },
@@ -109,107 +127,4 @@ function confetti(e) {
/>
</template>
<style>
.confetti-button {
cursor: pointer;
position: relative;
--confetti-color: var(--vp-c-brand);
--text-color: 0 0 0;
}
.dark .confetti-button {
--confetti-color: var(--vp-c-brand-dark);
--text-color: 255 255 255;
}
.confetti-button:before,
.confetti-button:after {
position: absolute;
content: "";
display: block;
width: 140%;
height: 100%;
left: -20%;
z-index: -1000;
transition: all ease-in-out 0.5s;
background-repeat: no-repeat;
}
.confetti-button:before {
content: "Copied!";
letter-spacing: 1px;
font-weight: bold;
transform: rotate(-10deg);
color: rgb(var(--text-color) / 1);
display: none;
top: -80%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 20%, var(--confetti-color) 20%, transparent 30%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
10% 10%, 18% 18%;
}
.confetti-button:after {
display: none;
bottom: -75%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
}
.confetti-button.animate:before {
display: block;
animation: topBubbles ease-in-out 1s forwards;
}
.confetti-button.animate:after {
display: block;
animation: bottomBubbles ease-in-out 1s forwards;
}
@keyframes topBubbles {
0% {
color: rgb(var(--text-color) / 0);
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
40% 90%, 55% 90%, 70% 90%;
}
30% {
color: rgb(var(--text-color) / 1);
}
50% {
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
50% 50%, 65% 20%, 90% 30%;
}
100% {
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%,
50% 40%, 65% 10%, 90% 20%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
color: rgb(var(--text-color) / 0);
}
}
@keyframes bottomBubbles {
0% {
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
70% -10%, 70% 0%;
}
50% {
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
105% 0%;
}
100% {
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
110% 10%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
}
}
</style>
<style src="./confetti.css" />
@@ -48,7 +48,7 @@
:iconNode="props.icon.iconNode"
customizable
/>
<IconInfo :icon="props.icon" />
<IconInfo :icon="props.icon" popoverPosition="top" />
</div>
</div>
</Transition>
@@ -9,6 +9,7 @@ import {useData, useRouter} from 'vitepress';
defineProps<{
icon: IconEntity
popoverPosition?: 'top' | 'bottom'
}>()
const { go } = useRouter()
@@ -37,8 +38,8 @@ const { page } = useData()
text="See in action"
@click="go(`/icons/${icon.name}`)"
/>
<CopySVGButton :name="icon.name" />
<CopyCodeButton :name="icon.name"/>
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
</div>
</div>
@@ -0,0 +1,102 @@
.confetti-button {
cursor: pointer;
position: relative;
--confetti-color: var(--vp-c-brand);
--text-color: 0 0 0;
}
.dark .confetti-button {
--confetti-color: var(--vp-c-brand-dark);
--text-color: 255 255 255;
}
.confetti-button:before,
.confetti-button:after {
position: absolute;
content: "";
display: block;
width: 140%;
height: 100%;
left: -20%;
z-index: -1000;
transition: all ease-in-out 0.5s;
background-repeat: no-repeat;
}
.confetti-button:before {
content: attr(data-confetti-text);
letter-spacing: 1px;
font-weight: bold;
transform: rotate(-8deg);
color: rgb(var(--text-color) / 1);
display: none;
top: -85%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 20%, var(--confetti-color) 20%, transparent 30%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%,
10% 10%, 18% 18%;
}
.confetti-button:after {
display: none;
bottom: -75%;
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%);
background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%;
}
.confetti-button.animate:before {
display: block;
animation: topBubbles ease-in-out 1s forwards;
}
.confetti-button.animate:after {
display: block;
animation: bottomBubbles ease-in-out 1s forwards;
}
@keyframes topBubbles {
0% {
color: rgb(var(--text-color) / 0);
background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%,
40% 90%, 55% 90%, 70% 90%;
}
30% {
color: rgb(var(--text-color) / 1);
}
50% {
background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%,
50% 50%, 65% 20%, 90% 30%;
}
100% {
background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%,
50% 40%, 65% 10%, 90% 20%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
color: rgb(var(--text-color) / 0);
}
}
@keyframes bottomBubbles {
0% {
background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%,
70% -10%, 70% 0%;
}
50% {
background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%,
105% 0%;
}
100% {
background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%,
110% 10%;
background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%;
}
}