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

20
pnpm-lock.yaml generated
View File

@@ -845,6 +845,12 @@ importers:
tabbable:
specifier: ^6.2.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:
specifier: ^0.1.1
version: 0.1.1
@@ -11252,8 +11258,8 @@ packages:
tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
tailwind-merge@3.4.0:
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
tailwind-merge@3.5.0:
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
tailwind-variants@3.2.2:
resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==}
@@ -14959,8 +14965,8 @@ snapshots:
simple-icons: 16.9.0
svelte: 5.53.7
svelte-highlight: 7.9.0
tailwind-merge: 3.4.0
tailwind-variants: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.2.1)
tailwind-merge: 3.5.0
tailwind-variants: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.1)
tailwindcss: 4.2.1
transitivePeerDependencies:
- '@sveltejs/kit'
@@ -24554,13 +24560,13 @@ snapshots:
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:
tailwindcss: 4.2.1
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)):
dependencies:

View File

@@ -60,6 +60,8 @@
"svelte-maplibre": "^1.2.5",
"svelte-persisted-store": "^0.12.0",
"tabbable": "^6.2.0",
"tailwind-merge": "^3.5.0",
"tailwind-variants": "^3.2.2",
"thumbhash": "^0.1.1",
"transformation-matrix": "^3.1.0",
"uplot": "^1.6.32"

View File

@@ -1,11 +1,12 @@
<script lang="ts">
import { cleanClass } from '$lib';
import type { ClassValue } from 'svelte/elements';
interface Props {
class?: ClassValue;
}
let { class: className = '' }: Props = $props();
let { class: className }: Props = $props();
</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>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { cleanClass } from '$lib';
import type { ClassValue } from 'svelte/elements';
interface Props {
@@ -8,7 +9,7 @@
let { class: className }: Props = $props();
</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)}
<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>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { cleanClass } from '$lib';
import QueueCardBadge from '$lib/components/QueueCardBadge.svelte';
import QueueCardButton from '$lib/components/QueueCardButton.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="{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 class="text-2xl">
@@ -114,7 +118,10 @@
</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">
{waitingCount.toLocaleString($locale)}

View File

@@ -1,23 +1,25 @@
<script lang="ts" module>
export type Color = 'success' | 'warning';
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
import { tv } from 'tailwind-variants';
interface Props {
color: Color;
type Props = {
color: 'success' | 'warning';
children?: Snippet;
}
};
let { color, children }: Props = $props();
const colorClasses: Record<Color, string> = {
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',
};
const styles = tv({
base: 'w-full p-2 text-center text-sm ',
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>
<div class="w-full p-2 text-center text-sm {colorClasses[color]}">
<div class={styles({ color })}>
{@render children?.()}
</div>

View File

@@ -4,6 +4,7 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { tv } from 'tailwind-variants';
interface Props {
color: Colors;
@@ -14,24 +15,22 @@
let { color, disabled = false, onClick = () => {}, children }: Props = $props();
const colorClasses: Record<Colors, string> = {
'light-gray': 'bg-gray-300/80 dark:bg-gray-700',
gray: 'bg-gray-300/90 dark:bg-gray-700/90',
'dark-gray': 'bg-gray-300 dark:bg-gray-700/80',
};
const hoverClasses = disabled
? 'cursor-not-allowed'
: 'hover:bg-immich-primary hover:text-white dark:hover:bg-immich-dark-primary dark:hover:text-black';
const styles = tv({
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 ',
variants: {
color: {
'light-gray': 'bg-gray-300/80 dark:bg-gray-700',
gray: 'bg-gray-300/90 dark:bg-gray-700/90',
'dark-gray': 'bg-gray-300 dark:bg-gray-700/80',
},
disabled: {
true: 'cursor-not-allowed',
false: 'hover:bg-immich-primary hover:text-white dark:hover:bg-immich-dark-primary dark:hover:text-black',
},
},
});
</script>
<button
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}
>
<button type="button" {disabled} class={styles({ disabled, color })} onclick={onClick}>
{@render children?.()}
</button>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { cleanClass } from '$lib';
import { queueManager } from '$lib/managers/queue-manager.svelte';
import type { QueueSnapshot } from '$lib/types';
import type { QueueResponseDto } from '@immich/sdk';
@@ -13,7 +14,7 @@
class?: string;
};
const { queue, class: className = '' }: Props = $props();
const { queue, class: className }: Props = $props();
type Data = number | null;
type NormalizedData = [
@@ -159,7 +160,7 @@
requestAnimationFrame(update);
</script>
<div class="w-full {className}" bind:this={chartElement}>
<div class={cleanClass('w-full', className)} bind:this={chartElement}>
{#if data[0].length === 0}
<LoadingSpinner size="giant" />
{/if}

15
web/src/lib/index.spec.ts Normal file
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
web/src/lib/index.ts Normal file
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(' '),
);
};