mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	feat(web): responsive top navigation (#12373)
- icons fit in mobile - guarantee the search bar space in all screen sizes - fix the storage bar being too wide
This commit is contained in:
		
							parent
							
								
									17773f0a77
								
							
						
					
					
						commit
						0a552d2bfa
					
				@ -7,13 +7,14 @@
 | 
			
		||||
  import { preferences, user } from '$lib/stores/user.store';
 | 
			
		||||
  import { handleError } from '$lib/utils/handle-error';
 | 
			
		||||
  import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
 | 
			
		||||
  import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
 | 
			
		||||
  import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
  import { fade } from 'svelte/transition';
 | 
			
		||||
  import { NotificationType, notificationController } from '../notification/notification';
 | 
			
		||||
  import UserAvatar from '../user-avatar.svelte';
 | 
			
		||||
  import AvatarSelector from './avatar-selector.svelte';
 | 
			
		||||
  import { t } from 'svelte-i18n';
 | 
			
		||||
  import { page } from '$app/stores';
 | 
			
		||||
 | 
			
		||||
  let isShowSelectAvatar = false;
 | 
			
		||||
 | 
			
		||||
@ -46,7 +47,7 @@
 | 
			
		||||
  in:fade={{ duration: 100 }}
 | 
			
		||||
  out:fade={{ duration: 100 }}
 | 
			
		||||
  id="account-info-panel"
 | 
			
		||||
  class="absolute right-[25px] top-[75px] z-[100] w-[360px] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
 | 
			
		||||
  class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
 | 
			
		||||
  use:focusTrap
 | 
			
		||||
>
 | 
			
		||||
  <div
 | 
			
		||||
@ -73,19 +74,37 @@
 | 
			
		||||
      <p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <Button
 | 
			
		||||
      href={AppRoute.USER_SETTINGS}
 | 
			
		||||
      on:click={() => dispatch('close')}
 | 
			
		||||
      color="dark-gray"
 | 
			
		||||
      size="sm"
 | 
			
		||||
      shadow={false}
 | 
			
		||||
      border
 | 
			
		||||
    >
 | 
			
		||||
      <div class="flex place-content-center place-items-center gap-2 px-2">
 | 
			
		||||
        <Icon path={mdiCog} size="18" />
 | 
			
		||||
        {$t('account_settings')}
 | 
			
		||||
      </div>
 | 
			
		||||
    </Button>
 | 
			
		||||
    <div class="flex flex-col gap-1">
 | 
			
		||||
      <Button
 | 
			
		||||
        href={AppRoute.USER_SETTINGS}
 | 
			
		||||
        on:click={() => dispatch('close')}
 | 
			
		||||
        color="dark-gray"
 | 
			
		||||
        size="sm"
 | 
			
		||||
        shadow={false}
 | 
			
		||||
        border
 | 
			
		||||
      >
 | 
			
		||||
        <div class="flex place-content-center place-items-center text-center gap-2 px-2">
 | 
			
		||||
          <Icon path={mdiCog} size="18" ariaHidden />
 | 
			
		||||
          {$t('account_settings')}
 | 
			
		||||
        </div>
 | 
			
		||||
      </Button>
 | 
			
		||||
      {#if $user.isAdmin}
 | 
			
		||||
        <Button
 | 
			
		||||
          href={AppRoute.ADMIN_USER_MANAGEMENT}
 | 
			
		||||
          on:click={() => dispatch('close')}
 | 
			
		||||
          color="dark-gray"
 | 
			
		||||
          size="sm"
 | 
			
		||||
          shadow={false}
 | 
			
		||||
          border
 | 
			
		||||
          aria-current={$page.url.pathname.includes('/admin') ? 'page' : undefined}
 | 
			
		||||
        >
 | 
			
		||||
          <div class="flex place-content-center place-items-center text-center gap-2 px-2">
 | 
			
		||||
            <Icon path={mdiWrench} size="18" ariaHidden />
 | 
			
		||||
            {$t('administration')}
 | 
			
		||||
          </div>
 | 
			
		||||
        </Button>
 | 
			
		||||
      {/if}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="mb-4 flex flex-col">
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,10 @@
 | 
			
		||||
  import { user } from '$lib/stores/user.store';
 | 
			
		||||
  import { handleLogout } from '$lib/utils/auth';
 | 
			
		||||
  import { logout } from '@immich/sdk';
 | 
			
		||||
  import { mdiCog, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
 | 
			
		||||
  import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
  import { t } from 'svelte-i18n';
 | 
			
		||||
  import { fade, fly } from 'svelte/transition';
 | 
			
		||||
  import { fade } from 'svelte/transition';
 | 
			
		||||
  import { AppRoute } from '../../../constants';
 | 
			
		||||
  import ImmichLogo from '../immich-logo.svelte';
 | 
			
		||||
  import SearchBar from '../search-bar/search-bar.svelte';
 | 
			
		||||
@ -45,72 +45,41 @@
 | 
			
		||||
    <a data-sveltekit-preload-data="hover" class="ml-4" href={AppRoute.PHOTOS}>
 | 
			
		||||
      <ImmichLogo width="55%" noText={innerWidth < 768} />
 | 
			
		||||
    </a>
 | 
			
		||||
    <div class="flex justify-between gap-16 pr-6">
 | 
			
		||||
      <div class="hidden w-full max-w-5xl flex-1 pl-4 tall:pl-0 sm:block">
 | 
			
		||||
    <div class="flex justify-between gap-4 lg:gap-8 pr-6">
 | 
			
		||||
      <div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block">
 | 
			
		||||
        {#if $featureFlags.search}
 | 
			
		||||
          <SearchBar grayTheme={true} />
 | 
			
		||||
        {/if}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <section class="flex place-items-center justify-end gap-4 max-sm:w-full">
 | 
			
		||||
      <section class="flex place-items-center justify-end gap-2 md:gap-4 w-full sm:w-auto">
 | 
			
		||||
        {#if $featureFlags.search}
 | 
			
		||||
          <CircleIconButton
 | 
			
		||||
            href={AppRoute.SEARCH}
 | 
			
		||||
            id="search-button"
 | 
			
		||||
            class="ml-4 sm:hidden"
 | 
			
		||||
            class="sm:hidden"
 | 
			
		||||
            title={$t('go_to_search')}
 | 
			
		||||
            icon={mdiMagnify}
 | 
			
		||||
            padding="2"
 | 
			
		||||
          />
 | 
			
		||||
        {/if}
 | 
			
		||||
 | 
			
		||||
        <ThemeButton />
 | 
			
		||||
        <ThemeButton padding="2" />
 | 
			
		||||
 | 
			
		||||
        {#if !$page.url.pathname.includes('/admin') && showUploadButton}
 | 
			
		||||
          <div in:fly={{ x: 50, duration: 250 }}>
 | 
			
		||||
            <LinkButton on:click={() => dispatch('uploadClicked')}>
 | 
			
		||||
              <div class="flex gap-2">
 | 
			
		||||
                <Icon path={mdiTrayArrowUp} size="1.5em" />
 | 
			
		||||
                <span class="hidden md:block">{$t('upload')}</span>
 | 
			
		||||
              </div>
 | 
			
		||||
            </LinkButton>
 | 
			
		||||
          </div>
 | 
			
		||||
        {/if}
 | 
			
		||||
 | 
			
		||||
        {#if $user.isAdmin}
 | 
			
		||||
          <a
 | 
			
		||||
            data-sveltekit-preload-data="hover"
 | 
			
		||||
            href={AppRoute.ADMIN_USER_MANAGEMENT}
 | 
			
		||||
            aria-label={$t('administration')}
 | 
			
		||||
            aria-current={$page.url.pathname.includes('/admin') ? 'page' : null}
 | 
			
		||||
          >
 | 
			
		||||
            <div
 | 
			
		||||
              class="inline-flex items-center justify-center transition-colors dark:text-immich-dark-fg p-2 font-medium rounded-lg"
 | 
			
		||||
            >
 | 
			
		||||
              <div class="hidden sm:block">
 | 
			
		||||
                <span
 | 
			
		||||
                  class={$page.url.pathname.includes('/admin')
 | 
			
		||||
                    ? 'item text-immich-primary underline dark:text-immich-dark-primary'
 | 
			
		||||
                    : ''}
 | 
			
		||||
                >
 | 
			
		||||
                  {$t('administration')}
 | 
			
		||||
                </span>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="block sm:hidden" aria-hidden="true">
 | 
			
		||||
                <Icon
 | 
			
		||||
                  path={mdiCog}
 | 
			
		||||
                  size="1.5em"
 | 
			
		||||
                  class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
 | 
			
		||||
                    ? 'text-immich-primary dark:text-immich-dark-primary'
 | 
			
		||||
                    : ''}"
 | 
			
		||||
                />
 | 
			
		||||
                <div
 | 
			
		||||
                  class={$page.url.pathname.includes('/admin')
 | 
			
		||||
                    ? 'border-t-1 mx-auto block w-2/3 border-immich-primary dark:border-immich-dark-primary'
 | 
			
		||||
                    : 'hidden'}
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
          <LinkButton on:click={() => dispatch('uploadClicked')} class="hidden lg:block">
 | 
			
		||||
            <div class="flex gap-2">
 | 
			
		||||
              <Icon path={mdiTrayArrowUp} size="1.5em" />
 | 
			
		||||
              <span>{$t('upload')}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </a>
 | 
			
		||||
          </LinkButton>
 | 
			
		||||
          <CircleIconButton
 | 
			
		||||
            on:click={() => dispatch('uploadClicked')}
 | 
			
		||||
            title={$t('upload')}
 | 
			
		||||
            icon={mdiTrayArrowUp}
 | 
			
		||||
            class="lg:hidden"
 | 
			
		||||
            padding="2"
 | 
			
		||||
          />
 | 
			
		||||
        {/if}
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
@ -129,7 +98,7 @@
 | 
			
		||||
            on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
 | 
			
		||||
          >
 | 
			
		||||
            {#key $user}
 | 
			
		||||
              <UserAvatar user={$user} size="lg" showTitle={false} interactive />
 | 
			
		||||
              <UserAvatar user={$user} size="md" showTitle={false} interactive />
 | 
			
		||||
            {/key}
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@
 | 
			
		||||
  $: hasQuota = $user?.quotaSizeInBytes !== null;
 | 
			
		||||
  $: availableBytes = (hasQuota ? $user?.quotaSizeInBytes : $serverInfo?.diskSizeRaw) || 0;
 | 
			
		||||
  $: usedBytes = (hasQuota ? $user?.quotaUsageInBytes : $serverInfo?.diskUseRaw) || 0;
 | 
			
		||||
  $: usedPercentage = Math.round((usedBytes / availableBytes) * 100);
 | 
			
		||||
  $: usedPercentage = Math.min(Math.round((usedBytes / availableBytes) * 100), 100);
 | 
			
		||||
 | 
			
		||||
  let aboutInfo: ServerAboutResponseDto;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { moonPath, moonViewBox, sunPath, sunViewBox } from '$lib/assets/svg-paths';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
  import CircleIconButton, { type Padding } from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
  import { Theme } from '$lib/constants';
 | 
			
		||||
  import { colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
 | 
			
		||||
  import { t } from 'svelte-i18n';
 | 
			
		||||
@ -8,6 +8,8 @@
 | 
			
		||||
  $: icon = $colorTheme.value === Theme.LIGHT ? moonPath : sunPath;
 | 
			
		||||
  $: viewBox = $colorTheme.value === Theme.LIGHT ? moonViewBox : sunViewBox;
 | 
			
		||||
  $: isDark = $colorTheme.value === Theme.DARK;
 | 
			
		||||
 | 
			
		||||
  export let padding: Padding = '3';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#if !$colorTheme.system}
 | 
			
		||||
@ -18,5 +20,6 @@
 | 
			
		||||
    role="switch"
 | 
			
		||||
    aria-checked={isDark ? 'true' : 'false'}
 | 
			
		||||
    on:click={handleToggleTheme}
 | 
			
		||||
    {padding}
 | 
			
		||||
  />
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user