Pārlūkot izejas kodu

refactor(web): centralize buttons (#2200)

Michel Heusschen 2 gadi atpakaļ
vecāks
revīzija
ab5b92ae68
42 mainītis faili ar 248 papildinājumiem un 242 dzēšanām
  1. 0 17
      web/src/app.css
  2. 2 5
      web/src/lib/components/admin-page/delete-confirm-dialoge.svelte
  3. 2 5
      web/src/lib/components/admin-page/restore-dialoge.svelte
  4. 3 12
      web/src/lib/components/admin-page/settings/setting-buttons-row.svelte
  5. 1 1
      web/src/lib/components/album-page/album-card.svelte
  6. 7 4
      web/src/lib/components/album-page/album-viewer.svelte
  7. 6 3
      web/src/lib/components/album-page/asset-selection.svelte
  8. 1 1
      web/src/lib/components/album-page/share-info-modal.svelte
  9. 6 3
      web/src/lib/components/album-page/thumbnail-selection.svelte
  10. 4 5
      web/src/lib/components/album-page/user-selection-modal.svelte
  11. 1 1
      web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
  12. 71 0
      web/src/lib/components/elements/buttons/button.svelte
  13. 1 4
      web/src/lib/components/elements/buttons/circle-icon-button.svelte
  14. 14 0
      web/src/lib/components/elements/buttons/icon-button.svelte
  15. 13 0
      web/src/lib/components/elements/buttons/link-button.svelte
  16. 2 1
      web/src/lib/components/forms/admin-registration-form.svelte
  17. 3 11
      web/src/lib/components/forms/api-key-form.svelte
  18. 3 10
      web/src/lib/components/forms/api-key-secret.svelte
  19. 2 1
      web/src/lib/components/forms/change-password-form.svelte
  20. 3 7
      web/src/lib/components/forms/create-user-form.svelte
  21. 3 10
      web/src/lib/components/forms/edit-user-form.svelte
  22. 15 14
      web/src/lib/components/forms/login-form.svelte
  23. 1 1
      web/src/lib/components/share-page/individual-shared-viewer.svelte
  24. 1 1
      web/src/lib/components/shared-components/base-modal.svelte
  25. 3 12
      web/src/lib/components/shared-components/confirm-dialogue.svelte
  26. 1 1
      web/src/lib/components/shared-components/control-app-bar.svelte
  27. 4 17
      web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte
  28. 12 5
      web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
  29. 18 14
      web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
  30. 3 6
      web/src/lib/components/shared-components/theme-button.svelte
  31. 2 4
      web/src/lib/components/shared-components/version-announcement-box.svelte
  32. 1 1
      web/src/lib/components/sharedlinks-page/shared-link-card.svelte
  33. 5 5
      web/src/lib/components/user-settings-page/change-password-settings.svelte
  34. 3 9
      web/src/lib/components/user-settings-page/oauth-settings.svelte
  35. 2 5
      web/src/lib/components/user-settings-page/user-api-key-list.svelte
  36. 2 6
      web/src/lib/components/user-settings-page/user-profile-settings.svelte
  37. 6 8
      web/src/routes/(user)/albums/+page.svelte
  38. 1 1
      web/src/routes/(user)/favorites/+page.svelte
  39. 1 1
      web/src/routes/(user)/photos/+page.svelte
  40. 11 16
      web/src/routes/(user)/sharing/+page.svelte
  41. 4 5
      web/src/routes/+page.svelte
  42. 4 9
      web/src/routes/admin/user-management/+page.svelte

+ 0 - 17
web/src/app.css

@@ -15,7 +15,6 @@
 
 
 :root {
 :root {
 	font-family: 'Work Sans', sans-serif;
 	font-family: 'Work Sans', sans-serif;
-	/* --immich-icon-button-hover-color: #d3d3d3; */
 }
 }
 
 
 html {
 html {
@@ -64,22 +63,6 @@ input:focus-visible {
 		@apply font-medium text-gray-500 dark:text-gray-300;
 		@apply font-medium text-gray-500 dark:text-gray-300;
 	}
 	}
 
 
-	.immich-btn-primary {
-		@apply bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray text-gray-100 border dark:border-immich-dark-gray rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary dark:hover:bg-immich-dark-primary/90 hover:shadow-lg text-sm font-medium;
-	}
-
-	.immich-btn-primary-big {
-		@apply inline-flex justify-center items-center bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray text-white enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/75 disabled:cursor-not-allowed px-6 py-4 rounded-3xl shadow-md w-full font-semibold;
-	}
-
-	.immich-btn-secondary-big {
-		@apply inline-flex justify-center items-center bg-gray-500 dark:bg-gray-200 text-white enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 dark:text-immich-dark-gray disabled:cursor-not-allowed px-6 py-4 rounded-3xl shadow-md w-full font-semibold;
-	}
-
-	.immich-text-button {
-		@apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
-	}
-
 	/* width */
 	/* width */
 	.immich-scrollbar::-webkit-scrollbar {
 	.immich-scrollbar::-webkit-scrollbar {
 		width: 8px;
 		width: 8px;

+ 2 - 5
web/src/lib/components/admin-page/delete-confirm-dialoge.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { api, UserResponseDto } from '@api';
 	import { api, UserResponseDto } from '@api';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -31,11 +32,7 @@
 		</p>
 		</p>
 
 
 		<div class="flex w-full px-4 gap-4 mt-8">
 		<div class="flex w-full px-4 gap-4 mt-8">
-			<button
-				on:click={deleteUser}
-				class="flex-1 transition-colors bg-red-500 hover:bg-red-400 px-6 py-3 text-white rounded-full w-full font-medium"
-				>Confirm
-			</button>
+			<Button fullwidth color="red" on:click={deleteUser}>Confirm</Button>
 		</div>
 		</div>
 	</div>
 	</div>
 </div>
 </div>

+ 2 - 5
web/src/lib/components/admin-page/restore-dialoge.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { api, UserResponseDto } from '@api';
 	import { api, UserResponseDto } from '@api';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -30,11 +31,7 @@
 		</p>
 		</p>
 
 
 		<div class="flex w-full px-4 gap-4 mt-8">
 		<div class="flex w-full px-4 gap-4 mt-8">
-			<button
-				on:click={restoreUser}
-				class="flex-1 transition-colors bg-lime-600 hover:bg-lime-500 px-6 py-3 text-white rounded-full w-full font-medium"
-				>Confirm
-			</button>
+			<Button color="green" fullwidth on:click={restoreUser}>Confirm</Button>
 		</div>
 		</div>
 	</div>
 	</div>
 </div>
 </div>

+ 3 - 12
web/src/lib/components/admin-page/settings/setting-buttons-row.svelte

@@ -1,4 +1,5 @@
 <script lang="ts">
 <script lang="ts">
+	import Button from '$lib/components/elements/buttons/button.svelte';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
@@ -19,17 +20,7 @@
 	</div>
 	</div>
 
 
 	<div class="right">
 	<div class="right">
-		<button
-			on:click={() => dispatch('reset')}
-			class="text-sm bg-gray-500 dark:bg-gray-200 hover:bg-gray-500/75 dark:hover:bg-gray-200/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-			>Reset
-		</button>
-
-		<button
-			type="submit"
-			on:click={() => dispatch('save')}
-			class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-			>Save
-		</button>
+		<Button size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
+		<Button size="sm" on:click={() => dispatch('save')}>Save</Button>
 	</div>
 	</div>
 </div>
 </div>

+ 1 - 1
web/src/lib/components/album-page/album-card.svelte

@@ -15,7 +15,7 @@
 	import { AlbumResponseDto, api, ThumbnailFormat } from '@api';
 	import { AlbumResponseDto, api, ThumbnailFormat } from '@api';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
 	import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
 	import { locale } from '$lib/stores/preferences.store';
 	import { locale } from '$lib/stores/preferences.store';
 
 

+ 7 - 4
web/src/lib/components/album-page/album-viewer.svelte

@@ -17,7 +17,7 @@
 	import AssetSelection from './asset-selection.svelte';
 	import AssetSelection from './asset-selection.svelte';
 	import UserSelectionModal from './user-selection-modal.svelte';
 	import UserSelectionModal from './user-selection-modal.svelte';
 	import ShareInfoModal from './share-info-modal.svelte';
 	import ShareInfoModal from './share-info-modal.svelte';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
 	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
@@ -42,6 +42,7 @@
 	import { locale } from '$lib/stores/preferences.store';
 	import { locale } from '$lib/stores/preferences.store';
 	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
 	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let album: AlbumResponseDto;
 	export let album: AlbumResponseDto;
 	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
@@ -469,12 +470,14 @@
 				{/if}
 				{/if}
 
 
 				{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
 				{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
-					<button
+					<Button
+						size="sm"
+						rounded="lg"
 						disabled={album.assetCount == 0}
 						disabled={album.assetCount == 0}
 						on:click={() => (isShowShareUserSelection = true)}
 						on:click={() => (isShowShareUserSelection = true)}
-						class="immich-text-button border bg-immich-primary dark:bg-immich-dark-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed dark:text-immich-dark-bg dark:border-immich-dark-gray"
-						><span class="px-2">Share</span></button
 					>
 					>
+						Share
+					</Button>
 				{/if}
 				{/if}
 			</svelte:fragment>
 			</svelte:fragment>
 		</ControlAppBar>
 		</ControlAppBar>

+ 6 - 3
web/src/lib/components/album-page/asset-selection.svelte

@@ -12,6 +12,7 @@
 		selectedAssets
 		selectedAssets
 	} from '$lib/stores/asset-interaction.store';
 	} from '$lib/stores/asset-interaction.store';
 	import { locale } from '$lib/stores/preferences.store';
 	import { locale } from '$lib/stores/preferences.store';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 
 
@@ -63,12 +64,14 @@
 			>
 			>
 				Select from computer
 				Select from computer
 			</button>
 			</button>
-			<button
+			<Button
+				size="sm"
+				rounded="lg"
 				disabled={$selectedAssets.size === 0}
 				disabled={$selectedAssets.size === 0}
 				on:click={addSelectedAssets}
 				on:click={addSelectedAssets}
-				class="immich-text-button border bg-immich-primary dark:bg-immich-dark-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed dark:text-immich-dark-bg dark:border-immich-dark-gray"
-				><span class="px-2">Done</span></button
 			>
 			>
+				Done
+			</Button>
 		</svelte:fragment>
 		</svelte:fragment>
 	</ControlAppBar>
 	</ControlAppBar>
 	<section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg dark:bg-immich-dark-bg">
 	<section class="pt-[100px] pl-[70px] grid h-screen bg-immich-bg dark:bg-immich-dark-bg">

+ 1 - 1
web/src/lib/components/album-page/share-info-modal.svelte

@@ -4,7 +4,7 @@
 	import BaseModal from '../shared-components/base-modal.svelte';
 	import BaseModal from '../shared-components/base-modal.svelte';
 	import CircleAvatar from '../shared-components/circle-avatar.svelte';
 	import CircleAvatar from '../shared-components/circle-avatar.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import {
 	import {

+ 6 - 3
web/src/lib/components/album-page/thumbnail-selection.svelte

@@ -5,6 +5,7 @@
 	import { fly } from 'svelte/transition';
 	import { fly } from 'svelte/transition';
 	import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
 	import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let album: AlbumResponseDto;
 	export let album: AlbumResponseDto;
 
 
@@ -30,12 +31,14 @@
 		</svelte:fragment>
 		</svelte:fragment>
 
 
 		<svelte:fragment slot="trailing">
 		<svelte:fragment slot="trailing">
-			<button
+			<Button
+				size="sm"
+				rounded="lg"
 				disabled={selectedThumbnail == undefined}
 				disabled={selectedThumbnail == undefined}
 				on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })}
 				on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })}
-				class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
-				><span class="px-2">Done</span></button
 			>
 			>
+				Done
+			</Button>
 		</svelte:fragment>
 		</svelte:fragment>
 	</ControlAppBar>
 	</ControlAppBar>
 
 

+ 4 - 5
web/src/lib/components/album-page/user-selection-modal.svelte

@@ -7,6 +7,7 @@
 	import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
 	import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let album: AlbumResponseDto;
 	export let album: AlbumResponseDto;
 	export let sharedUsersInAlbum: Set<UserResponseDto>;
 	export let sharedUsersInAlbum: Set<UserResponseDto>;
@@ -117,11 +118,9 @@
 
 
 		{#if selectedUsers.length > 0}
 		{#if selectedUsers.length > 0}
 			<div class="flex place-content-end p-5 ">
 			<div class="flex place-content-end p-5 ">
-				<button
-					on:click={() => dispatch('add-user', { selectedUsers })}
-					class="text-white bg-immich-primary px-4 py-2 rounded-lg text-sm font-bold transition-colors hover:bg-immich-primary/75"
-					>Add</button
-				>
+				<Button size="sm" rounded="lg" on:click={() => dispatch('add-user', { selectedUsers })}>
+					Add
+				</Button>
 			</div>
 			</div>
 		{/if}
 		{/if}
 	</div>
 	</div>

+ 1 - 1
web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte

@@ -6,7 +6,7 @@
 	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
 	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import Star from 'svelte-material-icons/Star.svelte';
 	import Star from 'svelte-material-icons/Star.svelte';

+ 71 - 0
web/src/lib/components/elements/buttons/button.svelte

@@ -0,0 +1,71 @@
+<script lang="ts" context="module">
+	export type Type = 'button' | 'submit' | 'reset';
+	export type Color =
+		| 'primary'
+		| 'secondary'
+		| 'transparent-primary'
+		| 'light-red'
+		| 'red'
+		| 'green'
+		| 'gray'
+		| 'transparent-gray'
+		| 'dark-gray';
+	export type Size = 'icon' | 'link' | 'sm' | 'base' | 'lg';
+	export type Rounded = 'lg' | '3xl' | 'full' | false;
+	export type Shadow = 'md' | false;
+</script>
+
+<script lang="ts">
+	export let type: Type = 'button';
+	export let color: Color = 'primary';
+	export let size: Size = 'base';
+	export let rounded: Rounded = '3xl';
+	export let shadow: Shadow = 'md';
+	export let disabled = false;
+	export let fullwidth = false;
+	export let border = false;
+	export let title: string | undefined = '';
+
+	const colorClasses: Record<Color, string> = {
+		primary:
+			'bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray enabled:dark:hover:bg-immich-dark-primary/80 enabled:hover:bg-immich-primary/90',
+		secondary:
+			'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90',
+		'transparent-primary':
+			'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700',
+		'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50',
+		red: 'bg-red-500 text-white enabled:hover:bg-red-400',
+		green: 'bg-lime-600 text-white enabled:hover:bg-lime-500',
+		gray: 'bg-gray-500 dark:bg-gray-200 enabled:hover:bg-gray-500/75 enabled:dark:hover:bg-gray-200/80 text-white dark:text-immich-dark-gray',
+		'transparent-gray':
+			'dark:text-immich-dark-fg enabled:hover:bg-immich-primary/5 enabled:hover:text-gray-700 enabled:hover:dark:text-immich-dark-fg enabled:dark:hover:bg-immich-dark-primary/25 ',
+		'dark-gray':
+			'dark:border-immich-dark-gray dark:bg-gray-500 enabled:dark:hover:bg-immich-dark-primary/50 enabled:hover:bg-immich-primary/10 dark:text-white'
+	};
+
+	const sizeClasses: Record<Size, string> = {
+		icon: 'p-2.5',
+		link: 'p-2 font-medium',
+		sm: 'px-4 py-2 text-sm font-medium',
+		base: 'px-6 py-3 font-medium',
+		lg: 'px-6 py-4 font-semibold'
+	};
+</script>
+
+<button
+	{type}
+	{disabled}
+	{title}
+	on:click
+	class="inline-flex justify-center items-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 {colorClasses[
+		color
+	]} {sizeClasses[size]}"
+	class:rounded-lg={rounded === 'lg'}
+	class:rounded-3xl={rounded === '3xl'}
+	class:rounded-full={rounded === 'full'}
+	class:shadow-md={shadow === 'md'}
+	class:w-full={fullwidth}
+	class:border
+>
+	<slot />
+</button>

+ 1 - 4
web/src/lib/components/shared-components/circle-icon-button.svelte → web/src/lib/components/elements/buttons/circle-icon-button.svelte

@@ -1,7 +1,4 @@
 <script lang="ts">
 <script lang="ts">
-	/**
-	 * This is the circle icon component.
-	 */
 	import type Icon from 'svelte-material-icons/AbTesting.svelte';
 	import type Icon from 'svelte-material-icons/AbTesting.svelte';
 
 
 	export let logo: typeof Icon;
 	export let logo: typeof Icon;
@@ -15,7 +12,7 @@
 	{title}
 	{title}
 	style:backgroundColor
 	style:backgroundColor
 	style:--immich-icon-button-hover-color={hoverColor}
 	style:--immich-icon-button-hover-color={hoverColor}
-	class={`immich-circle-icon-button dark:text-immich-dark-fg hover:dark:text-immich-dark-gray rounded-full p-3 flex place-items-center place-content-center transition-all`}
+	class="immich-circle-icon-button dark:text-immich-dark-fg hover:dark:text-immich-dark-gray rounded-full p-3 flex place-items-center place-content-center transition-all"
 	on:click
 	on:click
 >
 >
 	<svelte:component this={logo} {size} />
 	<svelte:component this={logo} {size} />

+ 14 - 0
web/src/lib/components/elements/buttons/icon-button.svelte

@@ -0,0 +1,14 @@
+<script lang="ts" context="module">
+	export type Color = 'transparent-primary' | 'transparent-gray';
+</script>
+
+<script lang="ts">
+	import Button from './button.svelte';
+
+	export let color: Color = 'transparent-primary';
+	export let title: string | undefined = undefined;
+</script>
+
+<Button size="icon" {color} {title} shadow={false} rounded="full" on:click>
+	<slot />
+</Button>

+ 13 - 0
web/src/lib/components/elements/buttons/link-button.svelte

@@ -0,0 +1,13 @@
+<script lang="ts" context="module">
+	export type Color = 'transparent-primary' | 'transparent-gray';
+</script>
+
+<script lang="ts">
+	import Button from './button.svelte';
+
+	export let color: Color = 'transparent-gray';
+</script>
+
+<Button size="link" {color} shadow={false} rounded="lg" on:click>
+	<slot />
+</Button>

+ 2 - 1
web/src/lib/components/forms/admin-registration-form.svelte

@@ -2,6 +2,7 @@
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import { AppRoute } from '$lib/constants';
 	import { AppRoute } from '$lib/constants';
 	import { api } from '@api';
 	import { api } from '@api';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	let error: string;
 	let error: string;
 	let password = '';
 	let password = '';
@@ -115,6 +116,6 @@
 	{/if}
 	{/if}
 
 
 	<div class="my-5 flex w-full">
 	<div class="my-5 flex w-full">
-		<button type="submit" class="immich-btn-primary-big">Sign Up</button>
+		<Button type="submit" size="lg" fullwidth>Sign up</Button>
 	</div>
 	</div>
 </form>
 </form>

+ 3 - 11
web/src/lib/components/forms/api-key-form.svelte

@@ -3,6 +3,7 @@
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 	import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
 	import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
 	import FullScreenModal from '../shared-components/full-screen-modal.svelte';
 	import FullScreenModal from '../shared-components/full-screen-modal.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let apiKey: Partial<APIKeyResponseDto>;
 	export let apiKey: Partial<APIKeyResponseDto>;
 	export let title = 'API Key';
 	export let title = 'API Key';
@@ -40,17 +41,8 @@
 			</div>
 			</div>
 
 
 			<div class="flex w-full px-4 gap-4 mt-8">
 			<div class="flex w-full px-4 gap-4 mt-8">
-				<button
-					type="button"
-					on:click={() => handleCancel()}
-					class="flex-1 transition-colors bg-gray-500 dark:bg-gray-200 hover:bg-gray-500/75 dark:hover:bg-gray-200/80 px-6 py-3 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium"
-					>{cancelText}
-				</button>
-				<button
-					type="submit"
-					class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-					>{submitText}</button
-				>
+				<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
+				<Button type="submit" fullwidth>{submitText}</Button>
 			</div>
 			</div>
 		</form>
 		</form>
 	</div>
 	</div>

+ 3 - 10
web/src/lib/components/forms/api-key-secret.svelte

@@ -7,6 +7,7 @@
 		notificationController,
 		notificationController,
 		NotificationType
 		NotificationType
 	} from '../shared-components/notification/notification';
 	} from '../shared-components/notification/notification';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let secret = '';
 	export let secret = '';
 
 
@@ -54,16 +55,8 @@
 		</div>
 		</div>
 
 
 		<div class="flex w-full px-4 gap-4 mt-8">
 		<div class="flex w-full px-4 gap-4 mt-8">
-			<button
-				on:click={() => handleCopy()}
-				class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-				>Copy to Clipboard</button
-			>
-			<button
-				on:click={() => handleDone()}
-				class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-				>Done</button
-			>
+			<Button on:click={() => handleCopy()} fullwidth>Copy to Clipboard</Button>
+			<Button on:click={() => handleDone()} fullwidth>Done</Button>
 		</div>
 		</div>
 	</div>
 	</div>
 </FullScreenModal>
 </FullScreenModal>

+ 2 - 1
web/src/lib/components/forms/change-password-form.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { api, UserResponseDto } from '@api';
 	import { api, UserResponseDto } from '@api';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 	let error: string;
 	let error: string;
@@ -78,6 +79,6 @@
 		<p class="text-immich-primary text-sm">{success}</p>
 		<p class="text-immich-primary text-sm">{success}</p>
 	{/if}
 	{/if}
 	<div class="my-5 flex w-full">
 	<div class="my-5 flex w-full">
-		<button type="submit" class="immich-btn-primary-big">Change Password</button>
+		<Button type="submit" size="lg" fullwidth>Change password</Button>
 	</div>
 	</div>
 </form>
 </form>

+ 3 - 7
web/src/lib/components/forms/create-user-form.svelte

@@ -6,6 +6,7 @@
 		notificationController,
 		notificationController,
 		NotificationType
 		NotificationType
 	} from '../shared-components/notification/notification';
 	} from '../shared-components/notification/notification';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	let error: string;
 	let error: string;
 	let success: string;
 	let success: string;
@@ -140,13 +141,8 @@
 		{#if success}
 		{#if success}
 			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 		{/if}
 		{/if}
-		<div class="flex w-full">
-			<button
-				type="submit"
-				class="m-4 bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-6 py-3 text-white dark:text-immich-dark-gray rounded-full shadow-md w-full font-medium"
-				disabled={isCreatingUser}
-				>Create
-			</button>
+		<div class="flex w-full p-4">
+			<Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button>
 		</div>
 		</div>
 	</form>
 	</form>
 </div>
 </div>

+ 3 - 10
web/src/lib/components/forms/edit-user-form.svelte

@@ -6,6 +6,7 @@
 		notificationController,
 		notificationController,
 		NotificationType
 		NotificationType
 	} from '../shared-components/notification/notification';
 	} from '../shared-components/notification/notification';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -112,16 +113,8 @@
 			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 		{/if}
 		{/if}
 		<div class="flex w-full px-4 gap-4 mt-8">
 		<div class="flex w-full px-4 gap-4 mt-8">
-			<button
-				on:click={resetPassword}
-				class="flex-1 transition-colors bg-[#F9DEDC] hover:bg-red-50 text-[#410E0B] px-6 py-3 rounded-full w-full font-medium"
-				>Reset password
-			</button>
-			<button
-				type="submit"
-				class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-				>Confirm
-			</button>
+			<Button color="light-red" fullwidth on:click={resetPassword}>Reset password</Button>
+			<Button type="submit" fullwidth>Confirm</Button>
 		</div>
 		</div>
 	</form>
 	</form>
 </div>
 </div>

+ 15 - 14
web/src/lib/components/forms/login-form.svelte

@@ -6,6 +6,7 @@
 	import { api, oauth, OAuthConfigResponseDto } from '@api';
 	import { api, oauth, OAuthConfigResponseDto } from '@api';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { fade } from 'svelte/transition';
 	import { fade } from 'svelte/transition';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	let error: string;
 	let error: string;
 	let email = '';
 	let email = '';
@@ -110,22 +111,20 @@
 		</div>
 		</div>
 
 
 		<div class="my-5 flex w-full">
 		<div class="my-5 flex w-full">
-			<button
-				type="submit"
-				class="immich-btn-primary-big inline-flex items-center h-14"
-				disabled={loading || oauthLoading}
-			>
+			<Button type="submit" size="lg" fullwidth disabled={loading || oauthLoading}>
 				{#if loading}
 				{#if loading}
-					<LoadingSpinner />
+					<span class="h-6">
+						<LoadingSpinner />
+					</span>
 				{:else}
 				{:else}
 					Login
 					Login
 				{/if}
 				{/if}
-			</button>
+			</Button>
 		</div>
 		</div>
 	</form>
 	</form>
 {/if}
 {/if}
 
 
-{#if authConfig.enabled}
+{#if !authConfig.enabled}
 	{#if authConfig.passwordLoginEnabled}
 	{#if authConfig.passwordLoginEnabled}
 		<div class="inline-flex items-center justify-center w-full">
 		<div class="inline-flex items-center justify-center w-full">
 			<hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
 			<hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
@@ -141,19 +140,21 @@
 			<p class="text-red-400" transition:fade>{oauthError}</p>
 			<p class="text-red-400" transition:fade>{oauthError}</p>
 		{/if}
 		{/if}
 		<a href={authConfig.url} class="flex w-full">
 		<a href={authConfig.url} class="flex w-full">
-			<button
+			<Button
 				type="button"
 				type="button"
 				disabled={loading || oauthLoading}
 				disabled={loading || oauthLoading}
-				class={'inline-flex items-center h-14 ' + authConfig.passwordLoginEnabled
-					? 'immich-btn-secondary-big'
-					: 'immich-btn-primary-big'}
+				size="lg"
+				fullwidth
+				color={authConfig.passwordLoginEnabled ? 'secondary' : 'primary'}
 			>
 			>
 				{#if oauthLoading}
 				{#if oauthLoading}
-					<LoadingSpinner />
+					<span class="h-6">
+						<LoadingSpinner />
+					</span>
 				{:else}
 				{:else}
 					{authConfig.buttonText || 'Login with OAuth'}
 					{authConfig.buttonText || 'Login with OAuth'}
 				{/if}
 				{/if}
-			</button>
+			</Button>
 		</a>
 		</a>
 	</div>
 	</div>
 {/if}
 {/if}

+ 1 - 1
web/src/lib/components/share-page/individual-shared-viewer.svelte

@@ -4,7 +4,7 @@
 	import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
 	import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
 	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
 	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
 	import { openFileUploadDialog } from '$lib/utils/file-uploader';
 	import { openFileUploadDialog } from '$lib/utils/file-uploader';

+ 1 - 1
web/src/lib/components/shared-components/base-modal.svelte

@@ -4,7 +4,7 @@
 	import Close from 'svelte-material-icons/Close.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import { createEventDispatcher, onMount, onDestroy } from 'svelte';
 	import { createEventDispatcher, onMount, onDestroy } from 'svelte';
 	import { browser } from '$app/environment';
 	import { browser } from '$app/environment';
-	import CircleIconButton from './circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import { clickOutside } from '$lib/utils/click-outside';
 	import { clickOutside } from '$lib/utils/click-outside';
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();

+ 3 - 12
web/src/lib/components/shared-components/confirm-dialogue.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 	import FullScreenModal from './full-screen-modal.svelte';
 	import FullScreenModal from './full-screen-modal.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let title = 'Confirm';
 	export let title = 'Confirm';
 	export let prompt = 'Are you sure you want to do this?';
 	export let prompt = 'Are you sure you want to do this?';
@@ -29,18 +30,8 @@
 			</slot>
 			</slot>
 
 
 			<div class="flex w-full px-4 gap-4 mt-4">
 			<div class="flex w-full px-4 gap-4 mt-4">
-				<button
-					on:click={() => handleCancel()}
-					class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-				>
-					{cancelText}
-				</button>
-				<button
-					on:click={() => handleConfirm()}
-					class="flex-1 transition-colors bg-red-500 hover:bg-red-400 px-6 py-3 text-white rounded-full w-full font-medium"
-				>
-					{confirmText}
-				</button>
+				<Button fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
+				<Button color="red" fullwidth on:click={() => handleConfirm()}>{confirmText}</Button>
 			</div>
 			</div>
 		</div>
 		</div>
 	</div>
 	</div>

+ 1 - 1
web/src/lib/components/shared-components/control-app-bar.svelte

@@ -3,7 +3,7 @@
 
 
 	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
 	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import { fly } from 'svelte/transition';
 	import { fly } from 'svelte/transition';
 
 
 	export let showBackButton = true;
 	export let showBackButton = true;

+ 4 - 17
web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte

@@ -17,6 +17,7 @@
 		SettingInputFieldType
 		SettingInputFieldType
 	} from '$lib/components/admin-page/settings/setting-input-field.svelte';
 	} from '$lib/components/admin-page/settings/setting-input-field.svelte';
 	import { handleError } from '$lib/utils/handle-error';
 	import { handleError } from '$lib/utils/handle-error';
+	import Button from '$lib/components/elements/buttons/button.svelte';
 
 
 	export let shareType: SharedLinkType;
 	export let shareType: SharedLinkType;
 	export let sharedAssets: AssetResponseDto[] = [];
 	export let sharedAssets: AssetResponseDto[] = [];
@@ -243,21 +244,11 @@
 		{#if !isShowSharedLink}
 		{#if !isShowSharedLink}
 			{#if editingLink}
 			{#if editingLink}
 				<div class="flex justify-end">
 				<div class="flex justify-end">
-					<button
-						on:click={handleEditLink}
-						class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
-					>
-						Confirm
-					</button>
+					<Button size="sm" rounded="lg" on:click={handleEditLink}>Confirm</Button>
 				</div>
 				</div>
 			{:else}
 			{:else}
 				<div class="flex justify-end">
 				<div class="flex justify-end">
-					<button
-						on:click={handleCreateSharedLink}
-						class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
-					>
-						Create Link
-					</button>
+					<Button size="sm" rounded="lg" on:click={handleCreateSharedLink}>Create link</Button>
 				</div>
 				</div>
 			{/if}
 			{/if}
 		{/if}
 		{/if}
@@ -266,11 +257,7 @@
 			<div class="flex w-full gap-4">
 			<div class="flex w-full gap-4">
 				<input class="immich-form-input w-full" bind:value={sharedLink} disabled />
 				<input class="immich-form-input w-full" bind:value={sharedLink} disabled />
 
 
-				<button
-					on:click={() => handleCopy()}
-					class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-2 text-white rounded-full shadow-md w-full font-medium"
-					>Copy</button
-				>
+				<Button on:click={() => handleCopy()}>Copy</Button>
 			</div>
 			</div>
 		{/if}
 		{/if}
 	</section>
 	</section>

+ 12 - 5
web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte

@@ -7,6 +7,7 @@
 	import Cog from 'svelte-material-icons/Cog.svelte';
 	import Cog from 'svelte-material-icons/Cog.svelte';
 	import Logout from 'svelte-material-icons/Logout.svelte';
 	import Logout from 'svelte-material-icons/Logout.svelte';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
+	import Button from '$lib/components/elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -59,16 +60,22 @@
 
 
 		<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
 		<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
 
 
-		<div class=" mt-4 flex place-items-center place-content-center">
-			<button
-				class="flex border rounded-3xl px-6 py-2 hover:bg-immich-primary/10 dark:border-immich-dark-gray dark:bg-gray-500 dark:hover:bg-immich-dark-primary/50 dark:text-white font-medium place-items-center place-content-center gap-2"
+		<div class="mt-4">
+			<Button
+				color="dark-gray"
+				size="sm"
+				shadow={false}
+				border
 				on:click={() => {
 				on:click={() => {
 					goto('/user-settings');
 					goto('/user-settings');
 					dispatch('close');
 					dispatch('close');
 				}}
 				}}
 			>
 			>
-				<span><Cog size="18" /></span>Account Settings</button
-			>
+				<div class="flex gap-2 place-items-center place-content-center px-2">
+					<Cog size="18" />
+					Account Settings
+				</div>
+			</Button>
 		</div>
 		</div>
 	</div>
 	</div>
 
 

+ 18 - 14
web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte

@@ -10,6 +10,7 @@
 	import AccountInfoPanel from './account-info-panel.svelte';
 	import AccountInfoPanel from './account-info-panel.svelte';
 	import ImmichLogo from '../immich-logo.svelte';
 	import ImmichLogo from '../immich-logo.svelte';
 	import SearchBar from '../search-bar/search-bar.svelte';
 	import SearchBar from '../search-bar/search-bar.svelte';
+	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 	export let shouldShowUploadButton = true;
 	export let shouldShowUploadButton = true;
 
 
@@ -61,24 +62,27 @@
 				<ThemeButton />
 				<ThemeButton />
 
 
 				{#if !$page.url.pathname.includes('/admin') && shouldShowUploadButton}
 				{#if !$page.url.pathname.includes('/admin') && shouldShowUploadButton}
-					<button
-						in:fly={{ x: 50, duration: 250 }}
-						on:click={() => dispatch('uploadClicked')}
-						class="immich-text-button dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
-					>
-						<TrayArrowUp size="20" />
-						<span> Upload </span>
-					</button>
+					<div in:fly={{ x: 50, duration: 250 }}>
+						<LinkButton on:click={() => dispatch('uploadClicked')}>
+							<div class="flex gap-2">
+								<TrayArrowUp size="20" />
+								<span>Upload</span>
+							</div>
+						</LinkButton>
+					</div>
 				{/if}
 				{/if}
 
 
 				{#if user.isAdmin}
 				{#if user.isAdmin}
 					<a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
 					<a data-sveltekit-preload-data="hover" href={AppRoute.ADMIN_USER_MANAGEMENT}>
-						<button
-							class={`flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5  dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg p-2 rounded-lg font-medium ${
-								$page.url.pathname.includes('/admin') &&
-								'text-immich-primary dark:immich-dark-primary underline'
-							}`}>Administration</button
-						>
+						<LinkButton>
+							<span
+								class={$page.url.pathname.includes('/admin')
+									? 'text-immich-primary dark:text-immich-dark-primary underline'
+									: ''}
+							>
+								Administration
+							</span>
+						</LinkButton>
 					</a>
 					</a>
 				{/if}
 				{/if}
 
 

+ 3 - 6
web/src/lib/components/shared-components/theme-button.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { browser } from '$app/environment';
 	import { browser } from '$app/environment';
 	import { colorTheme } from '$lib/stores/preferences.store';
 	import { colorTheme } from '$lib/stores/preferences.store';
+	import IconButton from '../elements/buttons/icon-button.svelte';
 
 
 	const toggleTheme = () => {
 	const toggleTheme = () => {
 		$colorTheme = $colorTheme === 'dark' ? 'light' : 'dark';
 		$colorTheme = $colorTheme === 'dark' ? 'light' : 'dark';
@@ -17,11 +18,7 @@
 	}
 	}
 </script>
 </script>
 
 
-<button
-	on:click={toggleTheme}
-	type="button"
-	class="text-gray-500 dark:text-immich-dark-primary hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none rounded-full p-2.5"
->
+<IconButton on:click={toggleTheme} title="Toggle theme">
 	{#if $colorTheme === 'light'}
 	{#if $colorTheme === 'light'}
 		<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
 		<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
 			><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /></svg
 			><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /></svg
@@ -35,4 +32,4 @@
 			/></svg
 			/></svg
 		>
 		>
 	{/if}
 	{/if}
-</button>
+</IconButton>

+ 2 - 4
web/src/lib/components/shared-components/version-announcement-box.svelte

@@ -3,6 +3,7 @@
 	import { onMount } from 'svelte';
 	import { onMount } from 'svelte';
 	import FullScreenModal from './full-screen-modal.svelte';
 	import FullScreenModal from './full-screen-modal.svelte';
 	import type { ServerVersionReponseDto } from '@api';
 	import type { ServerVersionReponseDto } from '@api';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let serverVersion: ServerVersionReponseDto;
 	export let serverVersion: ServerVersionReponseDto;
 
 
@@ -72,10 +73,7 @@
 			</div>
 			</div>
 
 
 			<div class="text-right mt-8">
 			<div class="text-right mt-8">
-				<button
-					class="transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-					on:click={onAcknowledge}>Acknowledge</button
-				>
+				<Button fullwidth on:click={onAcknowledge}>Acknowledge</Button>
 			</div>
 			</div>
 		</div>
 		</div>
 	</FullScreenModal>
 	</FullScreenModal>

+ 1 - 1
web/src/lib/components/sharedlinks-page/shared-link-card.svelte

@@ -6,7 +6,7 @@
 	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
 	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
 	import CircleEditOutline from 'svelte-material-icons/CircleEditOutline.svelte';
 	import CircleEditOutline from 'svelte-material-icons/CircleEditOutline.svelte';
 	import * as luxon from 'luxon';
 	import * as luxon from 'luxon';
-	import CircleIconButton from '../shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 
 

+ 5 - 5
web/src/lib/components/user-settings-page/change-password-settings.svelte

@@ -8,6 +8,7 @@
 	import SettingInputField, {
 	import SettingInputField, {
 		SettingInputFieldType
 		SettingInputFieldType
 	} from '../admin-page/settings/setting-input-field.svelte';
 	} from '../admin-page/settings/setting-input-field.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	let password = '';
 	let password = '';
 	let newPassword = '';
 	let newPassword = '';
@@ -64,13 +65,12 @@
 				/>
 				/>
 
 
 				<div class="flex justify-end">
 				<div class="flex justify-end">
-					<button
+					<Button
 						type="submit"
 						type="submit"
+						size="sm"
 						disabled={!(password && newPassword && newPassword === confirmPassword)}
 						disabled={!(password && newPassword && newPassword === confirmPassword)}
-						on:click={() => handleChangePassword()}
-						class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-						>Save
-					</button>
+						on:click={() => handleChangePassword()}>Save</Button
+					>
 				</div>
 				</div>
 			</div>
 			</div>
 		</form>
 		</form>

+ 3 - 9
web/src/lib/components/user-settings-page/oauth-settings.svelte

@@ -9,6 +9,7 @@
 		notificationController,
 		notificationController,
 		NotificationType
 		NotificationType
 	} from '../shared-components/notification/notification';
 	} from '../shared-components/notification/notification';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -67,17 +68,10 @@
 				</div>
 				</div>
 			{:else if config.enabled}
 			{:else if config.enabled}
 				{#if user.oauthId}
 				{#if user.oauthId}
-					<button
-						on:click={() => handleUnlink()}
-						class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-						>Unlink OAuth
-					</button>
+					<Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button>
 				{:else}
 				{:else}
 					<a href={config.url}>
 					<a href={config.url}>
-						<button
-							class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-							>Link to OAuth</button
-						>
+						<Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button>
 					</a>
 					</a>
 				{/if}
 				{/if}
 			{/if}
 			{/if}

+ 2 - 5
web/src/lib/components/user-settings-page/user-api-key-list.svelte

@@ -13,6 +13,7 @@
 		NotificationType
 		NotificationType
 	} from '../shared-components/notification/notification';
 	} from '../shared-components/notification/notification';
 	import { locale } from '$lib/stores/preferences.store';
 	import { locale } from '$lib/stores/preferences.store';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	let keys: APIKeyResponseDto[] = [];
 	let keys: APIKeyResponseDto[] = [];
 
 
@@ -124,11 +125,7 @@
 <section class="my-4">
 <section class="my-4">
 	<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
 	<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
 		<div class="flex justify-end mb-2">
 		<div class="flex justify-end mb-2">
-			<button
-				on:click={() => (newKey = { name: 'API Key' })}
-				class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-				>New API Key
-			</button>
+			<Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
 		</div>
 		</div>
 
 
 		{#if keys.length > 0}
 		{#if keys.length > 0}

+ 2 - 6
web/src/lib/components/user-settings-page/user-profile-settings.svelte

@@ -9,6 +9,7 @@
 	import SettingInputField, {
 	import SettingInputField, {
 		SettingInputFieldType
 		SettingInputFieldType
 	} from '../admin-page/settings/setting-input-field.svelte';
 	} from '../admin-page/settings/setting-input-field.svelte';
+	import Button from '../elements/buttons/button.svelte';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
@@ -65,12 +66,7 @@
 				/>
 				/>
 
 
 				<div class="flex justify-end">
 				<div class="flex justify-end">
-					<button
-						type="submit"
-						on:click={() => handleSaveProfile()}
-						class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
-						>Save
-					</button>
+					<Button type="submit" size="sm" on:click={() => handleSaveProfile()}>Save</Button>
 				</div>
 				</div>
 			</div>
 			</div>
 		</form>
 		</form>

+ 6 - 8
web/src/routes/(user)/albums/+page.svelte

@@ -9,6 +9,7 @@
 	import { useAlbums } from './albums.bloc';
 	import { useAlbums } from './albums.bloc';
 	import empty1Url from '$lib/assets/empty-1.svg';
 	import empty1Url from '$lib/assets/empty-1.svg';
 	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 
 
 	export let data: PageData;
 	export let data: PageData;
 
 
@@ -32,15 +33,12 @@
 
 
 <UserPageLayout user={data.user} title={data.meta.title}>
 <UserPageLayout user={data.user} title={data.meta.title}>
 	<div slot="buttons">
 	<div slot="buttons">
-		<button
-			on:click={handleCreateAlbum}
-			class="immich-text-button text-sm dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
-		>
-			<span>
+		<LinkButton on:click={handleCreateAlbum}>
+			<div class="flex place-items-center gap-2 text-sm">
 				<PlusBoxOutline size="18" />
 				<PlusBoxOutline size="18" />
-			</span>
-			<p>Create album</p>
-		</button>
+				Create album
+			</div>
+		</LinkButton>
 	</div>
 	</div>
 
 
 	<!-- Album Card -->
 	<!-- Album Card -->

+ 1 - 1
web/src/routes/(user)/favorites/+page.svelte

@@ -1,5 +1,5 @@
 <script lang="ts">
 <script lang="ts">
-	import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
 	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
 	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
 	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';

+ 1 - 1
web/src/routes/(user)/photos/+page.svelte

@@ -2,7 +2,7 @@
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
 	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
 	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
 	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
-	import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
 	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
 	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
 	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
 	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
 	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';

+ 11 - 16
web/src/routes/(user)/sharing/+page.svelte

@@ -11,6 +11,7 @@
 	} from '$lib/components/shared-components/notification/notification';
 	} from '$lib/components/shared-components/notification/notification';
 	import empty2Url from '$lib/assets/empty-2.svg';
 	import empty2Url from '$lib/assets/empty-2.svg';
 	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 
 
 	export let data: PageData;
 	export let data: PageData;
 
 
@@ -34,25 +35,19 @@
 
 
 <UserPageLayout user={data.user} title={data.meta.title}>
 <UserPageLayout user={data.user} title={data.meta.title}>
 	<div class="flex" slot="buttons">
 	<div class="flex" slot="buttons">
-		<button
-			on:click={createSharedAlbum}
-			class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700 dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
-		>
-			<span>
+		<LinkButton on:click={createSharedAlbum}>
+			<div class="flex place-items-center gap-1 text-sm">
 				<PlusBoxOutline size="18" />
 				<PlusBoxOutline size="18" />
-			</span>
-			<p>Create shared album</p>
-		</button>
+				Create shared album
+			</div>
+		</LinkButton>
 
 
-		<button
-			on:click={() => goto('/sharing/sharedlinks')}
-			class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700 dark:hover:bg-immich-dark-primary/25 dark:text-immich-dark-fg"
-		>
-			<span>
+		<LinkButton on:click={() => goto('/sharing/sharedlinks')}>
+			<div class="flex place-items-center gap-1 text-sm">
 				<Link size="18" />
 				<Link size="18" />
-			</span>
-			<p>Shared links</p>
-		</button>
+				Shared links
+			</div>
+		</LinkButton>
 	</div>
 	</div>
 
 
 	<section>
 	<section>

+ 4 - 5
web/src/routes/+page.svelte

@@ -1,5 +1,6 @@
 <script lang="ts">
 <script lang="ts">
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
+	import Button from '$lib/components/elements/buttons/button.svelte';
 	import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
 	import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
 </script>
 </script>
 
 
@@ -13,10 +14,8 @@
 		>
 		>
 			Welcome to IMMICH Web
 			Welcome to IMMICH Web
 		</h1>
 		</h1>
-		<button
-			class="border px-4 py-4 rounded-md bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:border-immich-dark-gray hover:bg-immich-primary/75 text-white font-bold w-[200px]"
-			on:click={() => goto('/auth/register')}
-			>Getting Started
-		</button>
+		<Button size="lg" rounded="lg" on:click={() => goto('/auth/register')}>
+			<span class="font-bold px-2">Getting Started</span>
+		</Button>
 	</div>
 	</div>
 </section>
 </section>

+ 4 - 9
web/src/routes/admin/user-management/+page.svelte

@@ -12,6 +12,7 @@
 	import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
 	import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
 	import { page } from '$app/stores';
 	import { page } from '$app/stores';
 	import { locale } from '$lib/stores/preferences.store';
 	import { locale } from '$lib/stores/preferences.store';
+	import Button from '$lib/components/elements/buttons/button.svelte';
 
 
 	let allUsers: UserResponseDto[] = [];
 	let allUsers: UserResponseDto[] = [];
 	let shouldShowEditUserForm = false;
 	let shouldShowEditUserForm = false;
@@ -151,12 +152,8 @@
 					Please inform the user, and they will need to change the password at the next log-on.
 					Please inform the user, and they will need to change the password at the next log-on.
 				</p>
 				</p>
 
 
-				<div class="flex w-full">
-					<button
-						on:click={() => (shouldShowInfoPanel = false)}
-						class="mt-6 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
-						>Done
-					</button>
+				<div class="flex w-full mt-6">
+					<Button fullwidth on:click={() => (shouldShowInfoPanel = false)}>Done</Button>
 				</div>
 				</div>
 			</div>
 			</div>
 		</FullScreenModal>
 		</FullScreenModal>
@@ -221,7 +218,5 @@
 		</tbody>
 		</tbody>
 	</table>
 	</table>
 
 
-	<button on:click={() => (shouldShowCreateUserForm = true)} class="immich-btn-primary"
-		>Create user</button
-	>
+	<Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>Create user</Button>
 </section>
 </section>