first pass at replacing all CircleIconButtons

This commit is contained in:
bwees 2025-05-22 09:57:12 -05:00
parent 0b18f5c12a
commit 1f6ad41d3f
No known key found for this signature in database
69 changed files with 797 additions and 370 deletions

View File

@ -2,44 +2,23 @@
import { t } from 'svelte-i18n';
import { onMount } from 'svelte';
import { mdiCast, mdiCastConnected } from '@mdi/js';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { CastDestinationType, castManager } from '$lib/managers/cast-manager.svelte';
import { GCastDestination } from '$lib/utils/cast/gcast-destination.svelte';
import { IconButton } from '@immich/ui';
interface Props {
whiteHover?: boolean;
navBar?: boolean;
}
let { whiteHover, navBar }: Props = $props();
onMount(async () => {
await castManager.initialize();
});
const getButtonColor = () => {
return castManager.isCasting ? 'primary' : whiteHover ? undefined : 'opaque';
};
</script>
{#if castManager.availableDestinations.length > 0 && castManager.availableDestinations[0].type === CastDestinationType.GCAST}
{#if navBar}
<IconButton
shape="round"
variant="ghost"
size="medium"
color={castManager.isCasting ? 'primary' : 'secondary'}
icon={castManager.isCasting ? mdiCastConnected : mdiCast}
onclick={() => void GCastDestination.showCastDialog()}
aria-label={$t('cast')}
/>
{:else}
<CircleIconButton
color={getButtonColor()}
icon={castManager.isCasting ? mdiCastConnected : mdiCast}
onclick={GCastDestination.showCastDialog}
title={$t('cast')}
/>
{/if}
<IconButton
shape="round"
variant="ghost"
size="medium"
color={castManager.isCasting ? 'primary' : 'secondary'}
icon={castManager.isCasting ? mdiCastConnected : mdiCast}
onclick={() => void GCastDestination.showCastDialog()}
aria-label={$t('cast')}
/>
{/if}

View File

@ -17,6 +17,7 @@
import { t } from 'svelte-i18n';
import JobTileButton from './job-tile-button.svelte';
import JobTileStatus from './job-tile-status.svelte';
import { IconButton } from '@immich/ui';
interface Props {
title: string;
@ -75,12 +76,12 @@
<span class="text-sm">
{$t('admin.jobs_failed', { values: { jobCount: jobCounts.failed.toLocaleString($locale) } })}
</span>
<CircleIconButton
<IconButton
color="primary"
icon={mdiClose}
title={$t('clear_message')}
size="12"
padding="1"
aria-label={$t('clear_message')}
size="tiny"
shape="round"
onclick={() => onCommand({ command: JobCommand.ClearFailed, force: false })}
/>
</div>

View File

@ -8,7 +8,7 @@
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags } from '$lib/stores/server-config.store';
import type { SystemConfigDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiMinusCircle } from '@mdi/js';
import { isEqual } from 'lodash-es';
import { t } from 'svelte-i18n';
@ -48,12 +48,11 @@
{#each config.machineLearning.urls as _, i (i)}
{#snippet removeButton()}
{#if config.machineLearning.urls.length > 1}
<CircleIconButton
size="24"
class="ms-2"
padding="2"
color="red"
title=""
<IconButton
size="large"
shape="round"
color="danger"
aria-label=""
onclick={() => config.machineLearning.urls.splice(i, 1)}
icon={mdiMinusCircle}
/>

View File

@ -4,6 +4,7 @@
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
import { getShortDateRange } from '$lib/utils/date-time';
import type { AlbumResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiDotsVertical } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -42,12 +43,13 @@
class="absolute end-6 top-6 opacity-0 group-hover:opacity-100 focus-within:opacity-100"
data-testid="context-button-parent"
>
<CircleIconButton
color="opaque"
title={$t('show_album_options')}
<IconButton
color="secondary"
aria-label={$t('show_album_options')}
icon={mdiDotsVertical}
size="20"
padding="2"
shape="round"
variant="ghost"
size="medium"
class="icon-white-drop-shadow"
onclick={showAlbumContextMenu}
/>

View File

@ -7,7 +7,7 @@
import { delay } from '$lib/utils/asset-utils';
import { navigate } from '$lib/utils/navigation';
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
import { LoadingSpinner, Modal, ModalBody } from '@immich/ui';
import { IconButton, LoadingSpinner, Modal, ModalBody } from '@immich/ui';
import { mdiMapOutline } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -103,7 +103,14 @@
}
</script>
<CircleIconButton title={$t('map')} onclick={openMap} icon={mdiMapOutline} />
<IconButton
variant="ghost"
shape="round"
color="secondary"
icon={mdiMapOutline}
onclick={openMap}
aria-label={$t('map')}
/>
{#if albumMapViewManager.isInMapView}
<Modal title={$t('map')} size="medium" onClose={closeMap}>

View File

@ -2,7 +2,7 @@
import SharedLinkCopy from '$lib/components/sharedlinks-page/actions/shared-link-copy.svelte';
import { locale } from '$lib/stores/preferences.store';
import type { AlbumResponseDto, SharedLinkResponseDto } from '@immich/sdk';
import { Text } from '@immich/ui';
import { IconButton, Text } from '@immich/ui';
import { mdiQrcode } from '@mdi/js';
import { DateTime } from 'luxon';
import { t } from 'svelte-i18n';
@ -40,7 +40,14 @@
<Text size="tiny" color="muted">{getShareProperties()}</Text>
</div>
<div class="flex">
<CircleIconButton title={$t('view_qr_code')} icon={mdiQrcode} onclick={onViewQrCode} />
<IconButton
aria-label={$t('view_qr_code')}
shape="round"
color="secondary"
variant="ghost"
icon={mdiQrcode}
onclick={onViewQrCode}
/>
<SharedLinkCopy link={sharedLink} />
</div>
</div>

View File

@ -14,7 +14,6 @@
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import DownloadAction from '../photos-page/actions/download-action.svelte';
import AssetGrid from '../photos-page/asset-grid.svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
@ -22,6 +21,7 @@
import ThemeButton from '../shared-components/theme-button.svelte';
import AlbumSummary from './album-summary.svelte';
import CastButton from '$lib/cast/cast-button.svelte';
import { IconButton } from '@immich/ui';
interface Props {
sharedLink: SharedLinkResponseDto;
@ -104,19 +104,25 @@
{/snippet}
{#snippet trailing()}
<CastButton whiteHover />
<CastButton />
{#if sharedLink.allowUpload}
<CircleIconButton
title={$t('add_photos')}
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('add_photos')}
onclick={() => openFileUploadDialog({ albumId: album.id })}
icon={mdiFileImagePlusOutline}
/>
{/if}
{#if album.assetCount > 0 && sharedLink.allowDownload}
<CircleIconButton
title={$t('download')}
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('download')}
onclick={() => downloadAlbum(album)}
icon={mdiFolderDownloadOutline}
/>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import { IconButton } from '@immich/ui';
import { mdiArrowLeft } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -12,4 +13,11 @@
<svelte:document use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: onClose }} />
<CircleIconButton color="opaque" icon={mdiArrowLeft} title={$t('go_back')} onclick={onClose} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiArrowLeft}
aria-label={$t('go_back')}
onclick={onClose}
/>

View File

@ -15,6 +15,7 @@
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction, PreAction } from './action';
import { IconButton } from '@immich/ui';
interface Props {
asset: AssetResponseDto;
@ -79,10 +80,12 @@
]}
/>
<CircleIconButton
color="opaque"
<IconButton
color="secondary"
shape="round"
variant="ghost"
icon={asset.isTrashed ? mdiDeleteForeverOutline : mdiDeleteOutline}
title={asset.isTrashed ? $t('permanently_delete') : $t('delete')}
aria-label={asset.isTrashed ? $t('permanently_delete') : $t('delete')}
onclick={() => trashOrDelete(asset.isTrashed)}
/>

View File

@ -5,6 +5,7 @@
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { downloadFile } from '$lib/utils/asset-utils';
import { getAssetInfo } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiFolderDownloadOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -21,7 +22,13 @@
<svelte:document use:shortcut={{ shortcut: { key: 'd', shift: true }, onShortcut: onDownloadFile }} />
{#if !menuItem}
<CircleIconButton color="opaque" icon={mdiFolderDownloadOutline} title={$t('download')} onclick={onDownloadFile} />
<IconButton
color="primary"
shape="round"
icon={mdiFolderDownloadOutline}
aria-label={$t('download')}
onclick={onDownloadFile}
/>
{:else}
<MenuOption icon={mdiFolderDownloadOutline} text={$t('download')} onClick={onDownloadFile} />
{/if}

View File

@ -11,6 +11,7 @@
import { mdiHeart, mdiHeartOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction } from './action';
import { IconButton } from '@immich/ui';
interface Props {
asset: AssetResponseDto;
@ -47,9 +48,11 @@
<svelte:document use:shortcut={{ shortcut: { key: 'f' }, onShortcut: toggleFavorite }} />
<CircleIconButton
color="opaque"
<IconButton
color="secondary"
shape="round"
variant="ghost"
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
title={asset.isFavorite ? $t('unfavorite') : $t('to_favorite')}
aria-label={asset.isFavorite ? $t('unfavorite') : $t('to_favorite')}
onclick={toggleFavorite}
/>

View File

@ -1,4 +1,5 @@
<script lang="ts"></script>
<script lang="ts">
import { IconButton } from '@immich/ui';
import { mdiMotionPauseOutline, mdiPlaySpeed } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -10,9 +11,10 @@
let { isPlaying, onClick }: Props = $props();
</script>
<CircleIconButton
color="opaque"
<IconButton
shape="round"
color="primary"
icon={isPlaying ? mdiMotionPauseOutline : mdiPlaySpeed}
title={isPlaying ? $t('stop_motion_photo') : $t('play_motion_photo')}
aria-label={isPlaying ? $t('stop_motion_photo') : $t('play_motion_photo')}
onclick={() => onClick(!isPlaying)}
/>

View File

@ -4,6 +4,7 @@
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { makeSharedLinkUrl } from '$lib/utils';
import type { AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -22,4 +23,11 @@
};
</script>
<CircleIconButton color="opaque" icon={mdiShareVariantOutline} onclick={handleClick} title={$t('share')} />
<IconButton
color="secondary"
shape="round"
variant="ghost"
icon={mdiShareVariantOutline}
onclick={handleClick}
aria-label={$t('share')}
/>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import { IconButton } from '@immich/ui';
import { mdiInformationOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -12,4 +13,11 @@
<svelte:document use:shortcut={{ shortcut: { key: 'i' }, onShortcut: onShowDetail }} />
<CircleIconButton color="opaque" icon={mdiInformationOutline} onclick={onShowDetail} title={$t('info')} />
<IconButton
color="secondary"
shape="round"
variant="ghost"
icon={mdiInformationOutline}
onclick={onShowDetail}
aria-label={$t('info')}
/>

View File

@ -12,10 +12,10 @@
import { handleError } from '$lib/utils/handle-error';
import { isTenMinutesApart } from '$lib/utils/timesince';
import { ReactionType, type ActivityResponseDto, type AssetTypeEnum, type UserResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
import * as luxon from 'luxon';
import { t } from 'svelte-i18n';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import UserAvatar from '../shared-components/user-avatar.svelte';
@ -125,7 +125,14 @@
bind:clientHeight={activityHeight}
>
<div class="flex place-items-center gap-2">
<CircleIconButton onclick={onClose} icon={mdiClose} title={$t('close')} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
onclick={onClose}
icon={mdiClose}
aria-label={$t('close')}
/>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('activity')}</p>
</div>
@ -159,7 +166,7 @@
title={$t('comment_options')}
align="top-right"
direction="left"
size="16"
size="small"
>
<MenuOption
activeColor="bg-red-200"
@ -212,7 +219,7 @@
title={$t('reaction_options')}
align="top-right"
direction="left"
size="16"
size="small"
>
<MenuOption
activeColor="bg-red-200"
@ -269,9 +276,11 @@
</div>
{:else if message}
<div class="flex items-end w-fit ms-0">
<CircleIconButton
title={$t('send_message')}
size="15"
<IconButton
shape="round"
aria-label={$t('send_message')}
size="small"
variant="ghost"
icon={mdiSend}
class="dark:text-immich-dark-gray"
onclick={() => handleSendComment()}

View File

@ -35,6 +35,7 @@
type PersonResponseDto,
type StackResponseDto,
} from '@immich/sdk';
import { IconButton } from '@immich/ui';
import {
mdiAlertOutline,
mdiCogRefreshOutline,
@ -122,22 +123,37 @@
<ShareAction {asset} />
{/if}
{#if asset.isOffline}
<CircleIconButton color="alert" icon={mdiAlertOutline} onclick={onShowDetail} title={$t('asset_offline')} />
<IconButton
shape="round"
color="danger"
icon={mdiAlertOutline}
onclick={onShowDetail}
aria-label={$t('asset_offline')}
/>
{/if}
{#if asset.livePhotoVideoId}
{@render motionPhoto?.()}
{/if}
{#if asset.type === AssetTypeEnum.Image}
<CircleIconButton
color="opaque"
hideMobile={true}
<IconButton
class="hidden sm:flex"
color="secondary"
variant="ghost"
shape="round"
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
title={$t('zoom_image')}
aria-label={$t('zoom_image')}
onclick={onZoomImage}
/>
{/if}
{#if canCopyImageToClipboard() && asset.type === AssetTypeEnum.Image}
<CircleIconButton color="opaque" icon={mdiContentCopy} title={$t('copy_image')} onclick={() => onCopyImage?.()} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiContentCopy}
aria-label={$t('copy_image')}
onclick={() => onCopyImage?.()}
/>
{/if}
{#if !isOwner && showDownloadButton}
@ -151,20 +167,11 @@
{#if isOwner}
<FavoriteAction {asset} {onAction} />
{/if}
<!-- {#if showEditorButton}
<CircleIconButton
color="opaque"
hideMobile={true}
icon={mdiImageEditOutline}
onclick={showEditorHandler}
title={$t('editor')}
/>
{/if} -->
{#if isOwner}
<DeleteAction {asset} {onAction} {preAction} />
<ButtonContextMenu direction="left" align="top-right" color="opaque" title={$t('more')} icon={mdiDotsVertical}>
<ButtonContextMenu direction="left" align="top-right" color="secondary" title={$t('more')} icon={mdiDotsVertical}>
{#if showSlideshow && !isLocked}
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if}

View File

@ -27,6 +27,7 @@
type AssetResponseDto,
type ExifResponseDto,
} from '@immich/sdk';
import { IconButton } from '@immich/ui';
import {
mdiCalendar,
mdiCameraIris,
@ -42,7 +43,6 @@
import { t } from 'svelte-i18n';
import { slide } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
@ -158,7 +158,14 @@
<section class="relative p-2">
<div class="flex place-items-center gap-2">
<CircleIconButton icon={mdiClose} title={$t('close')} onclick={onClose} />
<IconButton
icon={mdiClose}
aria-label={$t('close')}
onclick={onClose}
shape="round"
color="secondary"
variant="ghost"
/>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('info')}</p>
</div>
@ -193,30 +200,34 @@
<h2>{$t('people').toUpperCase()}</h2>
<div class="flex gap-2 items-center">
{#if people.some((person) => person.isHidden)}
<CircleIconButton
title={$t('show_hidden_people')}
<IconButton
aria-label={$t('show_hidden_people')}
icon={showingHiddenPeople ? mdiEyeOff : mdiEye}
padding="1"
buttonSize="32"
size="medium"
shape="round"
color="secondary"
variant="ghost"
onclick={() => (showingHiddenPeople = !showingHiddenPeople)}
/>
{/if}
<CircleIconButton
title={$t('tag_people')}
<IconButton
aria-label={$t('tag_people')}
icon={mdiPlus}
padding="1"
size="20"
buttonSize="32"
size="medium"
shape="round"
color="secondary"
variant="ghost"
onclick={() => (isFaceEditMode.value = !isFaceEditMode.value)}
/>
{#if people.length > 0 || unassignedFaces.length > 0}
<CircleIconButton
title={$t('edit_people')}
<IconButton
aria-label={$t('edit_people')}
icon={mdiPencil}
padding="1"
size="20"
buttonSize="32"
size="medium"
shape="round"
color="secondary"
variant="ghost"
onclick={() => (showEditFaces = true)}
/>
{/if}
@ -369,11 +380,13 @@
<p class="break-all flex place-items-center gap-2 whitespace-pre-wrap">
{asset.originalFileName}
{#if isOwner}
<CircleIconButton
<IconButton
icon={mdiInformationOutline}
title={$t('show_file_location')}
size="16"
padding="2"
aria-label={$t('show_file_location')}
size="small"
shape="round"
color="secondary"
variant="ghost"
onclick={toggleAssetPath}
/>
{/if}

View File

@ -5,7 +5,7 @@
import { t } from 'svelte-i18n';
import { fly, slide } from 'svelte/transition';
import { getByteUnitString } from '../../utils/byte-units';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { IconButton } from '@immich/ui';
const abort = (downloadKey: string, download: DownloadProgress) => {
download.abort?.abort();
@ -42,10 +42,13 @@
</div>
</div>
<div class="absolute end-2">
<CircleIconButton
title={$t('close')}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label={$t('close')}
onclick={() => abort(downloadKey, download)}
size="20"
size="large"
icon={mdiClose}
class="dark:text-immich-dark-gray"
/>

View File

@ -9,6 +9,7 @@
rotateDegrees,
type CropAspectRatio,
} from '$lib/stores/asset-editor.store';
import { IconButton } from '@immich/ui';
import { mdiBackupRestore, mdiCropFree, mdiRotateLeft, mdiRotateRight, mdiSquareOutline } from '@mdi/js';
import { tick } from 'svelte';
import { t } from 'svelte-i18n';
@ -147,7 +148,25 @@
<h2>{$t('editor_crop_tool_h2_rotation').toUpperCase()}</h2>
</div>
<ul class="flex-wrap flex-row flex gap-x-6 gap-y-4 justify-center">
<li><CircleIconButton title={$t('anti_clockwise')} onclick={() => rotate(false)} icon={mdiRotateLeft} /></li>
<li><CircleIconButton title={$t('clockwise')} onclick={() => rotate(true)} icon={mdiRotateRight} /></li>
<li>
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('anti_clockwise')}
onclick={() => rotate(false)}
icon={mdiRotateLeft}
/>
</li>
<li>
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('clockwise')}
onclick={() => rotate(true)}
icon={mdiRotateRight}
/>
</li>
</ul>
</div>

View File

@ -4,10 +4,10 @@
import { editTypes, showCancelConfirmDialog } from '$lib/stores/asset-editor.store';
import { websocketEvents } from '$lib/stores/websocket';
import { type AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiClose } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import CircleIconButton from '../../elements/buttons/circle-icon-button.svelte';
onMount(() => {
return websocketEvents.on('on_asset_update', (assetUpdate) => {
@ -44,17 +44,25 @@
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
<div class="flex place-items-center gap-2">
<CircleIconButton icon={mdiClose} title={$t('close')} onclick={onClose} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
icon={mdiClose}
aria-label={$t('close')}
onclick={onClose}
/>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg capitalize">{$t('editor')}</p>
</div>
<section class="px-4 py-4">
<ul class="flex w-full justify-around">
{#each editTypes as etype (etype.name)}
<li>
<CircleIconButton
color={etype.name === selectedType ? 'primary' : 'opaque'}
<IconButton
shape="round"
color={etype.name === selectedType ? 'primary' : 'secondary'}
icon={etype.icon}
title={etype.name}
aria-label={etype.name}
onclick={() => selectType(etype.name)}
/>
</li>

View File

@ -3,6 +3,7 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { castManager, CastState } from '$lib/managers/cast-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { IconButton } from '@immich/ui';
import { mdiCastConnected, mdiPause, mdiPlay } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -83,11 +84,13 @@
<LoadingSpinner />
</div>
{:else}
<CircleIconButton
color="opaque"
<IconButton
color="primary"
shape="round"
variant="ghost"
icon={castManager.castState == CastState.PLAYING ? mdiPause : mdiPlay}
onclick={() => handlePlayPauseButton()}
title={castManager.castState == CastState.PLAYING ? 'Pause' : 'Play'}
aria-label={castManager.castState == CastState.PLAYING ? 'Pause' : 'Play'}
/>
{/if}

View File

@ -1,29 +0,0 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { render, screen } from '@testing-library/svelte';
describe('CircleIconButton component', () => {
it('should render as a button', () => {
render(CircleIconButton, { icon: '', title: 'test' });
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('type', 'button');
expect(button).not.toHaveAttribute('href');
expect(button).toHaveAttribute('title', 'test');
});
it('should render as a link if href prop is set', () => {
render(CircleIconButton, { props: { href: '/test', icon: '', title: 'test' } });
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', '/test');
expect(link).not.toHaveAttribute('type');
});
it('should render icon inside button', () => {
render(CircleIconButton, { icon: '', title: 'test' });
const button = screen.getByRole('button');
const icon = button.querySelector('svg');
expect(icon).toBeInTheDocument();
expect(icon).toHaveAttribute('aria-label', 'test');
});
});

View File

@ -3,6 +3,7 @@
import { mdiClose, mdiMagnify } from '@mdi/js';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { t } from 'svelte-i18n';
import { IconButton } from '@immich/ui';
interface Props {
name: string;
@ -42,11 +43,13 @@
? 'rounded-2xl'
: 'rounded-t-lg'} bg-gray-200 p-2 dark:bg-immich-dark-gray gap-2 place-items-center h-full"
>
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiMagnify}
title={$t('search')}
size="16"
padding="2"
aria-label={$t('search')}
size="small"
onclick={() => onSearch({ force: true })}
/>
<input
@ -64,6 +67,14 @@
</div>
{/if}
{#if name}
<CircleIconButton icon={mdiClose} title={$t('clear_value')} size="16" padding="2" onclick={resetSearch} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiClose}
aria-label={$t('clear_value')}
size="small"
onclick={resetSearch}
/>
{/if}
</div>

View File

@ -7,6 +7,7 @@
import { zoomImageToBase64 } from '$lib/utils/people-utils';
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
import { AssetTypeEnum, getAllPeople, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -78,19 +79,36 @@
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}
<div class="flex items-center gap-2">
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} onclick={onClose} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiArrowLeftThin}
aria-label={$t('back')}
onclick={onClose}
/>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('select_face')}</p>
</div>
<div class="flex justify-end gap-2">
<CircleIconButton
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiMagnify}
title={$t('search_for_existing_person')}
aria-label={$t('search_for_existing_person')}
onclick={() => {
searchFaces = true;
}}
/>
{#if !isShowLoadingNewPerson}
<CircleIconButton icon={mdiPlus} title={$t('create_new_person')} onclick={handleCreatePerson} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiPlus}
aria-label={$t('create_new_person')}
onclick={handleCreatePerson}
/>
{:else}
<div class="flex place-content-center place-items-center">
<LoadingSpinner />
@ -98,7 +116,14 @@
{/if}
</div>
{:else}
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} onclick={onClose} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiArrowLeftThin}
aria-label={$t('back')}
onclick={onClose}
/>
<div class="w-full flex">
<SearchPeople
type="input"
@ -112,7 +137,14 @@
</div>
{/if}
</div>
<CircleIconButton icon={mdiClose} title={$t('cancel_search')} onclick={() => (searchFaces = false)} />
<IconButton
color="secondary"
variant="ghost"
shape="round"
icon={mdiClose}
aria-label={$t('cancel_search')}
onclick={() => (searchFaces = false)}
/>
{/if}
</div>
<div class="px-4 py-4 text-sm">

View File

@ -11,10 +11,9 @@
import { getPeopleThumbnailUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { updatePeople, type PersonResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiClose, mdiEye, mdiEyeOff, mdiEyeSettings, mdiRestart } from '@mdi/js';
import { t } from 'svelte-i18n';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
interface Props {
people: PersonResponseDto[];
@ -114,7 +113,14 @@
class="fixed top-0 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
>
<div class="flex items-center">
<CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('close')}
icon={mdiClose}
onclick={onClose}
/>
<div class="flex gap-2 items-center">
<p id={titleId} class="ms-2">{$t('show_and_hide_people')}</p>
<p class="text-sm text-gray-400 dark:text-gray-600">({totalPeopleCount.toLocaleString($locale)})</p>
@ -122,8 +128,22 @@
</div>
<div class="flex items-center justify-end">
<div class="flex items-center md:me-4">
<CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} onclick={handleResetVisibility} />
<CircleIconButton title={toggleButton.label} icon={toggleButton.icon} onclick={handleToggleVisibility} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('reset_people_visibility')}
icon={mdiRestart}
onclick={handleResetVisibility}
/>
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={toggleButton.label}
icon={toggleButton.icon}
onclick={handleToggleVisibility}
/>
</div>
<Button loading={showLoadingSpinner} onclick={handleSaveVisibility} size="small">{$t('done')}</Button>
</div>

View File

@ -6,14 +6,13 @@
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import FaceThumbnail from './face-thumbnail.svelte';
@ -133,10 +132,13 @@
</div>
{#if selectedPeople.length === 1}
<div class="absolute bottom-2">
<CircleIconButton
title={$t('swap_merge_direction')}
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('swap_merge_direction')}
icon={mdiSwapHorizontal}
size="24"
size="large"
onclick={handleSwapPeople}
/>
</div>

View File

@ -4,6 +4,7 @@
import { t } from 'svelte-i18n';
import FaceThumbnail from './face-thumbnail.svelte';
import { mdiSwapVertical } from '@mdi/js';
import { IconButton } from '@immich/ui';
interface Props {
screenHeight: number;
@ -31,15 +32,17 @@
</div>
{#if handleSearch}
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiSwapVertical}
onclick={() => {
sortBySimilarirty = !sortBySimilarirty;
handleSearch(sortBySimilarirty);
}}
color="neutral"
title={$t('sort_people_by_similarity')}
></CircleIconButton>
aria-label={$t('sort_people_by_similarity')}
/>
{/if}
</div>

View File

@ -28,6 +28,7 @@
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
import { IconButton } from '@immich/ui';
interface Props {
assetId: string;
@ -196,7 +197,14 @@
>
<div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2">
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} onclick={onClose} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiArrowLeftThin}
aria-label={$t('back')}
onclick={onClose}
/>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('edit_faces')}</p>
</div>
{#if !isShowLoadingDone}
@ -303,22 +311,22 @@
<div class="absolute -end-[5px] -top-[5px] h-[20px] w-[20px] rounded-full">
{#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]}
<CircleIconButton
<IconButton
shape="round"
variant="ghost"
color="primary"
icon={mdiRestart}
title={$t('reset')}
size="18"
padding="1"
aria-label={$t('reset')}
size="medium"
class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
onclick={() => handleReset(face.id)}
/>
{:else}
<CircleIconButton
<IconButton
color="primary"
icon={mdiPencil}
title={$t('select_new_face')}
size="18"
padding="1"
aria-label={$t('select_new_face')}
size="medium"
class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
onclick={() => handleFacePicker(face)}
/>
@ -335,12 +343,12 @@
</div>
{#if face.person != null}
<div class="absolute -end-[5px] top-[25px] h-[20px] w-[20px] rounded-full">
<CircleIconButton
color="red"
<IconButton
shape="round"
color="danger"
icon={mdiTrashCan}
title={$t('delete_face')}
size="18"
padding="1"
aria-label={$t('delete_face')}
size="medium"
class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
onclick={() => deleteAssetFace(face)}
/>

View File

@ -2,7 +2,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk';
import { validate, type LibraryResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiAlertOutline, mdiCheckCircleOutline, mdiPencilOutline, mdiRefresh } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -198,11 +198,12 @@
<td class="w-4/5 text-ellipsis px-4 text-sm">{validatedPath.importPath}</td>
<td class="w-1/5 text-ellipsis flex justify-center">
<CircleIconButton
<IconButton
shape="round"
color="primary"
icon={mdiPencilOutline}
title={$t('edit_import_path')}
size="16"
aria-label={$t('edit_import_path')}
size="small"
onclick={() => {
editImportPath = listIndex;
editedImportPath = validatedPath.importPath;

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { type LibraryResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiPencilOutline } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -130,11 +130,12 @@
>
<td class="w-3/4 text-ellipsis px-4 text-sm">{exclusionPattern}</td>
<td class="w-1/4 text-ellipsis flex justify-center">
<CircleIconButton
<IconButton
shape="round"
color="primary"
icon={mdiPencilOutline}
title={$t('edit_exclusion_pattern')}
size="16"
aria-label={$t('edit_exclusion_pattern')}
size="small"
onclick={() => {
editExclusionPattern = listIndex;
editedExclusionPattern = exclusionPattern;

View File

@ -2,6 +2,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import { copyToClipboard } from '$lib/utils';
import { IconButton } from '@immich/ui';
import { mdiCodeTags, mdiContentCopy, mdiMessage, mdiPartyPopper } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -40,10 +41,11 @@
🚨 {$t('error_title')}
</h1>
<div class="flex justify-end">
<CircleIconButton
<IconButton
shape="round"
color="primary"
icon={mdiContentCopy}
title={$t('copy_error')}
aria-label={$t('copy_error')}
onclick={() => handleCopy()}
/>
</div>

View File

@ -320,7 +320,14 @@
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CreateSharedLink />
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAll}
/>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum />
@ -361,8 +368,11 @@
{/snippet}
<div class="flex place-content-center place-items-center gap-2 overflow-hidden">
<CircleIconButton
title={paused ? $t('play_memories') : $t('pause_memories')}
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={paused ? $t('play_memories') : $t('pause_memories')}
icon={paused ? mdiPlay : mdiPause}
onclick={() => handlePromiseError(handleAction('PlayPauseButtonClick', paused ? 'play' : 'pause'))}
class="hover:text-black"
@ -380,8 +390,11 @@
{(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}
</p>
</div>
<CircleIconButton
title={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
icon={$videoViewerMuted ? mdiVolumeOff : mdiVolumeHigh}
onclick={() => ($videoViewerMuted = !$videoViewerMuted)}
/>
@ -399,7 +412,14 @@
onclick={() => memoryWrapper?.scrollIntoView({ behavior: 'smooth' })}
disabled={!galleryInView}
>
<CircleIconButton title={$t('hide_gallery')} icon={mdiChevronUp} color="light" onclick={() => {}} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('hide_gallery')}
icon={mdiChevronUp}
onclick={() => {}}
/>
</button>
</div>
{/if}
@ -499,11 +519,10 @@
/> -->
<ButtonContextMenu
icon={mdiDotsVertical}
padding="3"
title={$t('menu')}
onclick={() => handlePromiseError(handleAction('ContextMenuClick', 'pause'))}
direction="left"
size="20"
size="medium"
align="bottom-right"
class="text-white dark:text-white"
>
@ -532,10 +551,12 @@
<!-- CONTROL BUTTONS -->
{#if current.previous}
<div class="absolute top-1/2 start-0 ms-4">
<CircleIconButton
title={$t('previous_memory')}
<IconButton
shape="round"
aria-label={$t('previous_memory')}
icon={mdiChevronLeft}
color="dark"
variant="ghost"
color="secondary"
onclick={handlePreviousAsset}
/>
</div>
@ -543,10 +564,12 @@
{#if current.next}
<div class="absolute top-1/2 end-0 me-4">
<CircleIconButton
title={$t('next_memory')}
<IconButton
shape="round"
aria-label={$t('next_memory')}
icon={mdiChevronRight}
color="dark"
variant="ghost"
color="secondary"
onclick={handleNextAsset}
/>
</div>
@ -614,10 +637,12 @@
class:opacity-0={galleryInView}
class:opacity-100={!galleryInView}
>
<CircleIconButton
title={$t('show_gallery')}
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('show_gallery')}
icon={mdiChevronDown}
color="light"
onclick={() => memoryGallery?.scrollIntoView({ behavior: 'smooth' })}
/>
</div>

View File

@ -6,6 +6,7 @@
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { IconButton } from '@immich/ui';
interface Props {
onArchive?: OnArchive;
@ -41,8 +42,15 @@
{#if !menuItem}
{#if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} onclick={() => {}} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<CircleIconButton title={text} {icon} onclick={handleArchive} />
<IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={handleArchive} />
{/if}
{/if}

View File

@ -6,6 +6,7 @@
import { makeSharedLinkUrl } from '$lib/utils';
import { mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { IconButton } from '@immich/ui';
const { getAssets } = getAssetControlContext();
@ -20,4 +21,11 @@
};
</script>
<CircleIconButton title={$t('share')} icon={mdiShareVariantOutline} onclick={handleClick} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('share')}
icon={mdiShareVariantOutline}
onclick={handleClick}
/>

View File

@ -6,6 +6,7 @@
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import DeleteAssetDialog from '../delete-asset-dialog.svelte';
import { IconButton } from '@immich/ui';
interface Props {
onAssetDelete: OnDelete;
@ -44,9 +45,23 @@
{#if menuItem}
<MenuOption text={label} icon={mdiDeleteOutline} onClick={handleTrash} />
{:else if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} onclick={() => {}} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<CircleIconButton title={label} icon={mdiDeleteForeverOutline} onclick={handleTrash} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={label}
icon={mdiDeleteForeverOutline}
onclick={handleTrash}
/>
{/if}
{#if isShowConfirmation}

View File

@ -8,6 +8,7 @@
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { IconButton } from '@immich/ui';
interface Props {
filename?: string;
@ -39,5 +40,12 @@
{#if menuItem}
<MenuOption text={$t('download')} icon={menuItemIcon} onClick={handleDownloadFiles} />
{:else}
<CircleIconButton title={$t('download')} icon={mdiCloudDownloadOutline} onclick={handleDownloadFiles} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('download')}
icon={mdiCloudDownloadOutline}
onclick={handleDownloadFiles}
/>
{/if}

View File

@ -10,6 +10,7 @@
import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { IconButton } from '@immich/ui';
interface Props {
onFavorite?: OnFavorite;
@ -67,8 +68,15 @@
{#if !menuItem}
{#if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} onclick={() => {}} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<CircleIconButton title={text} {icon} onclick={handleFavorite} />
<IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={handleFavorite} />
{/if}
{/if}

View File

@ -9,6 +9,7 @@
import { mdiLinkOff, mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { IconButton } from '@immich/ui';
interface Props {
onLink: OnLink;
@ -75,8 +76,15 @@
{#if !menuItem}
{#if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} onclick={() => {}} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<CircleIconButton title={text} {icon} onclick={onClick} />
<IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={onClick} />
{/if}
{/if}

View File

@ -5,6 +5,7 @@
} from '$lib/components/shared-components/notification/notification';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { getAlbumInfo, removeAssetFromAlbum, type AlbumResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiDeleteOutline, mdiImageRemoveOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
@ -60,5 +61,12 @@
{#if menuItem}
<MenuOption text={$t('remove_from_album')} icon={mdiImageRemoveOutline} onClick={removeFromAlbum} />
{:else}
<CircleIconButton title={$t('remove_from_album')} icon={mdiDeleteOutline} onclick={removeFromAlbum} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('remove_from_album')}
icon={mdiDeleteOutline}
onclick={removeFromAlbum}
/>
{/if}

View File

@ -7,6 +7,7 @@
import { t } from 'svelte-i18n';
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { IconButton } from '@immich/ui';
interface Props {
sharedLink: SharedLinkResponseDto;
@ -58,4 +59,11 @@
};
</script>
<CircleIconButton title={$t('remove_from_shared_link')} onclick={handleRemove} icon={mdiDeleteOutline} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('remove_from_shared_link')}
onclick={handleRemove}
icon={mdiDeleteOutline}
/>

View File

@ -2,7 +2,7 @@
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { type AssetStore, isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { cancelMultiselect, selectAllAssets } from '$lib/utils/asset-utils';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiSelectAll, mdiSelectRemove } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -34,8 +34,11 @@
{$isSelectingAllAssets ? $t('unselect_all') : $t('select_all')}
</Button>
{:else}
<CircleIconButton
title={$isSelectingAllAssets ? $t('unselect_all') : $t('select_all')}
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$isSelectingAllAssets ? $t('unselect_all') : $t('select_all')}
icon={$isSelectingAllAssets ? mdiSelectRemove : mdiSelectAll}
onclick={$isSelectingAllAssets ? handleCancel : handleSelectAll}
/>

View File

@ -3,6 +3,7 @@
import { tagAssets } from '$lib/utils/asset-utils';
import { mdiTagMultipleOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import { IconButton } from '@immich/ui';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
@ -39,9 +40,16 @@
{#if !menuItem}
{#if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} onclick={() => {}} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<CircleIconButton title={text} {icon} onclick={handleOpen} />
<IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={handleOpen} />
{/if}
{/if}

View File

@ -16,13 +16,13 @@
import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js';
import { t } from 'svelte-i18n';
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import DownloadAction from '../photos-page/actions/download-action.svelte';
import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import { IconButton } from '@immich/ui';
interface Props {
sharedLink: SharedLinkResponseDto;
@ -95,7 +95,14 @@
assets={assetInteraction.selectedAssets}
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAll}
/>
{#if sharedLink?.allowDownload}
<DownloadAction filename="immich-shared.zip" />
{/if}
@ -111,15 +118,25 @@
{#snippet trailing()}
{#if sharedLink?.allowUpload}
<CircleIconButton
title={$t('add_photos')}
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('add_photos')}
onclick={() => handleUploadAssets()}
icon={mdiFileImagePlusOutline}
/>
{/if}
{#if sharedLink?.allowDownload}
<CircleIconButton title={$t('download')} onclick={downloadAssets} icon={mdiFolderDownloadOutline} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('download')}
onclick={downloadAssets}
icon={mdiFolderDownloadOutline}
/>
{/if}
{/snippet}
</ControlAppBar>

View File

@ -29,6 +29,7 @@
import { t } from 'svelte-i18n';
import type { FormEventHandler } from 'svelte/elements';
import { fly } from 'svelte/transition';
import { IconButton } from '@immich/ui';
interface Props {
label: string;
@ -329,7 +330,7 @@
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 = 'info',
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;
@ -82,7 +82,16 @@
>
<div class="flex place-items-center sm:gap-6 justify-self-start dark:text-immich-dark-fg">
{#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,6 +1,7 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.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';
@ -36,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

@ -6,7 +6,7 @@
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AvatarEditModal from '$lib/modals/AvatarEditModal.svelte';
import { user } from '$lib/stores/user.store';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
@ -33,13 +33,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

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

View File

@ -7,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';
@ -87,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

@ -14,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;
@ -271,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}
@ -290,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,6 +1,7 @@
<script lang="ts">
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';
@ -132,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

@ -13,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';
@ -129,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,21 @@
<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 { themeManager } from '$lib/managers/theme-manager.svelte';
import { IconButton } from '@immich/ui';
import { mdiWeatherNight, mdiWhiteBalanceSunny } from '@mdi/js';
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();
let icon = $derived(themeManager.isDark ? mdiWhiteBalanceSunny : mdiWeatherNight);
</script>
{#if !themeManager.theme.system}
<CircleIconButton
title={$t('toggle_theme')}
<IconButton
aria-label={$t('toggle_theme')}
{icon}
{viewBox}
role="switch"
aria-checked={themeManager.isDark ? 'true' : 'false'}
onclick={() => themeManager.toggleTheme()}
{padding}
color="secondary"
variant="ghost"
shape="round"
/>
{/if}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { IconButton } from '@immich/ui';
import { mdiArrowUpLeft, mdiChevronRight } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -18,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>
@ -34,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}

View File

@ -2,6 +2,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { copyToClipboard, makeSharedLinkUrl } from '$lib/utils';
import type { SharedLinkResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiContentCopy } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -20,5 +21,12 @@
{#if menuItem}
<MenuOption text={$t('copy_link')} icon={mdiContentCopy} onClick={handleCopy} />
{:else}
<CircleIconButton title={$t('copy_link')} icon={mdiContentCopy} onclick={handleCopy} />
<IconButton
color="secondary"
shape="round"
variant="ghost"
aria-label={$t('copy_link')}
icon={mdiContentCopy}
onclick={handleCopy}
/>
{/if}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { IconButton } from '@immich/ui';
import { mdiDelete } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -14,5 +15,12 @@
{#if menuItem}
<MenuOption text={$t('delete_link')} icon={mdiDelete} onClick={onDelete} />
{:else}
<CircleIconButton title={$t('delete_link')} icon={mdiDelete} onclick={onDelete} />
<IconButton
color="secondary"
shape="round"
variant="ghost"
aria-label={$t('delete_link')}
icon={mdiDelete}
onclick={onDelete}
/>
{/if}

View File

@ -3,6 +3,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants';
import type { SharedLinkResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiCircleEditOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -21,5 +22,12 @@
{#if menuItem}
<MenuOption text={$t('edit_link')} icon={mdiCircleEditOutline} onClick={onEdit} />
{:else}
<CircleIconButton title={$t('edit_link')} icon={mdiCircleEditOutline} onclick={onEdit} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('edit_link')}
icon={mdiCircleEditOutline}
onclick={onEdit}
/>
{/if}

View File

@ -2,6 +2,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store';
import type { SessionResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import {
mdiAndroid,
mdiApple,
@ -74,11 +75,13 @@
</div>
{#if !device.current && onDelete}
<div>
<CircleIconButton
color="primary"
<IconButton
color="danger"
variant="ghost"
shape="round"
icon={mdiTrashCanOutline}
title={$t('log_out')}
size="16"
aria-label={$t('log_out')}
size="small"
onclick={onDelete}
/>
</div>

View File

@ -12,12 +12,11 @@
type PartnerResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiCheck, mdiClose } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { handleError } from '../../utils/handle-error';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import Icon from '../elements/icon.svelte';
interface PartnerSharing {
@ -144,11 +143,14 @@
</div>
{#if partner.sharedByMe}
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
onclick={() => handleRemovePartner(partner.user)}
icon={mdiClose}
size="16"
title={$t('stop_sharing_photos_with_user')}
size="small"
aria-label={$t('stop_sharing_photos_with_user')}
/>
{/if}
</div>

View File

@ -12,7 +12,7 @@
updateApiKey,
type ApiKeyResponseDto,
} from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
@ -127,18 +127,20 @@
>{new Date(key.createdAt).toLocaleDateString($locale, dateFormats.settings)}
</td>
<td class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-1/3">
<CircleIconButton
<IconButton
shape="round"
color="primary"
icon={mdiPencilOutline}
title={$t('edit_key')}
size="16"
aria-label={$t('edit_key')}
size="small"
onclick={() => handleUpdate(key)}
/>
<CircleIconButton
<IconButton
shape="round"
color="primary"
icon={mdiTrashCanOutline}
title={$t('delete_key')}
size="16"
aria-label={$t('delete_key')}
size="small"
onclick={() => handleDelete(key)}
/>
</td>

View File

@ -7,12 +7,11 @@
import { getPeopleThumbnailUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { mergePerson, type PersonResponseDto } from '@immich/sdk';
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
import { Button, IconButton, Modal, ModalBody, ModalFooter } from '@immich/ui';
import { mdiArrowLeft, mdiMerge } from '@mdi/js';
import { onMount, tick } from 'svelte';
import { t } from 'svelte-i18n';
import ImageThumbnail from '../components/assets/thumbnail/image-thumbnail.svelte';
import CircleIconButton from '../components/elements/buttons/circle-icon-button.svelte';
interface Props {
personToMerge: PersonResponseDto;
@ -75,8 +74,11 @@
/>
</div>
<div class="mx-0.5 flex md:mx-2">
<CircleIconButton
title={$t('swap_merge_direction')}
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('swap_merge_direction')}
icon={mdiMerge}
onclick={() => ([personToMerge, personToBeMergedInto] = [personToBeMergedInto, personToMerge])}
/>

View File

@ -68,7 +68,7 @@
updateAlbumInfo,
type AlbumUserAddDto,
} from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, IconButton } from '@immich/ui';
import {
mdiArrowLeft,
mdiCogOutline,
@ -492,10 +492,11 @@
<div class="my-3 flex gap-x-1">
<!-- link -->
{#if album.hasSharedLink && isOwned}
<CircleIconButton
title={$t('create_link_to_share')}
color="gray"
size="20"
<IconButton
aria-label={$t('create_link_to_share')}
color="secondary"
size="medium"
shape="round"
icon={mdiLink}
onclick={handleShareLink}
/>
@ -515,22 +516,24 @@
<!-- display ellipsis if there are readonly users too -->
{#if albumHasViewers}
<CircleIconButton
title={$t('view_all_users')}
color="gray"
size="20"
<IconButton
shape="round"
aria-label={$t('view_all_users')}
color="secondary"
size="medium"
icon={mdiDotsVertical}
onclick={handleEditUsers}
/>
{/if}
{#if isOwned}
<CircleIconButton
color="gray"
size="20"
<IconButton
shape="round"
color="secondary"
size="medium"
icon={mdiPlus}
onclick={handleShare}
title={$t('add_more_users')}
aria-label={$t('add_more_users')}
/>
{/if}
</div>
@ -627,11 +630,14 @@
{#if viewMode === AlbumPageViewMode.VIEW}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(backUrl)}>
{#snippet trailing()}
<CastButton whiteHover />
<CastButton />
{#if isEditor}
<CircleIconButton
title={$t('add_photos')}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label={$t('add_photos')}
onclick={async () => {
assetStore.suspendTransitions = true;
viewMode = AlbumPageViewMode.SELECT_ASSETS;
@ -646,18 +652,44 @@
{/if}
{#if isOwned}
<CircleIconButton title={$t('share')} onclick={handleShare} icon={mdiShareVariantOutline} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('share')}
onclick={handleShare}
icon={mdiShareVariantOutline}
/>
{/if}
<AlbumMap {album} />
{#if album.assetCount > 0}
<CircleIconButton title={$t('slideshow')} onclick={handleStartSlideshow} icon={mdiPresentationPlay} />
<CircleIconButton title={$t('download')} onclick={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('slideshow')}
onclick={handleStartSlideshow}
icon={mdiPresentationPlay}
/>
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={$t('download')}
onclick={handleDownloadAlbum}
icon={mdiFolderDownloadOutline}
/>
{/if}
{#if isOwned}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('album_options')} offset={{ x: 175, y: 25 }}>
<ButtonContextMenu
icon={mdiDotsVertical}
title={$t('album_options')}
color="secondary"
offset={{ x: 175, y: 25 }}
>
{#if album.assetCount > 0}
<MenuOption
icon={mdiImageOutline}

View File

@ -29,6 +29,7 @@
import { cancelMultiselect } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
import { IconButton } from '@immich/ui';
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -134,7 +135,14 @@
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CreateSharedLink />
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAllAssets} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAllAssets}
/>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum onAddToAlbum={() => cancelMultiselect(assetInteraction)} />
<AddToAlbum onAddToAlbum={() => cancelMultiselect(assetInteraction)} shared />

View File

@ -44,6 +44,7 @@
searchSmart,
type SmartSearchDto,
} from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
import { tick } from 'svelte';
import { t } from 'svelte-i18n';
@ -268,7 +269,14 @@
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CreateSharedLink />
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAll}
/>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum {onAddToAlbum} />
<AddToAlbum shared {onAddToAlbum} />
@ -400,7 +408,14 @@
clearSelect={() => cancelMultiselect(assetInteraction)}
>
<CreateSharedLink />
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('select_all')}
icon={mdiSelectAll}
onclick={handleSelectAll}
/>
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum {onAddToAlbum} />
<AddToAlbum shared {onAddToAlbum} />

View File

@ -3,7 +3,7 @@
import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import { Container } from '@immich/ui';
import { Container, IconButton } from '@immich/ui';
import { mdiKeyboard } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
@ -17,9 +17,12 @@
<UserPageLayout title={data.meta.title}>
{#snippet buttons()}
<CircleIconButton
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiKeyboard}
title={$t('show_keyboard_shortcuts')}
aria-label={$t('show_keyboard_shortcuts')}
onclick={() => modalManager.show(ShortcutsModal, {})}
/>
{/snippet}

View File

@ -193,11 +193,13 @@
<div class="text-sm dark:text-white">
<p>{$t('duplicates_description')}</p>
</div>
<CircleIconButton
<IconButton
shape="round"
variant="ghost"
color="secondary"
icon={mdiInformationOutline}
title={$t('deduplication_info')}
size="16"
padding="2"
aria-label={$t('deduplication_info')}
size="small"
onclick={() => modalManager.show(DuplicatesInformationModal, {})}
/>
</div>

View File

@ -314,9 +314,10 @@
align="top-right"
direction="left"
color="primary"
size="16"
size="medium"
icon={mdiDotsVertical}
title={$t('library_options')}
variant="filled"
>
<MenuOption onClick={() => onScanClicked(library)} text={$t('scan_library')} />
<hr />