瀏覽代碼

feat: add list component

Thomas Zhang 3 月之前
父節點
當前提交
816608ee7d
共有 2 個文件被更改,包括 75 次插入0 次删除
  1. 3 0
      src/lib/components/common/list/index.ts
  2. 72 0
      src/lib/components/common/list/list.svelte

+ 3 - 0
src/lib/components/common/list/index.ts

@@ -0,0 +1,3 @@
+import List from './list.svelte';
+
+export { List };

+ 72 - 0
src/lib/components/common/list/list.svelte

@@ -0,0 +1,72 @@
+<script generics="T" lang="ts">
+	import { type Snippet, untrack } from 'svelte';
+	import { sineInOut } from 'svelte/easing';
+	import type { ClassValue } from 'svelte/elements';
+	import { fly } from 'svelte/transition';
+
+	let {
+		class: className = '',
+		items,
+		key,
+		itemComponent,
+	}: {
+		class?: ClassValue;
+		items: T[];
+		key: (item: T) => string;
+		itemComponent: Snippet<[T, number]>;
+	} = $props();
+
+	type ViewportItem = { item: T; inDelay: number };
+	const DELAY = 20;
+
+	let viewportItems: ViewportItem[] = $state([]);
+	let initialized = false;
+
+	$effect(() => {
+		const newItems = items;
+		const current = untrack(() => viewportItems);
+
+		if (!initialized) {
+			initialized = true;
+			viewportItems = newItems.map((item, idx) => ({ item, inDelay: idx * DELAY }));
+			return;
+		}
+
+		const currentKeyMap = new Map(current.map((v) => [key(v.item), v]));
+		const newViewportItems: ViewportItem[] = [];
+		let addedIdx = 0;
+		for (const item of newItems) {
+			const k = key(item);
+			if (currentKeyMap.has(k)) {
+				// Preserve existing item with no delay
+				newViewportItems.push({ item, inDelay: 0 });
+			} else {
+				// New item with staggered in delay
+				newViewportItems.push({ item, inDelay: addedIdx++ * DELAY });
+			}
+		}
+
+		document.startViewTransition(() => {
+			viewportItems = newViewportItems;
+		});
+	});
+</script>
+
+<div class={['', className]}>
+	{#each viewportItems as { item, inDelay }, idx (key(item))}
+		{@const itemKey = key(item)}
+		<div
+			style={`view-transition-name: ${itemKey};`}
+			in:fly={{ x: -20, y: -20, duration: 200, opacity: 0, delay: inDelay, easing: sineInOut }}
+		>
+			{@render itemComponent(item, idx)}
+		</div>
+	{/each}
+</div>
+
+<style>
+	::view-transition-old(*),
+	::view-transition-new(*) {
+		animation-duration: 0.2s;
+	}
+</style>