diff --git a/e2e/src/specs/web/album.e2e-spec.ts b/e2e/src/specs/web/album.e2e-spec.ts
index 953c7d00ae1cd..a1c8cbfafa735 100644
--- a/e2e/src/specs/web/album.e2e-spec.ts
+++ b/e2e/src/specs/web/album.e2e-spec.ts
@@ -1,5 +1,5 @@
import { LoginResponseDto } from '@immich/sdk';
-import { test } from '@playwright/test';
+import { expect, test } from '@playwright/test';
import { utils } from 'src/utils';
test.describe('Album', () => {
@@ -22,4 +22,35 @@ test.describe('Album', () => {
await page.reload();
await page.getByRole('button', { name: 'Select photos' }).waitFor();
});
+
+ test('resizes album title font size based on text and viewport size', async ({ context, page }) => {
+ await utils.setAuthCookies(context, admin.accessToken);
+ await page.setViewportSize({ width: 1100, height: 1025 });
+
+ await page.goto('/albums');
+ await page.getByRole('button', { name: 'Create album' }).click();
+
+ const input = page.getByPlaceholder('Add a title');
+ await expect(input).toBeVisible();
+
+ const getFontSize = async () => Number.parseFloat(await input.evaluate((el) => getComputedStyle(el).fontSize));
+
+ await input.pressSequentially('A small title');
+ await page.waitForTimeout(100);
+ const shortTitleFontSize = await getFontSize();
+
+ await input.clear();
+ await input.pressSequentially(
+ 'A title that is quite large, maybe too large, stop please! A title that is quite large, maybe too large, stop please!',
+ );
+ await page.waitForTimeout(100);
+ const longTitleFontSize = await getFontSize();
+
+ await page.setViewportSize({ width: 600, height: 1025 });
+ await page.waitForTimeout(100);
+ const smallWindowFontSize = await getFontSize();
+
+ expect(shortTitleFontSize).toBeGreaterThan(longTitleFontSize);
+ expect(longTitleFontSize).toBeGreaterThan(smallWindowFontSize);
+ });
});
diff --git a/web/src/lib/components/album-page/album-title.svelte b/web/src/lib/components/album-page/album-title.svelte
index 1ded91dcd40f2..5db30bfe1f79a 100644
--- a/web/src/lib/components/album-page/album-title.svelte
+++ b/web/src/lib/components/album-page/album-title.svelte
@@ -3,6 +3,7 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { updateAlbumInfo } from '@immich/sdk';
+ import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { tv } from 'tailwind-variants';
@@ -16,6 +17,15 @@
let { id, albumName = $bindable(), isOwned, onUpdate }: Props = $props();
let newAlbumName = $derived(albumName);
+ let inputEl: HTMLInputElement;
+ let sizeIndex = $state(0);
+
+ const sizeClasses = [
+ 'text-2xl md:text-4xl lg:text-6xl',
+ 'text-xl md:text-2xl lg:text-4xl',
+ 'text-lg md:text-xl lg:text-2xl',
+ 'text-base md:text-lg lg:text-xl',
+ ];
const handleUpdateName = async () => {
if (newAlbumName === albumName) {
@@ -39,7 +49,7 @@
};
const styles = tv({
- base: 'w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90',
+ base: 'w-[99%] mb-2 border-b-2 border-transparent text-primary outline-none transition-all focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90',
variants: {
isOwned: {
true: 'hover:border-gray-400',
@@ -47,12 +57,49 @@
},
},
});
+
+ const updateTextSize = () => {
+ if (!inputEl) {
+ return;
+ }
+
+ const availableWidth = inputEl.clientWidth;
+ const style = getComputedStyle(inputEl);
+ const span = document.createElement('span');
+
+ span.textContent = inputEl.value || inputEl.placeholder;
+ span.style.fontFamily = style.fontFamily;
+ span.style.fontWeight = style.fontWeight;
+ span.style.fontStyle = style.fontStyle;
+ span.style.letterSpacing = style.letterSpacing;
+ span.style.textTransform = style.textTransform;
+
+ document.body.append(span);
+ sizeIndex = sizeClasses.length - 1;
+
+ for (let i = 0; i < sizeClasses.length; i += 1) {
+ span.className = `${sizeClasses[i]} absolute invisible whitespace-nowrap pointer-events-none`;
+
+ if (Math.ceil(span.getBoundingClientRect().width) <= availableWidth) {
+ sizeIndex = i;
+ break;
+ }
+ }
+
+ span.remove();
+ };
+
+ onMount(updateTextSize);
+
+
e.currentTarget.blur() }}
onblur={handleUpdateName}
- class={styles({ isOwned })}
+ oninput={updateTextSize}
+ class={`${styles({ isOwned })} ${sizeClasses[sizeIndex]}`}
type="text"
bind:value={newAlbumName}
disabled={!isOwned}