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-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 MinusIcon from "@lucide/svelte/icons/minus";
  import PlusIcon from "@lucide/svelte/icons/plus";
  import * as Drawer from "$lib/components/ui/drawer/index.js";
  import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
  import { BarChart } from "layerchart";
  import { scaleBand } from "d3-scale";
  import { cubicInOut } from "svelte/easing";
 
  const data = [
    {
      goal: 400
    },
    {
      goal: 300
    },
    {
      goal: 200
    },
    {
      goal: 300
    },
    {
      goal: 200
    },
    {
      goal: 278
    },
    {
      goal: 189
    },
    {
      goal: 239
    },
    {
      goal: 300
    },
    {
      goal: 200
    },
    {
      goal: 278
    },
    {
      goal: 189
    },
    {
      goal: 349
    }
  ];
 
  let goal = $state(350);
 
  function handleClick(adjustment: number) {
    goal = Math.max(200, Math.min(400, goal + adjustment));
  }
</script>
 
<Drawer.Root>
  <Drawer.Trigger class={buttonVariants({ variant: "outline" })}
    >Open Drawer</Drawer.Trigger
  >
  <Drawer.Content>
    <div class="mx-auto w-full max-w-sm">
      <Drawer.Header>
        <Drawer.Title>Move Goal</Drawer.Title>
        <Drawer.Description>Set your daily activity goal.</Drawer.Description>
      </Drawer.Header>
      <div class="p-4 pb-0">
        <div class="flex items-center justify-center space-x-2">
          <Button
            variant="outline"
            size="icon"
            class="size-8 shrink-0 rounded-full"
            onclick={() => handleClick(-10)}
            disabled={goal <= 200}
          >
            <MinusIcon />
            <span class="sr-only">Decrease</span>
          </Button>
          <div class="flex-1 text-center">
            <div class="text-7xl font-bold tracking-tighter">
              {goal}
            </div>
            <div class="text-muted-foreground text-[0.70rem] uppercase">
              Calories/day
            </div>
          </div>
          <Button
            variant="outline"
            size="icon"
            class="size-8 shrink-0 rounded-full"
            onclick={() => handleClick(10)}
            disabled={goal >= 400}
          >
            <PlusIcon />
            <span class="sr-only">Increase</span>
          </Button>
        </div>
        <div class="mt-3 h-[120px]">
          <div class="h-full w-full">
            <BarChart
              data={data.map((d, i) => ({ goal: d.goal, index: i }))}
              y="goal"
              x="index"
              xScale={scaleBand().padding(0.25)}
              axis={false}
              tooltipContext={false}
              props={{
                bars: {
                  stroke: "none",
                  rounded: "all",
                  radius: 4,
                  motion: { type: "tween", duration: 500, easing: cubicInOut },
                  fill: "var(--color-foreground)",
                  fillOpacity: 0.9
                },
                highlight: { area: { fill: "none" } }
              }}
            />
          </div>
        </div>
      </div>
      <Drawer.Footer>
        <Button>Submit</Button>
        <Drawer.Close class={buttonVariants({ variant: "outline" })}
          >Cancel</Drawer.Close
        >
      </Drawer.Footer>
    </div>
  </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} 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.