mirror of
https://github.com/immich-app/immich.git
synced 2025-01-04 02:46:47 +01:00
feat(web): custom button popovers
This commit is contained in:
parent
20b4d281bb
commit
b4bc9a6d2e
2 changed files with 74 additions and 2 deletions
|
@ -31,6 +31,9 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
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;
|
type $$Props = Props;
|
||||||
|
|
||||||
|
@ -80,19 +83,73 @@
|
||||||
$: colorClass = colorClasses[color];
|
$: colorClass = colorClasses[color];
|
||||||
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
|
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
|
||||||
$: paddingClass = paddingClasses[padding];
|
$: 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>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<svelte:element
|
<svelte:element
|
||||||
this={href ? 'a' : 'button'}
|
this={href ? 'a' : 'button'}
|
||||||
|
bind:this={element}
|
||||||
type={href ? undefined : type}
|
type={href ? undefined : type}
|
||||||
{title}
|
|
||||||
{href}
|
{href}
|
||||||
style:width={buttonSize ? buttonSize + 'px' : ''}
|
style:width={buttonSize ? buttonSize + 'px' : ''}
|
||||||
style:height={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}"
|
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
|
on:click
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
|
<Icon path={icon} {size} ariaHidden {viewBox} color="currentColor" />
|
||||||
</svelte:element>
|
</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}
|
||||||
|
|
15
web/src/lib/utils/popover-timer.ts
Normal file
15
web/src/lib/utils/popover-timer.ts
Normal 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 };
|
Loading…
Reference in a new issue