1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

feat(web): custom button popovers

This commit is contained in:
ben-basten 2024-10-12 11:07:50 -04:00
parent 20b4d281bb
commit b4bc9a6d2e
No known key found for this signature in database
GPG key ID: 78803E894B258348
2 changed files with 74 additions and 2 deletions

View file

@ -31,6 +31,9 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { scale } from 'svelte/transition';
import { tick } from 'svelte';
import { getHoverMode, hoverDelay, setHoverMode } from '$lib/utils/popover-timer';
type $$Props = Props;
@ -80,19 +83,73 @@
$: colorClass = colorClasses[color];
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
$: paddingClass = paddingClasses[padding];
let popover: HTMLElement;
let element: HTMLElement;
let top = 0;
let left = 0;
let showPopover = false;
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const togglePopover = (show: boolean) => {
if (!show) {
clearTimeout(timeoutId);
setHoverMode(false);
showPopover = false;
return;
}
if (getHoverMode()) {
onShowPopover();
} else {
timeoutId = setTimeout(onShowPopover, hoverDelay);
}
};
const onShowPopover = () => {
const box = element.getBoundingClientRect();
top = box.top + box.height + 5;
left = box.left;
showPopover = true;
setHoverMode(true);
void tick().then(() => {
const offsetWidth = popover?.offsetWidth ?? 10;
const spaceBelow = (window.visualViewport?.height ?? window.innerHeight) - box.top - box.height;
if (spaceBelow < popover.offsetHeight) {
top = box.top - popover.offsetHeight - 5;
}
left = box.left + box.width / 2 - offsetWidth / 2;
});
};
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<svelte:element
this={href ? 'a' : 'button'}
bind:this={element}
type={href ? undefined : type}
{title}
{href}
style:width={buttonSize ? buttonSize + 'px' : ''}
style:height={buttonSize ? buttonSize + 'px' : ''}
class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all disabled:cursor-default hover:dark:text-immich-dark-gray {className} {mobileClass}"
aria-label={title}
on:mouseenter={() => togglePopover(true)}
on:mouseleave={() => togglePopover(false)}
on:focus={() => togglePopover(true)}
on:blur={() => togglePopover(false)}
on:click
{...$$restProps}
>
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
<Icon path={icon} {size} ariaHidden {viewBox} color="currentColor" />
</svelte:element>
{#if showPopover}
<div
in:scale={{ duration: 150, opacity: 0, start: 0.9 }}
out:scale={{ duration: 150, opacity: 0, start: 0.9 }}
class="fixed inset-[unset] rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
style:top={top + 'px'}
style:left={left + 'px'}
bind:this={popover}
>
{title}
</div>
{/if}

View file

@ -0,0 +1,15 @@
let _hoverMode = false;
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const hoverDelay = 600;
const getHoverMode = () => _hoverMode;
const setHoverMode = (mode: boolean) => {
if (mode) {
clearTimeout(timeoutId);
_hoverMode = true;
return;
}
timeoutId = setTimeout(() => {
_hoverMode = false;
}, hoverDelay);
};
export { getHoverMode, hoverDelay, setHoverMode };