init commit
This commit is contained in:
commit
c9d982669a
461 changed files with 30317 additions and 0 deletions
43
resources/js/components/ui/carousel/carousel-content.svelte
Normal file
43
resources/js/components/ui/carousel/carousel-content.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
import emblaCarouselSvelte from "embla-carousel-svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { getEmblaContext } from "./context.js";
|
||||
import { cn, type WithElementRef } from "@/lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
|
||||
const emblaCtx = getEmblaContext("<Carousel.Content/>");
|
||||
</script>
|
||||
|
||||
<div
|
||||
data-slot="carousel-content"
|
||||
class="overflow-hidden"
|
||||
use:emblaCarouselSvelte={{
|
||||
options: {
|
||||
container: "[data-embla-container]",
|
||||
slides: "[data-embla-slide]",
|
||||
...emblaCtx.options,
|
||||
axis: emblaCtx.orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins: emblaCtx.plugins,
|
||||
}}
|
||||
onemblaInit={emblaCtx.onInit}
|
||||
>
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"flex",
|
||||
emblaCtx.orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className
|
||||
)}
|
||||
data-embla-container=""
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
30
resources/js/components/ui/carousel/carousel-item.svelte
Normal file
30
resources/js/components/ui/carousel/carousel-item.svelte
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { getEmblaContext } from "./context.js";
|
||||
import { cn, type WithElementRef } from "@/lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
|
||||
const emblaCtx = getEmblaContext("<Carousel.Item/>");
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="carousel-item"
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
class={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
emblaCtx.orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className
|
||||
)}
|
||||
data-embla-slide=""
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
38
resources/js/components/ui/carousel/carousel-next.svelte
Normal file
38
resources/js/components/ui/carousel/carousel-next.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import ArrowRightIcon from "@lucide/svelte/icons/arrow-right";
|
||||
import type { WithoutChildren } from "bits-ui";
|
||||
import { getEmblaContext } from "./context.js";
|
||||
import { cn } from "@/lib/utils.js";
|
||||
import { Button, type Props } from "@/components/ui/button/index.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
variant = "outline",
|
||||
size = "icon",
|
||||
...restProps
|
||||
}: WithoutChildren<Props> = $props();
|
||||
|
||||
const emblaCtx = getEmblaContext("<Carousel.Next/>");
|
||||
</script>
|
||||
|
||||
<Button
|
||||
data-slot="carousel-next"
|
||||
{variant}
|
||||
{size}
|
||||
class={cn(
|
||||
"absolute size-8 rounded-full",
|
||||
emblaCtx.orientation === "horizontal"
|
||||
? "-right-12 top-1/2 -translate-y-1/2"
|
||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!emblaCtx.canScrollNext}
|
||||
onclick={emblaCtx.scrollNext}
|
||||
onkeydown={emblaCtx.handleKeyDown}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
>
|
||||
<ArrowRightIcon class="size-4" />
|
||||
<span class="sr-only">Next slide</span>
|
||||
</Button>
|
38
resources/js/components/ui/carousel/carousel-previous.svelte
Normal file
38
resources/js/components/ui/carousel/carousel-previous.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import ArrowLeftIcon from "@lucide/svelte/icons/arrow-left";
|
||||
import type { WithoutChildren } from "bits-ui";
|
||||
import { getEmblaContext } from "./context.js";
|
||||
import { cn } from "@/lib/utils.js";
|
||||
import { Button, type Props } from "@/components/ui/button/index.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
variant = "outline",
|
||||
size = "icon",
|
||||
...restProps
|
||||
}: WithoutChildren<Props> = $props();
|
||||
|
||||
const emblaCtx = getEmblaContext("<Carousel.Previous/>");
|
||||
</script>
|
||||
|
||||
<Button
|
||||
data-slot="carousel-previous"
|
||||
{variant}
|
||||
{size}
|
||||
class={cn(
|
||||
"absolute size-8 rounded-full",
|
||||
emblaCtx.orientation === "horizontal"
|
||||
? "-left-12 top-1/2 -translate-y-1/2"
|
||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!emblaCtx.canScrollPrev}
|
||||
onclick={emblaCtx.scrollPrev}
|
||||
onkeydown={emblaCtx.handleKeyDown}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
>
|
||||
<ArrowLeftIcon class="size-4" />
|
||||
<span class="sr-only">Previous slide</span>
|
||||
</Button>
|
100
resources/js/components/ui/carousel/carousel.svelte
Normal file
100
resources/js/components/ui/carousel/carousel.svelte
Normal file
|
@ -0,0 +1,100 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
type CarouselAPI,
|
||||
type CarouselProps,
|
||||
type EmblaContext,
|
||||
setEmblaContext,
|
||||
} from "./context.js";
|
||||
import { cn, type WithElementRef } from "@/lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
opts = {},
|
||||
plugins = [],
|
||||
setApi = () => {},
|
||||
orientation = "horizontal",
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<CarouselProps> = $props();
|
||||
|
||||
let carouselState = $state<EmblaContext>({
|
||||
api: undefined,
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
orientation,
|
||||
canScrollNext: false,
|
||||
canScrollPrev: false,
|
||||
handleKeyDown,
|
||||
options: opts,
|
||||
plugins,
|
||||
onInit,
|
||||
scrollSnaps: [],
|
||||
selectedIndex: 0,
|
||||
scrollTo,
|
||||
});
|
||||
|
||||
setEmblaContext(carouselState);
|
||||
|
||||
function scrollPrev() {
|
||||
carouselState.api?.scrollPrev();
|
||||
}
|
||||
function scrollNext() {
|
||||
carouselState.api?.scrollNext();
|
||||
}
|
||||
function scrollTo(index: number, jump?: boolean) {
|
||||
carouselState.api?.scrollTo(index, jump);
|
||||
}
|
||||
|
||||
function onSelect(api: CarouselAPI) {
|
||||
if (!api) return;
|
||||
carouselState.canScrollPrev = api.canScrollPrev();
|
||||
carouselState.canScrollNext = api.canScrollNext();
|
||||
carouselState.selectedIndex = api.selectedScrollSnap();
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (carouselState.api) {
|
||||
onSelect(carouselState.api);
|
||||
carouselState.api.on("select", onSelect);
|
||||
carouselState.api.on("reInit", onSelect);
|
||||
}
|
||||
});
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
scrollPrev();
|
||||
} else if (e.key === "ArrowRight") {
|
||||
e.preventDefault();
|
||||
scrollNext();
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
setApi(carouselState.api);
|
||||
});
|
||||
|
||||
function onInit(event: CustomEvent<CarouselAPI>) {
|
||||
carouselState.api = event.detail;
|
||||
|
||||
carouselState.scrollSnaps = carouselState.api.scrollSnapList();
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
return () => {
|
||||
carouselState.api?.off("select", onSelect);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="carousel"
|
||||
class={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
56
resources/js/components/ui/carousel/context.ts
Normal file
56
resources/js/components/ui/carousel/context.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import type { WithElementRef } from "@/lib/utils.js";
|
||||
import type { EmblaCarouselSvelteType } from "embla-carousel-svelte";
|
||||
import type emblaCarouselSvelte from "embla-carousel-svelte";
|
||||
import { getContext, hasContext, setContext } from "svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
export type CarouselAPI =
|
||||
NonNullable<NonNullable<EmblaCarouselSvelteType["$$_attributes"]>["on:emblaInit"]> extends (
|
||||
evt: CustomEvent<infer CarouselAPI>
|
||||
) => void
|
||||
? CarouselAPI
|
||||
: never;
|
||||
|
||||
type EmblaCarouselConfig = NonNullable<Parameters<typeof emblaCarouselSvelte>[1]>;
|
||||
|
||||
export type CarouselOptions = EmblaCarouselConfig["options"];
|
||||
export type CarouselPlugins = EmblaCarouselConfig["plugins"];
|
||||
|
||||
////
|
||||
|
||||
export type CarouselProps = {
|
||||
opts?: CarouselOptions;
|
||||
plugins?: CarouselPlugins;
|
||||
setApi?: (api: CarouselAPI | undefined) => void;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
} & WithElementRef<HTMLAttributes<HTMLDivElement>>;
|
||||
|
||||
const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT");
|
||||
|
||||
export type EmblaContext = {
|
||||
api: CarouselAPI | undefined;
|
||||
orientation: "horizontal" | "vertical";
|
||||
scrollNext: () => void;
|
||||
scrollPrev: () => void;
|
||||
canScrollNext: boolean;
|
||||
canScrollPrev: boolean;
|
||||
handleKeyDown: (e: KeyboardEvent) => void;
|
||||
options: CarouselOptions;
|
||||
plugins: CarouselPlugins;
|
||||
onInit: (e: CustomEvent<CarouselAPI>) => void;
|
||||
scrollTo: (index: number, jump?: boolean) => void;
|
||||
scrollSnaps: number[];
|
||||
selectedIndex: number;
|
||||
};
|
||||
|
||||
export function setEmblaContext(config: EmblaContext): EmblaContext {
|
||||
setContext(EMBLA_CAROUSEL_CONTEXT, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getEmblaContext(name = "This component") {
|
||||
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
|
||||
throw new Error(`${name} must be used within a <Carousel.Root> component`);
|
||||
}
|
||||
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT);
|
||||
}
|
19
resources/js/components/ui/carousel/index.ts
Normal file
19
resources/js/components/ui/carousel/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Root from "./carousel.svelte";
|
||||
import Content from "./carousel-content.svelte";
|
||||
import Item from "./carousel-item.svelte";
|
||||
import Previous from "./carousel-previous.svelte";
|
||||
import Next from "./carousel-next.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Previous,
|
||||
Next,
|
||||
//
|
||||
Root as Carousel,
|
||||
Content as CarouselContent,
|
||||
Item as CarouselItem,
|
||||
Previous as CarouselPrevious,
|
||||
Next as CarouselNext,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue