fix: paginator

This commit is contained in:
Arman
2025-04-10 14:07:59 +02:00
parent 55d9eafff8
commit 73a6ea1ba3
10 changed files with 409 additions and 399 deletions
+79 -73
View File
@@ -173,84 +173,90 @@
</Table.Root>
{:then response}
{#if response?.length}
<Paginator
items={response}
let:paginatedItems
hideFooter={response?.length <= 6}
limit={6}>
<Table.Root columns={1} let:root>
{#each paginatedItems as repo}
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
{#if action === 'select'}
<input
class="is-small u-margin-inline-end-8"
type="radio"
name="repositories"
bind:group={selectedRepository}
onchange={() => repository.set(repo)}
value={repo.id} />
{/if}
{#if product === 'sites'}
{#if repo?.framework && repo.framework !== 'other'}
<Avatar size="xs" alt={repo.name}>
<SvgIcon
name={getFrameworkIcon(repo.framework)}
iconSize="small" />
</Avatar>
{:else}
<Avatar size="xs" alt={repo.name} empty />
{/if}
{:else}
{@const iconName = repo?.runtime
? repo.runtime.split('-')[0]
: undefined}
<Avatar size="xs" alt={repo.name} empty={!iconName}>
<SvgIcon name={iconName} iconSize="small" />
</Avatar>
{/if}
<Paginator items={response} hideFooter={response?.length <= 6} limit={6}>
{#snippet children(paginatedItems)}
<Table.Root columns={1} let:root>
{#each paginatedItems as repo}
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack
gap="s"
direction="row"
alignItems="center">
<Typography.Text
truncate
color="--fgcolor-neutral-secondary">
{repo.name}
</Typography.Text>
{#if repo.private}
<Icon
size="s"
icon={IconLockClosed}
color="--fgcolor-neutral-tertiary" />
alignItems="center"
gap="s">
{#if action === 'select'}
<input
class="is-small u-margin-inline-end-8"
type="radio"
name="repositories"
bind:group={selectedRepository}
onchange={() => repository.set(repo)}
value={repo.id} />
{/if}
<time datetime={repo.pushedAt}>
<Typography.Caption
variant="400"
truncate
color="--fgcolor-neutral-tertiary">
{timeFromNow(repo.pushedAt)}
</Typography.Caption>
</time>
</Layout.Stack>
{#if action === 'button'}
<Layout.Stack
direction="row"
justifyContent="flex-end">
<PinkButton.Button
{#if product === 'sites'}
{#if repo?.framework && repo.framework !== 'other'}
<Avatar size="xs" alt={repo.name}>
<SvgIcon
name={getFrameworkIcon(
repo.framework
)}
iconSize="small" />
</Avatar>
{:else}
<Avatar size="xs" alt={repo.name} empty />
{/if}
{:else}
{@const iconName = repo?.runtime
? repo.runtime.split('-')[0]
: undefined}
<Avatar
size="xs"
variant="secondary"
on:click={() => connect(repo)}>
Connect
</PinkButton.Button>
alt={repo.name}
empty={!iconName}>
<SvgIcon name={iconName} iconSize="small" />
</Avatar>
{/if}
<Layout.Stack
gap="s"
direction="row"
alignItems="center">
<Typography.Text
truncate
color="--fgcolor-neutral-secondary">
{repo.name}
</Typography.Text>
{#if repo.private}
<Icon
size="s"
icon={IconLockClosed}
color="--fgcolor-neutral-tertiary" />
{/if}
<time datetime={repo.pushedAt}>
<Typography.Caption
variant="400"
truncate
color="--fgcolor-neutral-tertiary">
{timeFromNow(repo.pushedAt)}
</Typography.Caption>
</time>
</Layout.Stack>
{/if}
</Layout.Stack>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{#if action === 'button'}
<Layout.Stack
direction="row"
justifyContent="flex-end">
<PinkButton.Button
size="xs"
variant="secondary"
on:click={() => connect(repo)}>
Connect
</PinkButton.Button>
</Layout.Stack>
{/if}
</Layout.Stack>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{/snippet}
</Paginator>
{:else}
<EmptySearch hidePages hidePagination bind:search target="repositories">
+3 -2
View File
@@ -9,7 +9,8 @@
hideFooter = false,
hidePages = true,
hasLimit = false,
name = 'items'
name = 'items',
children
} = $props();
let total = $derived(items.length);
@@ -20,7 +21,7 @@
</script>
<Layout.Stack gap="s">
<slot {paginatedItems} {limit} />
{@render children(paginatedItems, limit)}
{#if !hideFooter}
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
@@ -52,37 +52,38 @@
</Button>
</Layout.Stack>
</div>
<Paginator
items={members.memberships}
let:paginatedItems
hideFooter={members?.total <= 5}>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Members</Table.Header.Cell>
<Table.Header.Cell {root}>Joined</Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as member}
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<AvatarInitials
size="xs"
name={member.userName
? member.userName
: member.userEmail} />
<Layout.Stack gap="none">
<Typography.Text>
{member.userName ? member.userName : member.userEmail}
</Typography.Text>
<Paginator items={members.memberships} hideFooter={members?.total <= 5}>
{#snippet children(paginatedItems: typeof members.memberships)}
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Members</Table.Header.Cell>
<Table.Header.Cell {root}>Joined</Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as member}
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<AvatarInitials
size="xs"
name={member.userName
? member.userName
: member.userEmail} />
<Layout.Stack gap="none">
<Typography.Text>
{member.userName
? member.userName
: member.userEmail}
</Typography.Text>
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
</Table.Cell>
<Table.Cell {root}>
<DualTimeView time={member.joined ?? member.$createdAt} />
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
</Table.Cell>
<Table.Cell {root}>
<DualTimeView time={member.joined ?? member.$createdAt} />
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{/snippet}
</Paginator>
{:else}
<EmptyCardCloud service="members" eventSource="organization_usage" />
@@ -27,34 +27,36 @@
<Modal bind:show title={selectedRelationship?.key} hideFooter>
{#if data?.length}
<Paginator items={data} {limit} let:paginatedItems>
<Table.Root
let:root
columns={[{ id: '$id', width: 150 }, ...args.map((id) => ({ id }))]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="$id" {root}>Document ID</Table.Header.Cell>
{#if args?.length}
{#each args as arg}
<Table.Header.Cell column={arg} {root}>{arg}</Table.Header.Cell>
{/each}
{/if}
</svelte:fragment>
{#each paginatedItems as doc}
<Table.Row.Link
{root}
href={`${base}/project-${projectId}/databases/database-${databaseId}/collection-${selectedRelationship.relatedCollection}/document-${doc.$id}`}
on:click={() => (show = false)}>
<Table.Cell column="$id" {root}>
<Id value={doc.$id}>{doc.$id}</Id>
</Table.Cell>
<Paginator items={data} {limit}>
{#snippet children(paginatedItems: typeof data)}
<Table.Root
let:root
columns={[{ id: '$id', width: 150 }, ...args.map((id) => ({ id }))]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="$id" {root}>Document ID</Table.Header.Cell>
{#if args?.length}
{#each args as arg}
<Table.Cell column={arg} {root}>{doc[arg]}</Table.Cell>
<Table.Header.Cell column={arg} {root}>{arg}</Table.Header.Cell>
{/each}
{/if}
</Table.Row.Link>
{/each}
</Table.Root>
</svelte:fragment>
{#each paginatedItems as doc}
<Table.Row.Link
{root}
href={`${base}/project-${projectId}/databases/database-${databaseId}/collection-${selectedRelationship.relatedCollection}/document-${doc.$id}`}
on:click={() => (show = false)}>
<Table.Cell column="$id" {root}>
<Id value={doc.$id}>{doc.$id}</Id>
</Table.Cell>
{#if args?.length}
{#each args as arg}
<Table.Cell column={arg} {root}>{doc[arg]}</Table.Cell>
{/each}
{/if}
</Table.Row.Link>
{/each}
</Table.Root>
{/snippet}
</Paginator>
{/if}
</Modal>
@@ -82,7 +82,7 @@
<Container>
<Layout.GridFraction start={1} end={3} gap="xxl">
<Layout.Stack gap="xl">
<SearchQuery search={data.search} placeholder="Search template" />
<SearchQuery placeholder="Search template" />
<Layout.Stack>
<Accordion title="Use case">
<Layout.Stack>
@@ -140,86 +140,88 @@
</Layout.Stack>
<Layout.Stack gap="l">
{#if data.templates?.length > 0}
<Paginator
items={data.templates}
let:paginatedItems
limit={12}
hidePages={false}
hasLimit>
<Layout.Grid columns={2} columnsXL={3} columnsXS={1}>
{#each paginatedItems as template}
{@const baseRuntimes = getBaseRuntimes(template.runtimes)}
{@const displayed = baseRuntimes.slice(0, 2)}
{@const hidden = baseRuntimes.slice(1, -1)}
<PinkCard.Base radius="m" padding="xs">
<Layout.Stack height="100%" justifyContent="space-between" gap="xl">
<Layout.Stack gap="xxxs">
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-primary">
{template.name}
</Typography.Text>
<Typography.Text variant="m-400">
{template.tagline}
</Typography.Text>
</Layout.Stack>
<Paginator items={data.templates} limit={12} hidePages={false} hasLimit>
{#snippet children(paginatedItems: typeof data.templates)}
<Layout.Grid columns={2} columnsXL={3} columnsXS={1}>
{#each paginatedItems as template}
{@const baseRuntimes = getBaseRuntimes(template.runtimes)}
{@const displayed = baseRuntimes.slice(0, 2)}
{@const hidden = baseRuntimes.slice(1, -1)}
<PinkCard.Base radius="m" padding="xs">
<Layout.Stack
direction="row"
height="100%"
justifyContent="space-between"
alignItems="center">
<AvatarGroup>
{#each displayed as runtime}
{@const icon = getIconFromRuntime(runtime.name)}
{#if icon}
<Avatar alt={runtime.name} size="xs">
<SvgIcon name={icon} iconSize="small" />
</Avatar>
{/if}
{/each}
{#if hidden.length}
<Tooltip>
<Avatar alt="hidden runtime number" size="xs">
<span style:font-size="10px">
+{hidden.length}
</span>
</Avatar>
<span slot="tooltip">
{hidden.map((n) => n.name).join(', ')}
</span>
</Tooltip>
{/if}
</AvatarGroup>
gap="xl">
<Layout.Stack gap="xxxs">
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-primary">
{template.name}
</Typography.Text>
<Typography.Text variant="m-400">
{template.tagline}
</Typography.Text>
</Layout.Stack>
<Layout.Stack
direction="row"
gap="s"
alignItems="center"
inline>
<Button
href={`${base}/project-${page.params.project}/functions/templates/template-${template.id}`}
text>
<span class="text">Details</span>
</Button>
{#if $canWriteFunctions}
<ContainerButton
title="functions"
disabled={buttonDisabled}
buttonType="secondary"
buttonHref={`${base}/project-${page.params.project}/functions/create-function/template-${template.id}`}
showIcon={false}
buttonText="Create"
buttonEventData={{
source: 'functions_template'
}}
buttonEvent="create_function" />
{/if}
justifyContent="space-between"
alignItems="center">
<AvatarGroup>
{#each displayed as runtime}
{@const icon = getIconFromRuntime(runtime.name)}
{#if icon}
<Avatar alt={runtime.name} size="xs">
<SvgIcon name={icon} iconSize="small" />
</Avatar>
{/if}
{/each}
{#if hidden.length}
<Tooltip>
<Avatar
alt="hidden runtime number"
size="xs">
<span style:font-size="10px">
+{hidden.length}
</span>
</Avatar>
<span slot="tooltip">
{hidden.map((n) => n.name).join(', ')}
</span>
</Tooltip>
{/if}
</AvatarGroup>
<Layout.Stack
direction="row"
gap="s"
alignItems="center"
inline>
<Button
href={`${base}/project-${page.params.project}/functions/templates/template-${template.id}`}
text>
<span class="text">Details</span>
</Button>
{#if $canWriteFunctions}
<ContainerButton
title="functions"
disabled={buttonDisabled}
buttonType="secondary"
buttonHref={`${base}/project-${page.params.project}/functions/create-function/template-${template.id}`}
showIcon={false}
buttonText="Create"
buttonEventData={{
source: 'functions_template'
}}
buttonEvent="create_function" />
{/if}
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
</PinkCard.Base>
{/each}
</Layout.Grid>
</PinkCard.Base>
{/each}
</Layout.Grid>
{/snippet}
</Paginator>
{:else if data?.search}
<EmptySearch hidePagination search={data.search}>
@@ -180,114 +180,118 @@
<Paginator
items={variables}
limit={6}
let:paginatedItems
hideFooter={variables.length <= 6}>
<Table.Root
let:root
columns={[
{ id: 'key', width: 200 },
{ id: 'value' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}
>Key</Table.Header.Cell>
<Table.Header.Cell column="value" {root}
>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root}
></Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as variable}
<Table.Row.Base {root}>
<Table.Cell column="key" {root}
>{variable.key}</Table.Cell>
<Table.Cell column="value" {root}>
<!-- TODO: fix max width -->
<div style="max-width: 20rem">
{#if variable.secret}
<Tooltip maxWidth="26rem">
<Badge
content="Secret"
variant="secondary"
size="s" />
<svelte:fragment slot="tooltip">
This value is secret, you cannot see
its value.
{#snippet children(paginatedItems: typeof variables)}
<Table.Root
let:root
columns={[
{ id: 'key', width: 200 },
{ id: 'value' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}
>Key</Table.Header.Cell>
<Table.Header.Cell column="value" {root}
>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root}
></Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as variable}
<Table.Row.Base {root}>
<Table.Cell column="key" {root}
>{variable.key}</Table.Cell>
<Table.Cell column="value" {root}>
<!-- TODO: fix max width -->
<div style="max-width: 20rem">
{#if variable.secret}
<Tooltip maxWidth="26rem">
<Badge
content="Secret"
variant="secondary"
size="s" />
<svelte:fragment slot="tooltip">
This value is secret, you cannot
see its value.
</svelte:fragment>
</Tooltip>
{:else}
<InteractiveText
variant="secret"
isVisible={false}
text={variable.value} />
{/if}
</div>
</Table.Cell>
<Table.Cell column="actions" {root}>
<div style="margin-inline-start: auto">
<Popover
padding="none"
placement="bottom-end"
let:toggle>
<PinkButton.Button
icon
variant="text"
size="s"
aria-label="More options"
on:click={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon
icon={IconDotsHorizontal}
size="s" />
</PinkButton.Button>
<svelte:fragment
slot="tooltip"
let:toggle>
<ActionMenu.Root>
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconPencil}
on:click={(e) => {
toggle(e);
currentVariable =
variable;
showUpdate = true;
}}>
Update
</ActionMenu.Item.Button>
{/if}
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconEyeOff}
on:click={(e) => {
toggle(e);
currentVariable =
variable;
showSecretModal = true;
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
on:click={(e) => {
toggle(e);
currentVariable =
variable;
showDelete = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Tooltip>
{:else}
<InteractiveText
variant="secret"
isVisible={false}
text={variable.value} />
{/if}
</div>
</Table.Cell>
<Table.Cell column="actions" {root}>
<div style="margin-inline-start: auto">
<Popover
padding="none"
placement="bottom-end"
let:toggle>
<PinkButton.Button
icon
variant="text"
size="s"
aria-label="More options"
on:click={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon
icon={IconDotsHorizontal}
size="s" />
</PinkButton.Button>
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconPencil}
on:click={(e) => {
toggle(e);
currentVariable =
variable;
showUpdate = true;
}}>
Update
</ActionMenu.Item.Button>
{/if}
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconEyeOff}
on:click={(e) => {
toggle(e);
currentVariable =
variable;
showSecretModal = true;
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
on:click={(e) => {
toggle(e);
currentVariable = variable;
showDelete = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</div>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
</Popover>
</div>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{/snippet}
</Paginator>
{:else}
<Empty on:click={() => (showCreate = true)}
@@ -1,6 +1,6 @@
<script lang="ts">
import { base } from '$app/paths';
import { EmptySearch, PaginationWithLimit, Paginator, SvgIcon } from '$lib/components';
import { EmptySearch, Paginator, SvgIcon } from '$lib/components';
import { Button, InputSearch } from '$lib/elements/forms';
import { page } from '$app/state';
import Wizard from '$lib/layout/wizard.svelte';
@@ -125,43 +125,40 @@
</svelte:fragment>
<Layout.Stack gap="l">
{#if data.templates?.length > 0}
<Paginator
items={data.templates}
let:paginatedItems
limit={12}
hidePages={false}
hasLimit>
<Layout.Grid columns={3} columnsXS={2} columnsXXS={1}>
{#each paginatedItems as template (template.name)}
{@const templateFrameworks = template.frameworks.map((t) => t.name)}
<Paginator items={data.templates} limit={12} hidePages={false} hasLimit>
{#snippet children(paginatedItems: typeof data.templates)}
<Layout.Grid columns={3} columnsXS={2} columnsXXS={1}>
{#each paginatedItems as template (template.name)}
{@const templateFrameworks = template.frameworks.map((t) => t.name)}
<Card.Link
variant="secondary"
href={`${base}/project-${page.params.project}/sites/create-site/templates/template-${template.key}`}
padding="xxs">
<Card.Media
title={template.name}
src={$app.themeInUse === 'dark'
? template?.screenshotDark ||
`${base}/images/sites/screenshot-placeholder-dark.svg`
: template?.screenshotLight ||
`${base}/images/sites/screenshot-placeholder-light.svg`}
alt={template.name}
avatar>
<svelte:fragment slot="avatar">
<Tooltip>
<SvgIcon
name={getFrameworkIcon(templateFrameworks[0])}
iconSize="small" />
<svelte:fragment slot="tooltip">
{capitalize(templateFrameworks[0])}
</svelte:fragment>
</Tooltip>
</svelte:fragment>
</Card.Media>
</Card.Link>
{/each}
</Layout.Grid>
<Card.Link
variant="secondary"
href={`${base}/project-${page.params.project}/sites/create-site/templates/template-${template.key}`}
padding="xxs">
<Card.Media
title={template.name}
src={$app.themeInUse === 'dark'
? template?.screenshotDark ||
`${base}/images/sites/screenshot-placeholder-dark.svg`
: template?.screenshotLight ||
`${base}/images/sites/screenshot-placeholder-light.svg`}
alt={template.name}
avatar>
<svelte:fragment slot="avatar">
<Tooltip>
<SvgIcon
name={getFrameworkIcon(templateFrameworks[0])}
iconSize="small" />
<svelte:fragment slot="tooltip">
{capitalize(templateFrameworks[0])}
</svelte:fragment>
</Tooltip>
</svelte:fragment>
</Card.Media>
</Card.Link>
{/each}
</Layout.Grid>
{/snippet}
</Paginator>
{:else}
<EmptySearch
@@ -7,19 +7,13 @@ export const columns = writable<Column[]>([
title: 'Domain',
type: 'string',
format: 'string',
width: { min: 200, max: 550 }
width: { min: 200 }
},
{
id: 'redirectUrl',
title: 'Redirect to',
id: 'target',
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
},
{
id: 'deploymentVcsProviderBranch',
title: 'Production branch',
type: 'string',
width: { min: 90, max: 220 }
}
]);
@@ -36,12 +36,12 @@
{domain.domain}
</Typography.Text>
</Link>
{:else if column.id === 'redirectUrl'}
<!-- TODO design redirect status code -->
{domain?.redirectUrl || 'No redirect'}
{domain?.redirectStatusCode ? `(${domain.redirectStatusCode})` : ''}
{:else if column.id === 'deploymentVcsProviderBranch'}
{domain.deploymentVcsProviderBranch || '-'}
{:else if column.id === 'target'}
{domain?.redirectUrl
? 'Redirect to ' + domain.redirectUrl
: domain?.deploymentVcsProviderBranch
? 'Deployed from' + domain.deploymentVcsProviderBranch
: '-'}
{/if}
</Table.Cell>
{/each}
@@ -123,43 +123,46 @@
{@const items = prodReadyDeployments.deployments.filter(
(prodDeployment) => prodDeployment.$id !== deployment.$id
)}
<Paginator {items} let:paginatedItems hideFooter={items?.length <= 6} limit={6}>
{#each paginatedItems as prodDeployment}
<LabelCard
padding="xs"
radius="s"
title={toLocaleDateTime(prodDeployment.$createdAt)}
bind:group={selectedDeploymentId}
value={prodDeployment.$id}>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary">
{calculateSize(deployment.buildSize)}
</Typography.Caption>
<svg
xmlns="http://www.w3.org/2000/svg"
width="2"
height="3"
viewBox="0 0 2 3"
fill="none">
<circle cx="1" cy="1.5" r="1" fill="currentColor" />
</svg>
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary"
truncate>
{capitalize(timeFromNow(prodDeployment.$createdAt))}
{#if prodDeployment?.providerCommitAuthorUrl && prodDeployment?.providerCommitAuthor}
by <Link
size="s"
href={prodDeployment.providerCommitAuthorUrl}
external>{prodDeployment.providerCommitAuthor}</Link>
{/if}
</Typography.Caption>
</Layout.Stack>
</LabelCard>
{/each}
<Paginator {items} hideFooter={items?.length <= 6} limit={6}>
{#snippet children(paginatedItems: typeof items)}
{#each paginatedItems as prodDeployment}
<LabelCard
padding="xs"
radius="s"
title={toLocaleDateTime(prodDeployment.$createdAt)}
bind:group={selectedDeploymentId}
value={prodDeployment.$id}>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary">
{calculateSize(deployment.buildSize)}
</Typography.Caption>
<svg
xmlns="http://www.w3.org/2000/svg"
width="2"
height="3"
viewBox="0 0 2 3"
fill="none">
<circle cx="1" cy="1.5" r="1" fill="currentColor" />
</svg>
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary"
truncate>
{capitalize(timeFromNow(prodDeployment.$createdAt))}
{#if prodDeployment?.providerCommitAuthorUrl && prodDeployment?.providerCommitAuthor}
by <Link
size="s"
href={prodDeployment.providerCommitAuthorUrl}
external
>{prodDeployment.providerCommitAuthor}</Link>
{/if}
</Typography.Caption>
</Layout.Stack>
</LabelCard>
{/each}
{/snippet}
</Paginator>
{/if}
</Layout.Stack>