{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "card-list",
	"title": "Card List",
	"type": "registry:ui",
	"description": "A keyed responsive card collection with empty-state and counted or uncounted pagination composition.",
	"dependencies": [
		"@lucide/svelte@^1.28.0"
	],
	"registryDependencies": [
		"button",
		"pagination"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport ChevronLeftIcon from '@lucide/svelte/icons/chevron-left';\n\timport ChevronRightIcon from '@lucide/svelte/icons/chevron-right';\n\n\timport { Button } from '$UI$/button/index.js';\n\timport * as Pagination from '$UI$/pagination/index.js';\n\timport { cn } from '$UTILS$.js';\n\n\timport type { CardListFooterProps } from './types.js';\n\n\tlet {\n\t\tclass: className,\n\t\tcount,\n\t\thasNext = false,\n\t\titemCount,\n\t\titemLabel,\n\t\titemLabelPlural = `${itemLabel}s`,\n\t\tonPageChange,\n\t\tpage,\n\t\tperPage\n\t}: CardListFooterProps = $props();\n\n\tlet first = $derived((page - 1) * perPage + 1);\n\tlet last = $derived(first + itemCount - 1);\n\tlet noun = $derived(count === 1 ? itemLabel : itemLabelPlural);\n\t// Without a total there is no page count to render, so an uncounted set gets\n\t// Previous/Next only. Hidden entirely on a single page, matching the\n\t// counted footer, which only appears once there is a second page to reach.\n\tlet steppedPaging = $derived(count === null && (hasNext || page > 1));\n</script>\n\n<div class={cn('flex flex-wrap items-center justify-between gap-3 border-t pt-4', className)}>\n\t<p class=\"text-xs text-muted-foreground\">\n\t\t{#if itemCount === 0}\n\t\t\tNo {itemLabelPlural} to show\n\t\t{:else if count === null}\n\t\t\tShowing {first}–{last}\n\t\t\t{itemLabelPlural}\n\t\t{:else}\n\t\t\tShowing {first}–{last} of {count}\n\t\t\t{noun}\n\t\t{/if}\n\t</p>\n\t{#if steppedPaging}\n\t\t<nav class=\"flex items-center gap-1\" aria-label=\"Pagination\">\n\t\t\t<Button\n\t\t\t\tvariant=\"ghost\"\n\t\t\t\tclass=\"pl-2!\"\n\t\t\t\tdisabled={page <= 1}\n\t\t\t\taria-label=\"Go to previous page\"\n\t\t\t\tonclick={() => onPageChange(page - 1)}\n\t\t\t>\n\t\t\t\t<ChevronLeftIcon data-icon=\"inline-start\" />\n\t\t\t\t<span class=\"hidden sm:block\">Previous</span>\n\t\t\t</Button>\n\t\t\t<Button\n\t\t\t\tvariant=\"ghost\"\n\t\t\t\tclass=\"pr-2!\"\n\t\t\t\tdisabled={!hasNext}\n\t\t\t\taria-label=\"Go to next page\"\n\t\t\t\tonclick={() => onPageChange(page + 1)}\n\t\t\t>\n\t\t\t\t<span class=\"hidden sm:block\">Next</span>\n\t\t\t\t<ChevronRightIcon data-icon=\"inline-end\" />\n\t\t\t</Button>\n\t\t</nav>\n\t{:else if count !== null && count > perPage}\n\t\t<Pagination.Root\n\t\t\t{count}\n\t\t\t{perPage}\n\t\t\t{page}\n\t\t\t{onPageChange}\n\t\t\tclass=\"mx-0 w-full justify-start sm:w-auto sm:justify-end\"\n\t\t>\n\t\t\t{#snippet children({ pages, currentPage })}\n\t\t\t\t<Pagination.Content>\n\t\t\t\t\t<Pagination.Item>\n\t\t\t\t\t\t<Pagination.Previous />\n\t\t\t\t\t</Pagination.Item>\n\t\t\t\t\t{#each pages as paginationPage (paginationPage.key)}\n\t\t\t\t\t\t{#if paginationPage.type === 'ellipsis'}\n\t\t\t\t\t\t\t<Pagination.Item>\n\t\t\t\t\t\t\t\t<Pagination.Ellipsis />\n\t\t\t\t\t\t\t</Pagination.Item>\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<Pagination.Item>\n\t\t\t\t\t\t\t\t<Pagination.Link\n\t\t\t\t\t\t\t\t\tpage={paginationPage}\n\t\t\t\t\t\t\t\t\tisActive={currentPage === paginationPage.value}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Pagination.Item>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t{/each}\n\t\t\t\t\t<Pagination.Item>\n\t\t\t\t\t\t<Pagination.Next />\n\t\t\t\t\t</Pagination.Item>\n\t\t\t\t</Pagination.Content>\n\t\t\t{/snippet}\n\t\t</Pagination.Root>\n\t{/if}\n</div>\n",
			"type": "registry:file",
			"target": "card-list/card-list-footer.svelte"
		},
		{
			"content": "<script lang=\"ts\" generics=\"T\">\n\timport { cn } from '$UTILS$.js';\n\n\timport type { CardListProps } from './types.js';\n\n\tlet {\n\t\tariaLabel = 'Card list',\n\t\tclass: className,\n\t\tempty,\n\t\tgetItemId,\n\t\titem,\n\t\titems\n\t}: CardListProps<T> = $props();\n</script>\n\n<div class={cn('flex w-full flex-col gap-3', className)} data-card-list role=\"list\" aria-label={ariaLabel}>\n\t{#each items as currentItem, index (getItemId(currentItem))}\n\t\t<div role=\"listitem\" data-card-list-item={getItemId(currentItem)}>\n\t\t\t{@render item(currentItem, index)}\n\t\t</div>\n\t{:else}\n\t\t<!--\n\t\t\tA caller's own empty state is rendered bare. The dashed box is only the\n\t\t\tfallback, so passing an `Empty.Root` does not land inside a second frame\n\t\t\twith a second set of padding.\n\t\t-->\n\t\t{#if empty}\n\t\t\t{@render empty()}\n\t\t{:else}\n\t\t\t<div\n\t\t\t\tclass=\"flex min-h-28 items-center justify-center rounded-4xl border border-dashed p-4 text-center text-sm text-muted-foreground\"\n\t\t\t>\n\t\t\t\tNo items found\n\t\t\t</div>\n\t\t{/if}\n\t{/each}\n</div>\n",
			"type": "registry:file",
			"target": "card-list/card-list.svelte"
		},
		{
			"content": "import Footer from './card-list-footer.svelte';\nimport Root from './card-list.svelte';\n\nexport { Root as CardList, Footer as CardListFooter, Footer, Root };\nexport type { CardListFooterProps, CardListProps } from './types.js';\n",
			"type": "registry:file",
			"target": "card-list/index.ts"
		},
		{
			"content": "import type { Snippet } from 'svelte';\n\nexport interface CardListProps<T> {\n\tariaLabel?: string;\n\tclass?: string;\n\tempty?: Snippet;\n\tgetItemId: (item: T) => string;\n\titem: Snippet<[item: T, index: number]>;\n\titems: readonly T[];\n}\n\nexport interface CardListFooterProps {\n\tclass?: string;\n\t/**\n\t * Total across every page, or `null` when the source cannot count the set it\n\t * is showing — a search the upstream cannot total, say. A `null` count shows\n\t * the range without a total and pages with Previous/Next off `hasNext`,\n\t * rather than inventing a total or dropping pagination altogether.\n\t */\n\tcount: null | number;\n\t/** Whether another page follows. Required when `count` is `null`. */\n\thasNext?: boolean;\n\t/** How many of `count` this page rendered, so the summary can state a range. */\n\titemCount: number;\n\titemLabel: string;\n\t/** Defaults to `itemLabel` with an `s`; pass it for an irregular plural. */\n\titemLabelPlural?: string;\n\tonPageChange: (page: number) => void;\n\tpage: number;\n\tperPage: number;\n}\n",
			"type": "registry:file",
			"target": "card-list/types.ts"
		}
	]
}