This commit is contained in:
Min Idzelis 2025-04-30 03:50:13 +00:00
parent 2e8a286540
commit 9806e94f6f
8 changed files with 69 additions and 58 deletions

View File

@ -34,6 +34,9 @@
--immich-ui-info: 14 165 233; --immich-ui-info: 14 165 233;
--immich-ui-default-border: 209 213 219; --immich-ui-default-border: 209 213 219;
} }
* {
--tw-ring-offset-width: 0px;
}
.dark { .dark {
/* dark */ /* dark */

View File

@ -5,8 +5,8 @@
import { cancelImageUrl } from '$lib/utils/sw-messaging'; import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { TUNABLES } from '$lib/utils/tunables'; import { TUNABLES } from '$lib/utils/tunables';
import { mdiEyeOffOutline } from '@mdi/js'; import { mdiEyeOffOutline } from '@mdi/js';
import type { ClassValue } from 'svelte/elements';
import type { ActionReturn } from 'svelte/action'; import type { ActionReturn } from 'svelte/action';
import type { ClassValue } from 'svelte/elements';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
interface Props { interface Props {
@ -77,7 +77,7 @@
circle && 'rounded-full', circle && 'rounded-full',
shadow && 'shadow-lg', shadow && 'shadow-lg',
(circle || !heightStyle) && 'aspect-square', (circle || !heightStyle) && 'aspect-square',
border && 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary', border && 'border-[3px] border-immich-dark-primary/80 hocus:border-immich-primary',
brokenAssetClass, brokenAssetClass,
] ]
.filter(Boolean) .filter(Boolean)

View File

@ -61,17 +61,17 @@
}: Props = $props(); }: Props = $props();
const colorClasses: Record<Color, string> = { const colorClasses: Record<Color, string> = {
transparent: 'bg-transparent hover:bg-[#d3d3d3] dark:text-immich-dark-fg', transparent: 'bg-transparent hocus:bg-[#d3d3d3] dark:text-immich-dark-fg',
opaque: 'bg-transparent hover:bg-immich-bg/30 text-white hover:dark:text-white', opaque: 'bg-transparent hocus:bg-immich-bg/30 text-white hocus:dark:text-white',
light: 'bg-white hover:bg-[#d3d3d3]', light: 'bg-white hocus:bg-[#d3d3d3]',
red: 'text-red-400 bg-red-100 hover:bg-[#d3d3d3]', red: 'text-red-400 bg-red-100 hocus:bg-[#d3d3d3]',
dark: 'bg-[#202123] hover:bg-[#d3d3d3]', dark: 'bg-[#202123] hocus:bg-[#d3d3d3]',
alert: 'text-[#ff0000] hover:text-white', alert: 'text-[#ff0000] hocus:text-white',
gray: 'bg-[#d3d3d3] hover:bg-[#e2e7e9] text-immich-dark-gray hover:text-black', gray: 'bg-[#d3d3d3] hocus:bg-[#e2e7e9] text-immich-dark-gray hocus:text-black',
neutral: neutral:
'dark:bg-immich-dark-gray dark:text-gray-300 hover:dark:bg-immich-dark-gray/50 hover:dark:text-gray-300 bg-gray-200 text-gray-700 hover:bg-gray-300', 'dark:bg-immich-dark-gray dark:text-gray-300 hocus:dark:bg-immich-dark-gray/50 hocus:dark:text-gray-300 bg-gray-200 text-gray-700 hocus:bg-gray-300',
primary: primary:
'bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 hover:dark:bg-immich-dark-primary/80 text-white dark:text-immich-dark-gray', 'bg-immich-primary dark:bg-immich-dark-primary hocus:bg-immich-primary/75 hocus:dark:bg-immich-dark-primary/80 text-white dark:text-immich-dark-gray',
}; };
const paddingClasses: Record<Padding, string> = { const paddingClasses: Record<Padding, string> = {
@ -92,9 +92,16 @@
{href} {href}
style:width={buttonSize ? buttonSize + 'px' : ''} style:width={buttonSize ? buttonSize + 'px' : ''}
style:height={buttonSize ? buttonSize + 'px' : ''} style:height={buttonSize ? buttonSize + 'px' : ''}
class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all disabled:cursor-default hover:dark:text-immich-dark-gray {className} {mobileClass}" class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all disabled:cursor-default hover:dark:text-immich-dark-gray focus:dark:text-immich-dark-gray outline-none ring-offset-transparent focus:ring-4 {className} {mobileClass}"
{onclick} {onclick}
{...rest} {...rest}
> >
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" /> <Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
</svelte:element> </svelte:element>
<style>
button,
a {
--tw-ring-offset-width: 0px;
}
</style>

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { focusOutside } from '$lib/actions/focus-outside';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import { AppRoute, QueryParameter } from '$lib/constants'; import { AppRoute, QueryParameter } from '$lib/constants';
@ -27,22 +26,13 @@
} }
let { person, onSetBirthDate, onMergePeople, onHidePerson, onToggleFavorite }: Props = $props(); let { person, onSetBirthDate, onMergePeople, onHidePerson, onToggleFavorite }: Props = $props();
let showVerticalDots = $state(false);
</script> </script>
<div <div id="people-card" class="relative group" role="group">
id="people-card"
class="relative"
onmouseenter={() => (showVerticalDots = true)}
onmouseleave={() => (showVerticalDots = false)}
role="group"
use:focusOutside={{ onFocusOut: () => (showVerticalDots = false) }}
>
<a <a
class="focus:outline-none"
href="{AppRoute.PEOPLE}/{person.id}?{QueryParameter.PREVIOUS_ROUTE}={AppRoute.PEOPLE}" href="{AppRoute.PEOPLE}/{person.id}?{QueryParameter.PREVIOUS_ROUTE}={AppRoute.PEOPLE}"
draggable="false" draggable="false"
onfocus={() => (showVerticalDots = true)}
> >
<div class="w-full h-full rounded-xl brightness-95 filter"> <div class="w-full h-full rounded-xl brightness-95 filter">
<ImageThumbnail <ImageThumbnail
@ -61,10 +51,9 @@
</div> </div>
</a> </a>
{#if showVerticalDots}
<div class="absolute top-2 end-2"> <div class="absolute top-2 end-2">
<ButtonContextMenu <ButtonContextMenu
buttonClass="icon-white-drop-shadow focus:opacity-100 {showVerticalDots ? 'opacity-100' : 'opacity-0'}" buttonClass="icon-white-drop-shadow focus:opacity-100 group-hover:opacity-100 opacity-0"
color="opaque" color="opaque"
padding="2" padding="2"
size="20" size="20"
@ -81,5 +70,4 @@
/> />
</ButtonContextMenu> </ButtonContextMenu>
</div> </div>
{/if}
</div> </div>

View File

@ -13,6 +13,7 @@
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { notificationManager } from '$lib/stores/notification-manager.svelte';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { sidebarStore } from '$lib/stores/sidebar.svelte'; import { sidebarStore } from '$lib/stores/sidebar.svelte';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
@ -26,7 +27,6 @@
import ThemeButton from '../theme-button.svelte'; import ThemeButton from '../theme-button.svelte';
import UserAvatar from '../user-avatar.svelte'; import UserAvatar from '../user-avatar.svelte';
import AccountInfoPanel from './account-info-panel.svelte'; import AccountInfoPanel from './account-info-panel.svelte';
import { notificationManager } from '$lib/stores/notification-manager.svelte';
interface Props { interface Props {
showUploadButton?: boolean; showUploadButton?: boolean;
@ -35,6 +35,8 @@
let { showUploadButton = true, onUploadClick }: Props = $props(); let { showUploadButton = true, onUploadClick }: Props = $props();
let isHocus = $state(false);
let shouldShowAccountInfo = $state(false); let shouldShowAccountInfo = $state(false);
let shouldShowAccountInfoPanel = $state(false); let shouldShowAccountInfoPanel = $state(false);
let shouldShowHelpPanel = $state(false); let shouldShowHelpPanel = $state(false);
@ -83,7 +85,11 @@
}} }}
class="sidebar:hidden" class="sidebar:hidden"
/> />
<a data-sveltekit-preload-data="hover" href={AppRoute.PHOTOS}> <a
class="outline-none rounded-xl ring-inset focus:ring-2"
data-sveltekit-preload-data="hover"
href={AppRoute.PHOTOS}
>
<ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} /> <ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} />
</a> </a>
</div> </div>
@ -97,6 +103,7 @@
<section class="flex place-items-center justify-end gap-1 md:gap-2 w-full sm:w-auto"> <section class="flex place-items-center justify-end gap-1 md:gap-2 w-full sm:w-auto">
{#if $featureFlags.search} {#if $featureFlags.search}
<IconButton <IconButton
class="sm:hidden focus:ring-2 ring-offset-transparent focus:bg-dark/10"
color="secondary" color="secondary"
shape="round" shape="round"
variant="ghost" variant="ghost"
@ -104,7 +111,6 @@
icon={mdiMagnify} icon={mdiMagnify}
href={AppRoute.SEARCH} href={AppRoute.SEARCH}
id="search-button" id="search-button"
class="sm:hidden"
aria-label={$t('go_to_search')} aria-label={$t('go_to_search')}
/> />
{/if} {/if}
@ -113,7 +119,7 @@
<Button <Button
leadingIcon={mdiTrayArrowUp} leadingIcon={mdiTrayArrowUp}
onclick={onUploadClick} onclick={onUploadClick}
class="hidden lg:flex" class="hidden lg:flex focus:ring-2 ring-offset-transparent focus:bg-dark/10"
variant="ghost" variant="ghost"
size="medium" size="medium"
color="secondary" color="secondary"
@ -128,7 +134,7 @@
title={$t('upload')} title={$t('upload')}
aria-label={$t('upload')} aria-label={$t('upload')}
icon={mdiTrayArrowUp} icon={mdiTrayArrowUp}
class="lg:hidden" class="lg:hidden focus:ring-2 ring-offset-transparent focus:bg-dark/10"
/> />
{/if} {/if}
@ -140,6 +146,7 @@
}} }}
> >
<IconButton <IconButton
class="focus:inset-ring-2 ring-offset-transparent focus:bg-dark/10"
shape="round" shape="round"
color="secondary" color="secondary"
variant="ghost" variant="ghost"
@ -157,6 +164,7 @@
}} }}
> >
<IconButton <IconButton
class="focus:ring-2 ring-offset-transparent focus:bg-dark/10"
shape="round" shape="round"
color={hasUnreadNotifications ? 'primary' : 'secondary'} color={hasUnreadNotifications ? 'primary' : 'secondary'}
variant="ghost" variant="ghost"
@ -179,11 +187,11 @@
> >
<button <button
type="button" type="button"
class="flex ps-2" class="flex ps-2 outline-none group"
onmouseover={() => (shouldShowAccountInfo = true)} onmouseover={() => ((shouldShowAccountInfo = true), (isHocus = true))}
onfocus={() => (shouldShowAccountInfo = true)} onfocus={() => ((shouldShowAccountInfo = true), (isHocus = true))}
onblur={() => (shouldShowAccountInfo = false)} onblur={() => ((shouldShowAccountInfo = false), (isHocus = false))}
onmouseleave={() => (shouldShowAccountInfo = false)} onmouseleave={() => ((shouldShowAccountInfo = false), (isHocus = false))}
onclick={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)} onclick={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
> >
{#key $user} {#key $user}

View File

@ -84,7 +84,7 @@
let title = $derived(label ?? `${user.name} (${user.email})`); let title = $derived(label ?? `${user.name} (${user.email})`);
let interactiveClass = $derived( let interactiveClass = $derived(
interactive interactive
? 'border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-colors' ? 'group-focus:ring-4 ring-2 ring-immich-primary dark:ring-immich-dark-primary hocus:immich-dark-primary transition-colors'
: '', : '',
); );
</script> </script>

View File

@ -410,7 +410,7 @@
<PeopleInfiniteScroll people={showPeople} hasNextPage={!!nextPage && !searchName} {loadNextPage}> <PeopleInfiniteScroll people={showPeople} hasNextPage={!!nextPage && !searchName} {loadNextPage}>
{#snippet children({ person })} {#snippet children({ person })}
<div <div
class="p-2 rounded-xl hover:bg-gray-200 border-2 hover:border-immich-primary/50 hover:shadow-sm dark:hover:bg-immich-dark-primary/20 hover:dark:border-immich-dark-primary/25 border-transparent transition-all" class="p-2 rounded-xl hocus:bg-gray-200 border-2 hocus-within:border-immich-primary/50 hocus-within:shadow-sm dark:hocus-within:bg-immich-dark-primary/20 dark:border-immich-dark-primary/25 border-transparent transition-all"
> >
<PeopleCard <PeopleCard
{person} {person}
@ -423,7 +423,7 @@
<form onsubmit={() => onNameChangeSubmit(newName, person)}> <form onsubmit={() => onNameChangeSubmit(newName, person)}>
<input <input
type="text" type="text"
class=" bg-white dark:bg-immich-dark-gray border-gray-100 placeholder-gray-400 text-center dark:border-gray-900 w-full rounded-2xl mt-2 py-2 text-sm text-immich-primary dark:text-immich-dark-primary" class="bg-immich-gray dark:bg-immich-dark-gray border-gray-100 placeholder-gray-400 text-center dark:border-gray-900 w-full rounded-2xl mt-2 py-2 text-sm text-immich-primary dark:text-immich-dark-primary"
value={person.name} value={person.name}
placeholder={$t('add_a_name')} placeholder={$t('add_a_name')}
onfocusin={() => onNameChangeInputFocus(person)} onfocusin={() => onNameChangeInputFocus(person)}

View File

@ -73,5 +73,10 @@ export default {
{ values: theme('width') }, { values: theme('width') },
); );
}), }),
plugin(({ addVariant }) => {
addVariant('hocus', ['&:hover', '&:focus']);
addVariant('hocus-within', ['&:hover', '&:focus-within']);
addVariant('hocus-visible', ['&:hover', '&:focus-visible']);
}),
], ],
}; };