1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-07 20:36:48 +01:00

refactor(web): Allow dropdown for more general use (#4515)

This commit is contained in:
Daniel Dietzler 2023-10-19 04:46:06 +02:00 committed by GitHub
parent 4b59f83288
commit 5a7ef02387
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 28 deletions

View file

@ -1,17 +1,31 @@
<script lang="ts"> <script lang="ts" context="module">
import Check from 'svelte-material-icons/Check.svelte'; // Necessary for eslint
/* eslint-disable @typescript-eslint/no-explicit-any */
type T = any;
</script>
<script lang="ts" generics="T">
import _ from 'lodash';
import LinkButton from './buttons/link-button.svelte'; import LinkButton from './buttons/link-button.svelte';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import type Icon from 'svelte-material-icons/DotsVertical.svelte'; import type Icon from 'svelte-material-icons/DotsVertical.svelte';
import Check from 'svelte-material-icons/Check.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
select: string; select: T;
}>(); }>();
export let options: string[];
export let value = options[0]; export let options: T[];
export let icons: (typeof Icon)[] | undefined = undefined; export let selectedOption = options[0];
export let render: (item: T) => string | RenderedOption = (item) => String(item);
type RenderedOption = {
title: string;
icon?: typeof Icon;
};
let showMenu = false; let showMenu = false;
@ -19,28 +33,37 @@
showMenu = false; showMenu = false;
}; };
const handleSelectOption = (index: number) => { const handleSelectOption = (option: T) => {
if (options[index] === value) { dispatch('select', option);
dispatch('select', value); selectedOption = option;
} else {
value = options[index];
}
showMenu = false; showMenu = false;
}; };
$: index = options.findIndex((option) => option === value); const renderOption = (option: T): RenderedOption => {
$: icon = icons?.[index]; const renderedOption = render(option);
switch (typeof renderedOption) {
case 'string':
return { title: renderedOption };
default:
return {
title: renderedOption.title,
icon: renderedOption.icon,
};
}
};
$: renderedSelectedOption = renderOption(selectedOption);
</script> </script>
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}> <div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
<!-- BUTTON TITLE --> <!-- BUTTON TITLE -->
<LinkButton on:click={() => (showMenu = true)}> <LinkButton on:click={() => (showMenu = true)}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
{#if icon} {#if renderedSelectedOption?.icon}
<svelte:component this={icon} size="18" /> <svelte:component this={renderedSelectedOption.icon} size="18" />
{/if} {/if}
<p class="hidden sm:block">{value}</p> <p class="hidden sm:block">{renderedSelectedOption.title}</p>
</div> </div>
</LinkButton> </LinkButton>
@ -50,22 +73,23 @@
transition:fly={{ y: -30, x: 30, duration: 200 }} transition:fly={{ y: -30, x: 30, duration: 200 }}
class="text-md absolute right-0 top-5 z-50 flex min-w-[250px] flex-col rounded-2xl bg-gray-100 py-4 text-black shadow-lg dark:bg-gray-700 dark:text-white" class="text-md absolute right-0 top-5 z-50 flex min-w-[250px] flex-col rounded-2xl bg-gray-100 py-4 text-black shadow-lg dark:bg-gray-700 dark:text-white"
> >
{#each options as option, index (option)} {#each options as option (option)}
{@const renderedOption = renderOption(option)}
<button <button
class="grid grid-cols-[20px,1fr] place-items-center gap-2 p-4 transition-all hover:bg-gray-300 dark:hover:bg-gray-800" class="grid grid-cols-[20px,1fr] place-items-center gap-2 p-4 transition-all hover:bg-gray-300 dark:hover:bg-gray-800"
on:click={() => handleSelectOption(index)} on:click={() => handleSelectOption(option)}
> >
{#if value == option} {#if _.isEqual(selectedOption, option)}
<div class="font-medium text-immich-primary dark:text-immich-dark-primary"> <div class="text-immich-primary dark:text-immich-dark-primary">
<Check size="18" /> <Check size="18" />
</div> </div>
<p class="justify-self-start font-medium text-immich-primary dark:text-immich-dark-primary"> <p class="justify-self-start text-immich-primary dark:text-immich-dark-primary">
{option} {renderedOption.title}
</p> </p>
{:else} {:else}
<div /> <div />
<p class="justify-self-start"> <p class="justify-self-start">
{option} {renderedOption.title}
</p> </p>
{/if} {/if}
</button> </button>

View file

@ -216,15 +216,20 @@
</LinkButton> </LinkButton>
<Dropdown <Dropdown
options={Object.values(sortByOptions).map((CourseInfo) => CourseInfo.sortTitle)} options={Object.values(sortByOptions)}
bind:value={$albumViewSettings.sortBy} render={(option) => {
icons={Object.keys(sortByOptions).map((key) => (sortByOptions[key].sortDesc ? ArrowDownThin : ArrowUpThin))} return {
title: option.sortTitle,
icon: option.sortDesc ? ArrowDownThin : ArrowUpThin,
};
}}
on:select={(event) => { on:select={(event) => {
for (const key in sortByOptions) { for (const key in sortByOptions) {
if (sortByOptions[key].sortTitle === event.detail) { if (sortByOptions[key].sortTitle === event.detail.sortTitle) {
sortByOptions[key].sortDesc = !sortByOptions[key].sortDesc; sortByOptions[key].sortDesc = !sortByOptions[key].sortDesc;
} }
} }
$albumViewSettings.sortBy = event.detail.sortTitle;
}} }}
/> />