January 2024 - New Components
New Components in shadcn-svelte.
Epicenter
Local-first, open source apps
We've added four new components to the project, Carousel, Drawer, Sonner, & Pagination.
New Component: Carousel #
1
2
3
4
5
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
import * as Carousel from "$lib/components/ui/carousel/index.js";
</script>
<Carousel.Root class="w-full max-w-[12rem] sm:max-w-xs">
<Carousel.Content>
{#each Array(5), i}
<Carousel.Item>
<div class="p-1">
<Card.Root>
<Card.Content
class="flex aspect-square items-center justify-center p-6"
>
<span class="text-4xl font-semibold">{i + 1}</span>
</Card.Content>
</Card.Root>
</div>
</Carousel.Item>
{/each}
</Carousel.Content>
<Carousel.Previous />
<Carousel.Next />
</Carousel.Root> New Component: Drawer #
<script lang="ts">
import { toast } from "svelte-sonner";
import * as Drawer from "$lib/components/ui/drawer/index.js";
import * as Field from "$lib/components/ui/field/index.js";
import * as RadioGroup from "$lib/components/ui/radio-group/index.js";
import { IsMobile } from "$lib/components/hooks/is-mobile.svelte.js";
import { Badge } from "$lib/components/ui/badge/index.js";
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
const deliveryTimes = [
{
value: "asap",
id: "delivery-asap",
label: "Standard delivery",
description: "25–35 min · Driver assigned now",
badge: "Fastest"
},
{
value: "5-00",
id: "delivery-5-00",
label: "5:00 PM – 5:15 PM",
description: "Prep starts at 4:45 PM"
},
{
value: "5-30",
id: "delivery-5-30",
label: "5:30 PM – 5:45 PM",
description: "Good if you're heading home"
},
{
value: "6-00",
id: "delivery-6-00",
label: "6:00 PM – 6:15 PM",
description: "Most popular · High demand"
},
{
value: "6-30",
id: "delivery-6-30",
label: "6:30 PM – 6:45 PM",
description: "Last slot before kitchen closes"
}
];
let open = $state(false);
let deliveryTime = $state("asap");
const isMobile = new IsMobile();
function handleConfirm() {
const selected = deliveryTimes.find((time) => time.value === deliveryTime);
if (!selected) {
return;
}
open = false;
toast("Delivery time confirmed", {
description: selected.label
});
}
</script>
<Drawer.Root bind:open direction={isMobile.current ? "bottom" : "right"}>
<Drawer.Trigger class={buttonVariants({ variant: "secondary" })}
>Open Drawer</Drawer.Trigger
>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Pick a delivery time</Drawer.Title>
<Drawer.Description
>We'll prepare your order as soon as possible.</Drawer.Description
>
</Drawer.Header>
<div class="flex-1 overflow-y-auto p-4">
<RadioGroup.Root bind:value={deliveryTime} class="gap-2">
{#each deliveryTimes as time (time.value)}
<Field.Label for={time.id}>
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Title class="flex items-center gap-2">
{time.label}
{#if time.badge}
<Badge variant="secondary">{time.badge}</Badge>
{/if}
</Field.Title>
<Field.Description>{time.description}</Field.Description>
</Field.Content>
<RadioGroup.Item value={time.value} id={time.id} />
</Field.Field>
</Field.Label>
{/each}
</RadioGroup.Root>
</div>
<Drawer.Footer>
<Button onclick={handleConfirm} class="h-[34px]"
>Confirm Delivery Time</Button
>
<Drawer.Close class={buttonVariants({ variant: "outline" })}
>Cancel</Drawer.Close
>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root> The Drawer is built on top of vaul-svelte and is a port of vaul, originally created by Emil Kowalski for React.
New Component: Sonner #
<script lang="ts">
import { toast } from "svelte-sonner";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button
variant="outline"
onclick={() =>
toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
action: {
label: "Undo",
onClick: () => console.info("Undo")
}
})}
>
Show Toast
</Button> The Sonner component is provided by svelte-sonner, which is a Svelte port of Sonner, originally created by Emil Kowalski for React.
New Component: Pagination #
- More pages
<script lang="ts">
import * as Pagination from "$lib/components/ui/pagination/index.js";
</script>
<Pagination.Root count={30} perPage={10} page={2}>
{#snippet children({ pages, currentPage })}
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === "ellipsis"}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<Pagination.Link {page} isActive={currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
<Pagination.Item>
<Pagination.Next />
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root> Pagination leverages the Pagination component from Bits UI.