8.0k New

January 2024 - New Components

New Components in shadcn-svelte.

Epicenter

Local-first, open source apps

Special Sponsor

We've added four new components to the project, Carousel, Drawer, Sonner, & Pagination.

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&apos;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

<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.