1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 06:31:58 +00:00

feat(web): scrollable context menus

This commit is contained in:
ben-basten 2024-09-09 23:29:09 -04:00
parent 971ba63447
commit e628e6a807
No known key found for this signature in database
GPG key ID: 78803E894B258348
15 changed files with 42 additions and 44 deletions

View file

@ -46,7 +46,11 @@ export const contextMenuNavigation: Action<HTMLElement, Options> = (node, option
};
const moveSelection = async (direction: 'up' | 'down', event: KeyboardEvent) => {
const { selectionChanged, container, openDropdown } = options;
const { selectionChanged, container, openDropdown, isOpen } = options;
if (!isOpen) {
// reset the scroll position before opening the menu
container?.scrollTo({ top: 0 });
}
if (openDropdown) {
openDropdown(event);
await tick();

View file

@ -109,7 +109,13 @@
{/if}
</div>
{#if isOwned}
<ButtonContextMenu icon={mdiDotsVertical} size="20" title={$t('options')}>
<ButtonContextMenu
icon={mdiDotsVertical}
size="20"
title={$t('options')}
direction="right"
align="top-left"
>
{#if role === AlbumUserRole.Viewer}
<MenuOption onClick={() => handleSetReadonly(user, AlbumUserRole.Editor)} text={$t('allow_edits')} />
{:else}

View file

@ -186,13 +186,7 @@
{/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="mr-4">
<ButtonContextMenu
icon={mdiDotsVertical}
title={$t('comment_options')}
align="top-right"
direction="left"
size="16"
>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('comment_options')} size="16">
<MenuOption
activeColor="bg-red-200"
icon={mdiDeleteOutline}
@ -239,13 +233,7 @@
{/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="mr-4">
<ButtonContextMenu
icon={mdiDotsVertical}
title={$t('reaction_options')}
align="top-right"
direction="left"
size="16"
>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('reaction_options')} size="16">
<MenuOption
activeColor="bg-red-200"
icon={mdiDeleteOutline}

View file

@ -128,7 +128,7 @@
{#if isOwner}
<DeleteAction {asset} {onAction} />
<ButtonContextMenu direction="left" align="top-right" color="opaque" title={$t('more')} icon={mdiDotsVertical}>
<ButtonContextMenu color="opaque" title={$t('more')} icon={mdiDotsVertical}>
{#if showSlideshow}
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if}

View file

@ -67,6 +67,8 @@
size="20"
icon={mdiDotsVertical}
title={$t('show_person_options')}
direction="right"
align="top-left"
>
<MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} />
<MenuOption onClick={onChangeName} icon={mdiAccountEditOutline} text={$t('change_name')} />

View file

@ -237,7 +237,7 @@
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={handleUpdate} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem />
<ChangeDate menuItem />
<ChangeLocation menuItem />

View file

@ -20,11 +20,11 @@
/**
* The alignment of the context menu relative to the button.
*/
export let align: Align = 'top-left';
export let align: Align = 'top-right';
/**
* The direction in which the context menu should open.
*/
export let direction: 'left' | 'right' = 'right';
export let direction: 'left' | 'right' = 'left';
export let color: Color = 'transparent';
export let size: string | undefined = undefined;
export let padding: Padding | undefined = undefined;

View file

@ -18,25 +18,26 @@
let left: number;
let top: number;
// We need to bind clientHeight since the bounding box may return a height
// of zero when starting the 'slide' animation.
let height: number;
$: {
if (menuElement) {
const rect = menuElement.getBoundingClientRect();
const directionWidth = direction === 'left' ? rect.width : 0;
const menuHeight = Math.min(menuElement.clientHeight, height) || 0;
const menuHeight = menuElement.clientHeight || 0;
left = Math.min(window.innerWidth - rect.width, x - directionWidth);
const calcLeft = Math.min(window.innerWidth - rect.width, x - directionWidth);
left = Math.max(0, calcLeft);
top = Math.min(window.innerHeight - menuHeight, y);
}
}
</script>
<div
bind:clientHeight={height}
class="fixed z-10 min-w-[200px] w-max max-w-[300px] overflow-hidden rounded-lg shadow-lg"
class="fixed z-10 overflow-hidden rounded-lg duration-[250ms] ease-in {isVisible
? 'shadow-lg transition-shadow'
: 'shadow-none transition-none'}"
class:shadow-none={!isVisible}
class:shadow-lg={isVisible}
class:transition-none={!isVisible}
style:left="{left}px"
style:top="{top}px"
transition:slide={{ duration: 250, easing: quintOut }}
@ -48,9 +49,9 @@
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
bind:this={menuElement}
class:max-h-[100vh]={isVisible}
class:max-h-0={!isVisible}
class="flex flex-col transition-all duration-[250ms] ease-in-out outline-none"
class="flex flex-col transition-all duration-[250ms] ease-in-out outline-none immich-scrollbar bg-slate-100 relative min-w-[200px] max-w-[200px] sm:max-w-[256px] rounded-lg {isVisible
? 'translate-x-0 max-h-dvh overflow-y-auto'
: `${direction === 'left' ? 'translate-x-28' : '-translate-x-28'} max-h-0 overflow-y-hidden`}"
role="menu"
tabindex="-1"
>

View file

@ -33,7 +33,9 @@
role="menuitem"
>
{#if icon}
<Icon path={icon} ariaHidden={true} size="18" />
<div class="flex-none">
<Icon path={icon} ariaHidden={true} size="18" />
</div>
{/if}
<div>
{text}

View file

@ -107,6 +107,8 @@
size="24"
padding="3"
hideContent
direction="right"
align="top-left"
>
<SharedLinkEdit menuItem {onEdit} />
<SharedLinkCopy menuItem {link} />

View file

@ -42,7 +42,7 @@
<AddToAlbum shared />
</ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem />
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
</ButtonContextMenu>

View file

@ -31,7 +31,7 @@
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={clearMultiselect}>
<CreateSharedLink />
<ButtonContextMenu icon={mdiPlus} title={$t('add')}>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum />
<AddToAlbum shared />
</ButtonContextMenu>

View file

@ -385,7 +385,7 @@
<AddToAlbum shared />
</ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem filename="{person.name || 'immich'}.zip" />
<MenuOption
icon={mdiAccountMultipleCheckOutline}

View file

@ -235,7 +235,7 @@
</ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem />
<ChangeDate menuItem />
<ChangeLocation menuItem />

View file

@ -285,14 +285,7 @@
</td>
<td class=" text-ellipsis px-4 text-sm">
<ButtonContextMenu
align="top-right"
direction="left"
color="primary"
size="16"
icon={mdiDotsVertical}
title={$t('library_options')}
>
<ButtonContextMenu color="primary" size="16" icon={mdiDotsVertical} title={$t('library_options')}>
<MenuOption onClick={() => onScanClicked(library)} text={$t('scan_library')} />
<hr />
<MenuOption onClick={() => onRenameClicked(index)} text={$t('rename')} />