chore(web): migrate CircleIconButton to @immich/ui IconButton (#18486)

* remove import and referenced file

* first pass at replacing all CircleIconButtons

* fix linting issues

* fix combobox formatting issues

* fix button context menu coloring

* remove circle icon button from search history box

* use theme switcher from UI lib

* dark mode force the asset viewer icons

* fix forced dark mode icons

* dark mode memory viewer icons

* fix: back button in memory viewer

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Brandon Wees 2025-06-02 09:47:23 -05:00 committed by GitHub
parent d544053c67
commit a02e1f5e7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 822 additions and 556 deletions

View file

@ -22,9 +22,9 @@
<script lang="ts">
import { focusOutside } from '$lib/actions/focus-outside';
import { shortcuts } from '$lib/actions/shortcut';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { generateId } from '$lib/utils/generate-id';
import { IconButton } from '@immich/ui';
import { mdiClose, mdiMagnify, mdiUnfoldMoreHorizontal } from '@mdi/js';
import { onMount, tick } from 'svelte';
import { t } from 'svelte-i18n';
@ -330,7 +330,15 @@
class:pointer-events-none={!selectedOption}
>
{#if selectedOption}
<CircleIconButton onclick={onClear} title={$t('clear_value')} icon={mdiClose} size="16" padding="2" />
<IconButton
shape="round"
color="secondary"
variant="ghost"
onclick={onClear}
aria-label={$t('clear_value')}
icon={mdiClose}
size="small"
/>
{:else if !isOpen}
<Icon path={mdiUnfoldMoreHorizontal} ariaHidden={true} />
{/if}

View file

@ -1,10 +1,6 @@
<script lang="ts">
import { contextMenuNavigation } from '$lib/actions/context-menu-navigation';
import { shortcuts } from '$lib/actions/shortcut';
import CircleIconButton, {
type Color,
type Padding,
} from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import { languageManager } from '$lib/managers/language-manager.svelte';
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
@ -14,6 +10,7 @@
type Align,
} from '$lib/utils/context-menu';
import { generateId } from '$lib/utils/generate-id';
import { IconButton, type Color, type Size, type Variants } from '@immich/ui';
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
@ -30,8 +27,8 @@
// TODO change to start vs end
direction?: 'left' | 'right';
color?: Color;
size?: string | undefined;
padding?: Padding | undefined;
size?: Size | undefined;
variant?: Variants | undefined;
/**
* Additional classes to apply to the button.
*/
@ -49,9 +46,9 @@
title,
align = 'top-left',
direction = 'right',
color = 'transparent',
color = 'secondary',
size = undefined,
padding = undefined,
variant = 'ghost',
buttonClass = undefined,
hideContent = false,
children,
@ -161,12 +158,13 @@
{...restProps}
>
<div bind:this={buttonContainer}>
<CircleIconButton
<IconButton
{color}
{icon}
{padding}
{size}
{title}
shape="round"
{variant}
aria-label={title}
aria-controls={menuId}
aria-expanded={isOpen}
aria-haspopup={true}

View file

@ -2,11 +2,11 @@
import { browser } from '$app/environment';
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { IconButton } from '@immich/ui';
import { mdiClose } from '@mdi/js';
import { onDestroy, onMount, type Snippet } from 'svelte';
import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
interface Props {
showBackButton?: boolean;
@ -80,9 +80,18 @@
forceDark ? 'bg-immich-dark-gray! text-white' : 'bg-subtle dark:bg-immich-dark-gray',
]}
>
<div class="flex place-items-center sm:gap-6 justify-self-start dark:text-immich-dark-fg">
<div class="flex place-items-center sm:gap-6 justify-self-start dark:text-immich-dark-fg {forceDark ? 'dark' : ''}">
{#if showBackButton}
<CircleIconButton title={$t('close')} onclick={handleClose} icon={backIcon} size="24" class={buttonClass} />
<IconButton
aria-label={$t('close')}
onclick={handleClose}
color="secondary"
shape="round"
variant="ghost"
icon={backIcon}
size="large"
class={buttonClass}
/>
{/if}
{@render leading?.()}
</div>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import { IconButton } from '@immich/ui';
import { mdiClose } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -37,5 +37,13 @@
</h1>
</div>
<CircleIconButton onclick={onClose} icon={mdiClose} size="20" title={$t('close')} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
onclick={onClose}
icon={mdiClose}
size="medium"
aria-label={$t('close')}
/>
</div>

View file

@ -1,7 +1,6 @@
<script lang="ts">
import { page } from '$app/state';
import { focusTrap } from '$lib/actions/focus-trap';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
@ -10,7 +9,7 @@
import { user } from '$lib/stores/user.store';
import { userInteraction } from '$lib/stores/user.svelte';
import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -44,13 +43,12 @@
<div class="relative">
<UserAvatar user={$user} size="xl" />
<div class="absolute bottom-0 end-0 rounded-full w-6 h-6">
<CircleIconButton
<IconButton
color="primary"
icon={mdiPencil}
title={$t('edit_avatar')}
class="border"
size="12"
padding="2"
aria-label={$t('edit_avatar')}
size="tiny"
shape="round"
onclick={async () => {
onClose();
await modalManager.show(AvatarEditModal, {});

View file

@ -117,7 +117,7 @@
/>
{/if}
<ThemeButton padding="2" />
<ThemeButton />
<div
use:clickOutside={{
@ -140,7 +140,7 @@
{/if}
</div>
<CastButton navBar />
<CastButton />
<div
use:clickOutside={{

View file

@ -1,5 +1,4 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import {
isComponentNotification,
@ -8,6 +7,7 @@
type ComponentNotification,
type Notification,
} from '$lib/components/shared-components/notification/notification';
import { IconButton } from '@immich/ui';
import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -88,12 +88,14 @@
{:else if notification.type == NotificationType.Info}{$t('info')}{/if}
</h2>
</div>
<CircleIconButton
<IconButton
variant="ghost"
shape="round"
color="secondary"
icon={mdiWindowClose}
title={$t('close')}
aria-label={$t('close')}
class="dark:text-immich-dark-gray"
size="20"
padding="2"
size="medium"
onclick={discard}
aria-hidden={true}
tabindex={-1}

View file

@ -2,7 +2,6 @@
import { goto } from '$app/navigation';
import { focusOutside } from '$lib/actions/focus-outside';
import { shortcuts } from '$lib/actions/shortcut';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import SearchFilterModal from '$lib/modals/SearchFilterModal.svelte';
@ -15,6 +14,7 @@
import { onDestroy, tick } from 'svelte';
import { t } from 'svelte-i18n';
import SearchHistoryBox from './search-history-box.svelte';
import { IconButton } from '@immich/ui';
interface Props {
value?: string;
@ -272,7 +272,15 @@
</div>
<div class="absolute inset-y-0 {showClearIcon ? 'end-14' : 'end-2'} flex items-center ps-6 transition-all">
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" />
<IconButton
aria-label={$t('show_search_options')}
shape="round"
icon={mdiTune}
onclick={onFilterClick}
size="medium"
color="secondary"
variant="ghost"
/>
</div>
{#if isFocus}
@ -291,11 +299,28 @@
{#if showClearIcon}
<div class="absolute inset-y-0 end-0 flex items-center pe-2">
<CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" />
<IconButton
onclick={onClear}
icon={mdiClose}
aria-label={$t('clear')}
size="medium"
color="secondary"
variant="ghost"
shape="round"
/>
</div>
{/if}
<div class="absolute inset-y-0 start-0 flex items-center ps-2">
<CircleIconButton type="submit" title={$t('search')} icon={mdiMagnify} size="20" onclick={() => {}} />
<IconButton
type="submit"
aria-label={$t('search')}
icon={mdiMagnify}
size="medium"
onclick={() => {}}
shape="round"
color="secondary"
variant="ghost"
/>
</div>
</form>
</div>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { searchStore } from '$lib/stores/search.svelte';
import { IconButton } from '@immich/ui';
import { mdiClose, mdiMagnify } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition';
@ -133,11 +133,13 @@
{savedSearchTerm}
</div>
<div aria-hidden={true} class="absolute end-5 top-0 items-center justify-center py-3">
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiClose}
title={$t('remove')}
size="18"
padding="1"
aria-label={$t('remove')}
size="medium"
tabindex={-1}
onclick={() => handleClearSingle(savedSearchTerm)}
/>

View file

@ -1,6 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import Portal from '$lib/components/shared-components/portal/portal.svelte';
@ -14,7 +13,7 @@
import { handleError } from '$lib/utils/handle-error';
import { getButtonVisibility } from '$lib/utils/purchase-utils';
import { updateMyPreferences } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiClose, mdiInformationOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
@ -130,13 +129,16 @@
<div class="h-10 w-10">
<ImmichLogo noText class="h-[32px]" />
</div>
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiClose}
onclick={() => {
showMessage = false;
}}
title={$t('close')}
size="18"
aria-label={$t('close')}
size="medium"
class="text-immich-dark-gray/85 dark:text-immich-gray"
/>
</div>

View file

@ -1,27 +1,13 @@
<script lang="ts">
import { moonPath, moonViewBox, sunPath, sunViewBox } from '$lib/assets/svg-paths';
import CircleIconButton, { type Padding } from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { Theme } from '$lib/constants';
import { themeManager } from '$lib/managers/theme-manager.svelte';
import { t } from 'svelte-i18n';
let icon = $derived(themeManager.isDark ? sunPath : moonPath);
let viewBox = $derived(themeManager.isDark ? sunViewBox : moonViewBox);
interface Props {
padding?: Padding;
}
let { padding = '3' }: Props = $props();
import { ThemeSwitcher } from '@immich/ui';
</script>
{#if !themeManager.theme.system}
<CircleIconButton
title={$t('toggle_theme')}
{icon}
{viewBox}
role="switch"
aria-checked={themeManager.isDark ? 'true' : 'false'}
onclick={() => themeManager.toggleTheme()}
{padding}
<ThemeSwitcher
size="medium"
color="secondary"
onChange={(theme) => themeManager.setTheme(theme == 'dark' ? Theme.DARK : Theme.LIGHT)}
/>
{/if}

View file

@ -1,6 +1,6 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { IconButton } from '@immich/ui';
import { mdiArrowUpLeft, mdiChevronRight } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -19,12 +19,14 @@
<nav class="flex items-center py-2">
{#if !isRoot}
<div>
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiArrowUpLeft}
title={$t('to_parent')}
aria-label={$t('to_parent')}
href={getLink(pathSegments.slice(0, -1).join('/'))}
class="me-2"
padding="2"
onclick={() => {}}
/>
</div>
@ -35,12 +37,14 @@
>
<ol class="flex gap-2 items-center">
<li>
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
{icon}
href={getLink('')}
{title}
size="1.25em"
padding="2"
aria-label={title}
size="medium"
aria-current={isRoot ? 'page' : undefined}
onclick={() => {}}
/>

View file

@ -3,11 +3,11 @@
import { locale } from '$lib/stores/preferences.store';
import { uploadAssetsStore } from '$lib/stores/upload';
import { uploadExecutionQueue } from '$lib/utils/file-uploader';
import { IconButton } from '@immich/ui';
import { mdiCancel, mdiCloudUploadOutline, mdiCog, mdiWindowMinimize } from '@mdi/js';
import { t } from 'svelte-i18n';
import { quartInOut } from 'svelte/easing';
import { fade, scale } from 'svelte/transition';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { notificationController, NotificationType } from './notification/notification';
import UploadAssetPreview from './upload-asset-preview.svelte';
@ -79,27 +79,33 @@
</div>
<div class="flex flex-col items-end">
<div class="flex flex-row">
<CircleIconButton
title={$t('toggle_settings')}
<IconButton
variant="ghost"
shape="round"
color="secondary"
icon={mdiCog}
size="14"
padding="1"
size="small"
onclick={() => (showOptions = !showOptions)}
aria-label={$t('toggle_settings')}
/>
<CircleIconButton
title={$t('minimize')}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label={$t('minimize')}
icon={mdiWindowMinimize}
size="14"
padding="1"
size="small"
onclick={() => (showDetail = false)}
/>
</div>
{#if $isDismissible}
<CircleIconButton
title={$t('dismiss_all_errors')}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label={$t('dismiss_all_errors')}
icon={mdiCancel}
size="14"
padding="1"
size="small"
onclick={() => uploadAssetsStore.dismissErrors()}
/>
{/if}