refactor: clean class (#26879)

This commit is contained in:
Jason Rasmussen
2026-03-12 10:53:46 -04:00
committed by GitHub
parent 5c3777ab46
commit 3bd37ebbfb
10 changed files with 93 additions and 43 deletions
+13 -7
View File
@@ -845,6 +845,12 @@ importers:
tabbable: tabbable:
specifier: ^6.2.0 specifier: ^6.2.0
version: 6.4.0 version: 6.4.0
tailwind-merge:
specifier: ^3.5.0
version: 3.5.0
tailwind-variants:
specifier: ^3.2.2
version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1)
thumbhash: thumbhash:
specifier: ^0.1.1 specifier: ^0.1.1
version: 0.1.1 version: 0.1.1
@@ -11252,8 +11258,8 @@ packages:
tabbable@6.4.0: tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
tailwind-merge@3.4.0: tailwind-merge@3.5.0:
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
tailwind-variants@3.2.2: tailwind-variants@3.2.2:
resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==}
@@ -14959,8 +14965,8 @@ snapshots:
simple-icons: 16.9.0 simple-icons: 16.9.0
svelte: 5.53.7 svelte: 5.53.7
svelte-highlight: 7.9.0 svelte-highlight: 7.9.0
tailwind-merge: 3.4.0 tailwind-merge: 3.5.0
tailwind-variants: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.2.1) tailwind-variants: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1)
tailwindcss: 4.2.1 tailwindcss: 4.2.1
transitivePeerDependencies: transitivePeerDependencies:
- '@sveltejs/kit' - '@sveltejs/kit'
@@ -24554,13 +24560,13 @@ snapshots:
tabbable@6.4.0: {} tabbable@6.4.0: {}
tailwind-merge@3.4.0: {} tailwind-merge@3.5.0: {}
tailwind-variants@3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.2.1): tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1):
dependencies: dependencies:
tailwindcss: 4.2.1 tailwindcss: 4.2.1
optionalDependencies: optionalDependencies:
tailwind-merge: 3.4.0 tailwind-merge: 3.5.0
tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)): tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)):
dependencies: dependencies:
+2
View File
@@ -60,6 +60,8 @@
"svelte-maplibre": "^1.2.5", "svelte-maplibre": "^1.2.5",
"svelte-persisted-store": "^0.12.0", "svelte-persisted-store": "^0.12.0",
"tabbable": "^6.2.0", "tabbable": "^6.2.0",
"tailwind-merge": "^3.5.0",
"tailwind-variants": "^3.2.2",
"thumbhash": "^0.1.1", "thumbhash": "^0.1.1",
"transformation-matrix": "^3.1.0", "transformation-matrix": "^3.1.0",
"uplot": "^1.6.32" "uplot": "^1.6.32"
@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { cleanClass } from '$lib';
import type { ClassValue } from 'svelte/elements'; import type { ClassValue } from 'svelte/elements';
interface Props { interface Props {
class?: ClassValue; class?: ClassValue;
} }
let { class: className = '' }: Props = $props(); let { class: className }: Props = $props();
</script> </script>
<div class="absolute h-full w-full bg-gray-300 dark:bg-gray-700 {className}"></div> <div class={cleanClass('absolute h-full w-full bg-gray-300 dark:bg-gray-700', className)}></div>
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cleanClass } from '$lib';
import type { ClassValue } from 'svelte/elements'; import type { ClassValue } from 'svelte/elements';
interface Props { interface Props {
@@ -8,7 +9,7 @@
let { class: className }: Props = $props(); let { class: className }: Props = $props();
</script> </script>
<div class="delayed inline-flex items-center gap-1 {className}"> <div class={cleanClass('delayed inline-flex items-center gap-1', className)}>
{#each [0, 1, 2] as i (i)} {#each [0, 1, 2] as i (i)}
<span class="dot block size-1.5 rounded-full bg-white shadow-[0_0_3px_rgba(0,0,0,0.6)]" style:--delay="{i * 0.25}s" <span class="dot block size-1.5 rounded-full bg-white shadow-[0_0_3px_rgba(0,0,0,0.6)]" style:--delay="{i * 0.25}s"
></span> ></span>
+9 -2
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cleanClass } from '$lib';
import QueueCardBadge from '$lib/components/QueueCardBadge.svelte'; import QueueCardBadge from '$lib/components/QueueCardBadge.svelte';
import QueueCardButton from '$lib/components/QueueCardButton.svelte'; import QueueCardButton from '$lib/components/QueueCardButton.svelte';
import Badge from '$lib/elements/Badge.svelte'; import Badge from '$lib/elements/Badge.svelte';
@@ -105,7 +106,10 @@
<div class="mt-2 flex w-full max-w-md flex-col sm:flex-row"> <div class="mt-2 flex w-full max-w-md flex-col sm:flex-row">
<div <div
class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-s-lg sm:rounded-e-none" class={cleanClass(
commonClasses,
'rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-s-lg sm:rounded-e-none',
)}
> >
<p>{$t('active')}</p> <p>{$t('active')}</p>
<p class="text-2xl"> <p class="text-2xl">
@@ -114,7 +118,10 @@
</div> </div>
<div <div
class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-s-none sm:rounded-e-lg" class={cleanClass(
commonClasses,
'flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-s-none sm:rounded-e-lg',
)}
> >
<p class="text-2xl"> <p class="text-2xl">
{waitingCount.toLocaleString($locale)} {waitingCount.toLocaleString($locale)}
+14 -12
View File
@@ -1,23 +1,25 @@
<script lang="ts" module>
export type Color = 'success' | 'warning';
</script>
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { tv } from 'tailwind-variants';
interface Props { type Props = {
color: Color; color: 'success' | 'warning';
children?: Snippet; children?: Snippet;
} };
let { color, children }: Props = $props(); let { color, children }: Props = $props();
const colorClasses: Record<Color, string> = { const styles = tv({
success: 'bg-green-500/70 text-gray-900 dark:bg-green-700/90 dark:text-gray-100', base: 'w-full p-2 text-center text-sm ',
warning: 'bg-orange-400/70 text-gray-900 dark:bg-orange-900 dark:text-gray-100', variants: {
}; color: {
success: 'bg-green-500/70 text-gray-900 dark:bg-green-700/90 dark:text-gray-100',
warning: 'bg-orange-400/70 text-gray-900 dark:bg-orange-900 dark:text-gray-100',
},
},
});
</script> </script>
<div class="w-full p-2 text-center text-sm {colorClasses[color]}"> <div class={styles({ color })}>
{@render children?.()} {@render children?.()}
</div> </div>
+16 -17
View File
@@ -4,6 +4,7 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { tv } from 'tailwind-variants';
interface Props { interface Props {
color: Colors; color: Colors;
@@ -14,24 +15,22 @@
let { color, disabled = false, onClick = () => {}, children }: Props = $props(); let { color, disabled = false, onClick = () => {}, children }: Props = $props();
const colorClasses: Record<Colors, string> = { const styles = tv({
'light-gray': 'bg-gray-300/80 dark:bg-gray-700', base: 'flex h-full w-full flex-col place-content-center place-items-center gap-2 px-8 py-2 text-xs text-gray-600 transition-colors dark:text-gray-200 ',
gray: 'bg-gray-300/90 dark:bg-gray-700/90', variants: {
'dark-gray': 'bg-gray-300 dark:bg-gray-700/80', color: {
}; 'light-gray': 'bg-gray-300/80 dark:bg-gray-700',
gray: 'bg-gray-300/90 dark:bg-gray-700/90',
const hoverClasses = disabled 'dark-gray': 'bg-gray-300 dark:bg-gray-700/80',
? 'cursor-not-allowed' },
: 'hover:bg-immich-primary hover:text-white dark:hover:bg-immich-dark-primary dark:hover:text-black'; disabled: {
true: 'cursor-not-allowed',
false: 'hover:bg-immich-primary hover:text-white dark:hover:bg-immich-dark-primary dark:hover:text-black',
},
},
});
</script> </script>
<button <button type="button" {disabled} class={styles({ disabled, color })} onclick={onClick}>
type="button"
{disabled}
class="flex h-full w-full flex-col place-content-center place-items-center gap-2 px-8 py-2 text-xs text-gray-600 transition-colors dark:text-gray-200 {colorClasses[
color
]} {hoverClasses}"
onclick={onClick}
>
{@render children?.()} {@render children?.()}
</button> </button>
+3 -2
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cleanClass } from '$lib';
import { queueManager } from '$lib/managers/queue-manager.svelte'; import { queueManager } from '$lib/managers/queue-manager.svelte';
import type { QueueSnapshot } from '$lib/types'; import type { QueueSnapshot } from '$lib/types';
import type { QueueResponseDto } from '@immich/sdk'; import type { QueueResponseDto } from '@immich/sdk';
@@ -13,7 +14,7 @@
class?: string; class?: string;
}; };
const { queue, class: className = '' }: Props = $props(); const { queue, class: className }: Props = $props();
type Data = number | null; type Data = number | null;
type NormalizedData = [ type NormalizedData = [
@@ -159,7 +160,7 @@
requestAnimationFrame(update); requestAnimationFrame(update);
</script> </script>
<div class="w-full {className}" bind:this={chartElement}> <div class={cleanClass('w-full', className)} bind:this={chartElement}>
{#if data[0].length === 0} {#if data[0].length === 0}
<LoadingSpinner size="giant" /> <LoadingSpinner size="giant" />
{/if} {/if}
+15
View File
@@ -0,0 +1,15 @@
import { cleanClass } from '$lib';
describe('cleanClass', () => {
it('should return a string of class names', () => {
expect(cleanClass('class1', 'class2', 'class3')).toBe('class1 class2 class3');
});
it('should filter out undefined, null, and false values', () => {
expect(cleanClass('class1', undefined, 'class2', null, 'class3', false)).toBe('class1 class2 class3');
});
it('should unnest arrays', () => {
expect(cleanClass('class1', ['class2', 'class3'])).toBe('class1 class2 class3');
});
});
+16
View File
@@ -0,0 +1,16 @@
import { twMerge } from 'tailwind-merge';
export const cleanClass = (...classNames: unknown[]) => {
return twMerge(
classNames
.flatMap((className) => (Array.isArray(className) ? className : [className]))
.filter((className) => {
if (!className || typeof className === 'boolean') {
return false;
}
return typeof className === 'string';
})
.join(' '),
);
};