@starwind-ui/core 1.7.3 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +41 -23
- package/dist/index.js.map +1 -1
- package/dist/src/components/accordion/Accordion.astro +8 -2
- package/dist/src/components/accordion/AccordionContent.astro +2 -1
- package/dist/src/components/accordion/AccordionItem.astro +8 -2
- package/dist/src/components/accordion/AccordionTrigger.astro +2 -1
- package/dist/src/components/accordion/index.ts +7 -5
- package/dist/src/components/alert/Alert.astro +5 -2
- package/dist/src/components/alert/AlertDescription.astro +4 -4
- package/dist/src/components/alert/AlertTitle.astro +3 -3
- package/dist/src/components/alert/index.ts +6 -4
- package/dist/src/components/alert-dialog/AlertDialog.astro +273 -0
- package/dist/src/components/alert-dialog/AlertDialogAction.astro +44 -0
- package/dist/src/components/alert-dialog/AlertDialogCancel.astro +45 -0
- package/dist/src/components/alert-dialog/AlertDialogContent.astro +50 -0
- package/dist/src/components/alert-dialog/AlertDialogDescription.astro +18 -0
- package/dist/src/components/alert-dialog/AlertDialogFooter.astro +16 -0
- package/dist/src/components/alert-dialog/AlertDialogHeader.astro +14 -0
- package/dist/src/components/alert-dialog/AlertDialogTitle.astro +20 -0
- package/dist/src/components/alert-dialog/AlertDialogTrigger.astro +47 -0
- package/dist/src/components/alert-dialog/index.ts +46 -0
- package/dist/src/components/avatar/Avatar.astro +2 -2
- package/dist/src/components/avatar/AvatarFallback.astro +2 -2
- package/dist/src/components/avatar/AvatarImage.astro +13 -2
- package/dist/src/components/avatar/index.ts +6 -4
- package/dist/src/components/badge/Badge.astro +2 -2
- package/dist/src/components/badge/index.ts +4 -2
- package/dist/src/components/breadcrumb/Breadcrumb.astro +1 -1
- package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +4 -1
- package/dist/src/components/breadcrumb/BreadcrumbItem.astro +2 -2
- package/dist/src/components/breadcrumb/BreadcrumbLink.astro +2 -2
- package/dist/src/components/breadcrumb/BreadcrumbList.astro +2 -2
- package/dist/src/components/breadcrumb/BreadcrumbPage.astro +2 -1
- package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +2 -1
- package/dist/src/components/breadcrumb/index.ts +16 -6
- package/dist/src/components/button/Button.astro +2 -2
- package/dist/src/components/button/index.ts +4 -2
- package/dist/src/components/card/Card.astro +2 -2
- package/dist/src/components/card/CardContent.astro +2 -2
- package/dist/src/components/card/CardDescription.astro +2 -2
- package/dist/src/components/card/CardFooter.astro +2 -2
- package/dist/src/components/card/CardHeader.astro +2 -2
- package/dist/src/components/card/CardTitle.astro +2 -2
- package/dist/src/components/card/index.ts +16 -7
- package/dist/src/components/carousel/Carousel.astro +55 -0
- package/dist/src/components/carousel/CarouselContent.astro +26 -0
- package/dist/src/components/carousel/CarouselItem.astro +26 -0
- package/dist/src/components/carousel/CarouselNext.astro +33 -0
- package/dist/src/components/carousel/CarouselPrevious.astro +33 -0
- package/dist/src/components/carousel/carousel-script.ts +191 -0
- package/dist/src/components/carousel/index.ts +32 -0
- package/dist/src/components/checkbox/Checkbox.astro +10 -3
- package/dist/src/components/checkbox/index.ts +4 -2
- package/dist/src/components/dialog/Dialog.astro +24 -11
- package/dist/src/components/dialog/DialogClose.astro +7 -2
- package/dist/src/components/dialog/DialogContent.astro +6 -3
- package/dist/src/components/dialog/DialogDescription.astro +2 -2
- package/dist/src/components/dialog/DialogFooter.astro +2 -2
- package/dist/src/components/dialog/DialogHeader.astro +2 -2
- package/dist/src/components/dialog/DialogTitle.astro +2 -2
- package/dist/src/components/dialog/DialogTrigger.astro +7 -1
- package/dist/src/components/dialog/index.ts +20 -5
- package/dist/src/components/dropdown/Dropdown.astro +1 -0
- package/dist/src/components/dropdown/DropdownContent.astro +2 -1
- package/dist/src/components/dropdown/DropdownItem.astro +2 -1
- package/dist/src/components/dropdown/DropdownLabel.astro +2 -2
- package/dist/src/components/dropdown/DropdownSeparator.astro +2 -1
- package/dist/src/components/dropdown/DropdownTrigger.astro +7 -2
- package/dist/src/components/dropdown/index.ts +14 -5
- package/dist/src/components/dropzone/Dropzone.astro +3 -2
- package/dist/src/components/dropzone/DropzoneFilesList.astro +3 -2
- package/dist/src/components/dropzone/DropzoneLoadingIndicator.astro +1 -1
- package/dist/src/components/dropzone/DropzoneUploadIndicator.astro +1 -1
- package/dist/src/components/dropzone/index.ts +14 -3
- package/dist/src/components/input/Input.astro +2 -2
- package/dist/src/components/input/index.ts +4 -2
- package/dist/src/components/label/Label.astro +2 -2
- package/dist/src/components/label/index.ts +4 -2
- package/dist/src/components/pagination/Pagination.astro +8 -2
- package/dist/src/components/pagination/PaginationContent.astro +2 -2
- package/dist/src/components/pagination/PaginationEllipsis.astro +7 -2
- package/dist/src/components/pagination/PaginationItem.astro +2 -2
- package/dist/src/components/pagination/PaginationLink.astro +2 -1
- package/dist/src/components/pagination/PaginationNext.astro +2 -1
- package/dist/src/components/pagination/PaginationPrevious.astro +2 -1
- package/dist/src/components/pagination/index.ts +18 -7
- package/dist/src/components/progress/Progress.astro +5 -2
- package/dist/src/components/progress/index.ts +7 -2
- package/dist/src/components/radio-group/RadioGroup.astro +2 -1
- package/dist/src/components/radio-group/RadioGroupItem.astro +7 -6
- package/dist/src/components/radio-group/index.ts +16 -3
- package/dist/src/components/select/Select.astro +1 -0
- package/dist/src/components/select/SelectContent.astro +3 -2
- package/dist/src/components/select/SelectGroup.astro +1 -1
- package/dist/src/components/select/SelectItem.astro +3 -2
- package/dist/src/components/select/SelectLabel.astro +2 -2
- package/dist/src/components/select/SelectSeparator.astro +2 -2
- package/dist/src/components/select/SelectTrigger.astro +2 -1
- package/dist/src/components/select/SelectValue.astro +2 -2
- package/dist/src/components/select/index.ts +18 -6
- package/dist/src/components/sheet/Sheet.astro +13 -0
- package/dist/src/components/sheet/SheetClose.astro +13 -0
- package/dist/src/components/sheet/SheetContent.astro +90 -0
- package/dist/src/components/sheet/SheetDescription.astro +16 -0
- package/dist/src/components/sheet/SheetFooter.astro +16 -0
- package/dist/src/components/sheet/SheetHeader.astro +16 -0
- package/dist/src/components/sheet/SheetTitle.astro +16 -0
- package/dist/src/components/sheet/SheetTrigger.astro +13 -0
- package/dist/src/components/sheet/index.ts +41 -0
- package/dist/src/components/skeleton/Skeleton.astro +2 -2
- package/dist/src/components/skeleton/index.ts +6 -2
- package/dist/src/components/switch/Switch.astro +6 -4
- package/dist/src/components/switch/index.ts +8 -2
- package/dist/src/components/table/Table.astro +3 -3
- package/dist/src/components/table/TableBody.astro +2 -2
- package/dist/src/components/table/TableCaption.astro +2 -2
- package/dist/src/components/table/TableCell.astro +2 -2
- package/dist/src/components/table/TableFoot.astro +2 -2
- package/dist/src/components/table/TableHead.astro +2 -2
- package/dist/src/components/table/TableHeader.astro +2 -2
- package/dist/src/components/table/TableRow.astro +2 -2
- package/dist/src/components/table/index.ts +30 -9
- package/dist/src/components/tabs/Tabs.astro +2 -1
- package/dist/src/components/tabs/TabsContent.astro +4 -1
- package/dist/src/components/tabs/TabsList.astro +8 -2
- package/dist/src/components/tabs/TabsTrigger.astro +2 -1
- package/dist/src/components/tabs/index.ts +12 -5
- package/dist/src/components/textarea/Textarea.astro +2 -2
- package/dist/src/components/textarea/index.ts +6 -2
- package/dist/src/components/tooltip/Tooltip.astro +2 -1
- package/dist/src/components/tooltip/TooltipContent.astro +2 -1
- package/dist/src/components/tooltip/TooltipTrigger.astro +1 -1
- package/dist/src/components/tooltip/index.ts +8 -3
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { type EmblaOptionsType } from "embla-carousel";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
const carousel = tv({
|
|
7
|
+
base: "starwind-carousel group/carousel relative",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export interface Props extends HTMLAttributes<"div"> {
|
|
11
|
+
orientation?: "horizontal" | "vertical";
|
|
12
|
+
opts?: EmblaOptionsType;
|
|
13
|
+
autoInit?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
class: className,
|
|
18
|
+
orientation = "horizontal",
|
|
19
|
+
opts = {},
|
|
20
|
+
autoInit = true,
|
|
21
|
+
...rest
|
|
22
|
+
} = Astro.props;
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class={carousel({ class: className })}
|
|
27
|
+
role="region"
|
|
28
|
+
aria-roledescription="carousel"
|
|
29
|
+
data-slot="carousel"
|
|
30
|
+
data-axis={orientation === "horizontal" ? "x" : "y"}
|
|
31
|
+
data-opts={JSON.stringify(opts)}
|
|
32
|
+
data-auto-init={autoInit}
|
|
33
|
+
{...rest}
|
|
34
|
+
>
|
|
35
|
+
<slot />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
import { initCarousel } from "./index";
|
|
40
|
+
|
|
41
|
+
const setupCarousels = () => {
|
|
42
|
+
const carousels = document.querySelectorAll(".starwind-carousel") as NodeListOf<HTMLElement>;
|
|
43
|
+
carousels.forEach((carousel) => {
|
|
44
|
+
if (carousel.dataset.autoInit === "false") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
initCarousel(carousel);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
document.addEventListener("DOMContentLoaded", setupCarousels);
|
|
52
|
+
|
|
53
|
+
// Re-initialize after Astro page transitions
|
|
54
|
+
document.addEventListener("astro:after-swap", setupCarousels);
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const carouselContent = tv({
|
|
6
|
+
base: "overflow-hidden",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const carouselContainer = tv({
|
|
10
|
+
base: [
|
|
11
|
+
"flex group-data-[axis=y]/carousel:flex-col",
|
|
12
|
+
"group-data-[axis=x]/carousel:-ml-4",
|
|
13
|
+
"group-data-[axis=y]/carousel:-mt-4",
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
type Props = HTMLAttributes<"div">;
|
|
18
|
+
|
|
19
|
+
const { class: className = "", ...rest } = Astro.props;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<div class={carouselContent()} data-slot="carousel-content" {...rest}>
|
|
23
|
+
<div class={carouselContainer({ class: className })} data-slot="carousel-container">
|
|
24
|
+
<slot />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const carouselItem = tv({
|
|
6
|
+
base: [
|
|
7
|
+
"min-w-0 shrink-0 grow-0 basis-full",
|
|
8
|
+
"group-data-[axis=x]/carousel:pl-4",
|
|
9
|
+
"group-data-[axis=y]/carousel:pt-4",
|
|
10
|
+
],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
type Props = HTMLAttributes<"div">;
|
|
14
|
+
|
|
15
|
+
const { class: className = "", ...rest } = Astro.props;
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<div
|
|
19
|
+
role="group"
|
|
20
|
+
aria-roledescription="slide"
|
|
21
|
+
data-slot="carousel-item"
|
|
22
|
+
class={carouselItem({ class: className })}
|
|
23
|
+
{...rest}
|
|
24
|
+
>
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ArrowRight from "@tabler/icons/outline/arrow-right.svg";
|
|
3
|
+
import type { ComponentProps } from "astro/types";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/starwind/button";
|
|
7
|
+
|
|
8
|
+
export const carouselNext = tv({
|
|
9
|
+
base: [
|
|
10
|
+
"starwind-carousel-next absolute size-8 rounded-full",
|
|
11
|
+
// Horizontal positioning
|
|
12
|
+
"group-data-[axis=x]/carousel:top-1/2 group-data-[axis=x]/carousel:-right-12 group-data-[axis=x]/carousel:-translate-y-1/2",
|
|
13
|
+
// Vertical positioning
|
|
14
|
+
"group-data-[axis=y]/carousel:-bottom-12 group-data-[axis=y]/carousel:left-1/2 group-data-[axis=y]/carousel:-translate-x-1/2 group-data-[axis=y]/carousel:rotate-90",
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
type Props = ComponentProps<typeof Button>;
|
|
19
|
+
|
|
20
|
+
const { class: className = "", variant = "outline", size = "icon", ...rest } = Astro.props;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<Button
|
|
24
|
+
data-slot="carousel-next"
|
|
25
|
+
variant={variant}
|
|
26
|
+
size={size}
|
|
27
|
+
class={carouselNext({ class: className })}
|
|
28
|
+
aria-label="Next slide"
|
|
29
|
+
{...rest}
|
|
30
|
+
>
|
|
31
|
+
<ArrowRight />
|
|
32
|
+
<span class="sr-only">Next slide</span>
|
|
33
|
+
</Button>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ArrowLeft from "@tabler/icons/outline/arrow-left.svg";
|
|
3
|
+
import type { ComponentProps } from "astro/types";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/starwind/button";
|
|
7
|
+
|
|
8
|
+
export const carouselPrevious = tv({
|
|
9
|
+
base: [
|
|
10
|
+
"starwind-carousel-previous absolute size-8 rounded-full",
|
|
11
|
+
// Horizontal positioning
|
|
12
|
+
"group-data-[axis=x]/carousel:top-1/2 group-data-[axis=x]/carousel:-left-12 group-data-[axis=x]/carousel:-translate-y-1/2",
|
|
13
|
+
// Vertical positioning
|
|
14
|
+
"group-data-[axis=y]/carousel:-top-12 group-data-[axis=y]/carousel:left-1/2 group-data-[axis=y]/carousel:-translate-x-1/2 group-data-[axis=y]/carousel:rotate-90",
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
type Props = ComponentProps<typeof Button>;
|
|
19
|
+
|
|
20
|
+
const { class: className = "", variant = "outline", size = "icon", ...rest } = Astro.props;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<Button
|
|
24
|
+
data-slot="carousel-previous"
|
|
25
|
+
variant={variant}
|
|
26
|
+
size={size}
|
|
27
|
+
class={carouselPrevious({ class: className })}
|
|
28
|
+
aria-label="Previous slide"
|
|
29
|
+
{...rest}
|
|
30
|
+
>
|
|
31
|
+
<ArrowLeft />
|
|
32
|
+
<span class="sr-only">Previous slide</span>
|
|
33
|
+
</Button>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import EmblaCarousel, {
|
|
2
|
+
type EmblaCarouselType,
|
|
3
|
+
type EmblaEventType,
|
|
4
|
+
type EmblaOptionsType,
|
|
5
|
+
type EmblaPluginType,
|
|
6
|
+
} from "embla-carousel";
|
|
7
|
+
|
|
8
|
+
export type CarouselApi = EmblaCarouselType;
|
|
9
|
+
|
|
10
|
+
export interface CarouselOptions {
|
|
11
|
+
opts?: EmblaOptionsType;
|
|
12
|
+
plugins?: EmblaPluginType[];
|
|
13
|
+
setApi?: (api: CarouselApi) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CarouselManager {
|
|
17
|
+
api: CarouselApi;
|
|
18
|
+
scrollPrev: () => void;
|
|
19
|
+
scrollNext: () => void;
|
|
20
|
+
canScrollPrev: () => boolean;
|
|
21
|
+
canScrollNext: () => boolean;
|
|
22
|
+
destroy: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function initCarousel(
|
|
26
|
+
carouselElement: HTMLElement,
|
|
27
|
+
options: CarouselOptions = {},
|
|
28
|
+
): CarouselManager | null {
|
|
29
|
+
// don't re-initialize if already initialized
|
|
30
|
+
if (carouselElement.dataset.initialized === "true") return null;
|
|
31
|
+
carouselElement.dataset.initialized = "true";
|
|
32
|
+
|
|
33
|
+
if (!carouselElement) {
|
|
34
|
+
console.warn("Carousel element not found");
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Find content element - Embla expects the viewport element, not the container
|
|
39
|
+
const viewportElement = carouselElement.querySelector(
|
|
40
|
+
'[data-slot="carousel-content"]',
|
|
41
|
+
) as HTMLElement;
|
|
42
|
+
if (!viewportElement) {
|
|
43
|
+
console.warn("Carousel content element not found");
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get configuration from data attributes
|
|
48
|
+
const axisData = carouselElement.dataset.axis;
|
|
49
|
+
const axis: EmblaOptionsType["axis"] = axisData === "y" ? "y" : "x";
|
|
50
|
+
|
|
51
|
+
// Safely parse data options
|
|
52
|
+
let dataOpts = {};
|
|
53
|
+
try {
|
|
54
|
+
const optsString = carouselElement.dataset.opts;
|
|
55
|
+
if (optsString && optsString !== "undefined" && optsString !== "null") {
|
|
56
|
+
dataOpts = JSON.parse(optsString);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn("Failed to parse carousel opts:", e);
|
|
60
|
+
dataOpts = {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Ensure dataOpts is a valid object
|
|
64
|
+
if (!dataOpts || typeof dataOpts !== "object") {
|
|
65
|
+
dataOpts = {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Merge options - ensure we always have a valid object
|
|
69
|
+
const emblaOptions: EmblaOptionsType = {
|
|
70
|
+
axis,
|
|
71
|
+
...dataOpts,
|
|
72
|
+
...(options.opts || {}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Handle plugins - EmblaCarousel expects undefined when no plugins, not empty array
|
|
76
|
+
const plugins = options.plugins && options.plugins.length > 0 ? options.plugins : undefined;
|
|
77
|
+
|
|
78
|
+
// console.log("ID:", carouselElement.id);
|
|
79
|
+
// console.log("Plugins:", plugins);
|
|
80
|
+
// console.log("Options:", emblaOptions);
|
|
81
|
+
|
|
82
|
+
// Find navigation buttons
|
|
83
|
+
const prevButton = carouselElement.querySelector(
|
|
84
|
+
'[data-slot="carousel-previous"]',
|
|
85
|
+
) as HTMLButtonElement;
|
|
86
|
+
const nextButton = carouselElement.querySelector(
|
|
87
|
+
'[data-slot="carousel-next"]',
|
|
88
|
+
) as HTMLButtonElement;
|
|
89
|
+
|
|
90
|
+
// Initialize Embla
|
|
91
|
+
let emblaApi: EmblaCarouselType;
|
|
92
|
+
if (plugins) {
|
|
93
|
+
emblaApi = EmblaCarousel(viewportElement, emblaOptions, plugins);
|
|
94
|
+
} else {
|
|
95
|
+
emblaApi = EmblaCarousel(viewportElement, emblaOptions);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Update button states
|
|
99
|
+
const updateButtons = () => {
|
|
100
|
+
const canScrollPrev = emblaApi.canScrollPrev();
|
|
101
|
+
const canScrollNext = emblaApi.canScrollNext();
|
|
102
|
+
|
|
103
|
+
if (prevButton) {
|
|
104
|
+
prevButton.disabled = !canScrollPrev;
|
|
105
|
+
prevButton.setAttribute("aria-disabled", (!canScrollPrev).toString());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (nextButton) {
|
|
109
|
+
nextButton.disabled = !canScrollNext;
|
|
110
|
+
nextButton.setAttribute("aria-disabled", (!canScrollNext).toString());
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Event handlers for cleanup
|
|
115
|
+
const prevClickHandler = () => emblaApi.scrollPrev();
|
|
116
|
+
const nextClickHandler = () => emblaApi.scrollNext();
|
|
117
|
+
const keydownHandler = (event: KeyboardEvent) => {
|
|
118
|
+
if (axis === "y") {
|
|
119
|
+
// Vertical axis: ArrowUp = previous, ArrowDown = next
|
|
120
|
+
if (event.key === "ArrowUp") {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
emblaApi.scrollPrev();
|
|
123
|
+
} else if (event.key === "ArrowDown") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
emblaApi.scrollNext();
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Horizontal axis (default): ArrowLeft = previous, ArrowRight = next
|
|
129
|
+
if (event.key === "ArrowLeft") {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
emblaApi.scrollPrev();
|
|
132
|
+
} else if (event.key === "ArrowRight") {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
emblaApi.scrollNext();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Setup event listeners
|
|
140
|
+
const setupEventListeners = () => {
|
|
141
|
+
// Navigation button listeners
|
|
142
|
+
prevButton?.addEventListener("click", prevClickHandler);
|
|
143
|
+
nextButton?.addEventListener("click", nextClickHandler);
|
|
144
|
+
|
|
145
|
+
// Keyboard navigation
|
|
146
|
+
carouselElement.addEventListener("keydown", keydownHandler);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Setup user API callback
|
|
150
|
+
const setupUserCallbacks = () => {
|
|
151
|
+
if (options.setApi) {
|
|
152
|
+
options.setApi(emblaApi);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Initialize everything
|
|
157
|
+
updateButtons();
|
|
158
|
+
setupEventListeners();
|
|
159
|
+
setupUserCallbacks();
|
|
160
|
+
|
|
161
|
+
// Setup internal event listeners
|
|
162
|
+
emblaApi.on("select", updateButtons);
|
|
163
|
+
emblaApi.on("init", () => {
|
|
164
|
+
updateButtons();
|
|
165
|
+
});
|
|
166
|
+
emblaApi.on("reInit", () => {
|
|
167
|
+
updateButtons();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Return manager interface
|
|
171
|
+
return {
|
|
172
|
+
api: emblaApi,
|
|
173
|
+
scrollPrev: () => emblaApi.scrollPrev(),
|
|
174
|
+
scrollNext: () => emblaApi.scrollNext(),
|
|
175
|
+
canScrollPrev: () => emblaApi.canScrollPrev(),
|
|
176
|
+
canScrollNext: () => emblaApi.canScrollNext(),
|
|
177
|
+
destroy: () => {
|
|
178
|
+
// Remove event listeners to prevent memory leaks
|
|
179
|
+
if (prevButton) {
|
|
180
|
+
prevButton.removeEventListener("click", prevClickHandler);
|
|
181
|
+
}
|
|
182
|
+
if (nextButton) {
|
|
183
|
+
nextButton.removeEventListener("click", nextClickHandler);
|
|
184
|
+
}
|
|
185
|
+
carouselElement.removeEventListener("keydown", keydownHandler);
|
|
186
|
+
|
|
187
|
+
// Destroy the Embla instance
|
|
188
|
+
emblaApi.destroy();
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Carousel from "./Carousel.astro";
|
|
2
|
+
import {
|
|
3
|
+
type CarouselApi,
|
|
4
|
+
type CarouselManager,
|
|
5
|
+
type CarouselOptions,
|
|
6
|
+
initCarousel,
|
|
7
|
+
} from "./carousel-script";
|
|
8
|
+
import CarouselContent from "./CarouselContent.astro";
|
|
9
|
+
import CarouselItem from "./CarouselItem.astro";
|
|
10
|
+
import CarouselNext from "./CarouselNext.astro";
|
|
11
|
+
import CarouselPrevious from "./CarouselPrevious.astro";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
Carousel,
|
|
15
|
+
type CarouselApi,
|
|
16
|
+
CarouselContent,
|
|
17
|
+
CarouselItem,
|
|
18
|
+
type CarouselManager,
|
|
19
|
+
CarouselNext,
|
|
20
|
+
type CarouselOptions,
|
|
21
|
+
CarouselPrevious,
|
|
22
|
+
initCarousel,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
Root: Carousel,
|
|
27
|
+
Content: CarouselContent,
|
|
28
|
+
Item: CarouselItem,
|
|
29
|
+
Next: CarouselNext,
|
|
30
|
+
Previous: CarouselPrevious,
|
|
31
|
+
init: initCarousel,
|
|
32
|
+
};
|
|
@@ -11,7 +11,7 @@ type Props = Omit<HTMLAttributes<"input">, "type"> &
|
|
|
11
11
|
label?: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const checkbox = tv({
|
|
14
|
+
export const checkbox = tv({
|
|
15
15
|
slots: {
|
|
16
16
|
base: "starwind-checkbox relative flex items-center space-x-2",
|
|
17
17
|
input: [
|
|
@@ -70,11 +70,18 @@ const { base, input, icon, label: labelClass } = checkbox({ size, variant });
|
|
|
70
70
|
---
|
|
71
71
|
|
|
72
72
|
<div class={base()}>
|
|
73
|
-
<input
|
|
73
|
+
<input
|
|
74
|
+
type="checkbox"
|
|
75
|
+
id={id}
|
|
76
|
+
class={input({ class: className })}
|
|
77
|
+
data-slot="checkbox-input"
|
|
78
|
+
{checked}
|
|
79
|
+
{...rest}
|
|
80
|
+
/>
|
|
74
81
|
<Check class={icon()} />
|
|
75
82
|
{
|
|
76
83
|
label && (
|
|
77
|
-
<label for={id} class={labelClass()}>
|
|
84
|
+
<label for={id} class={labelClass()} data-slot="checkbox-label">
|
|
78
85
|
{label}
|
|
79
86
|
</label>
|
|
80
87
|
)
|
|
@@ -6,16 +6,16 @@ type Props = HTMLAttributes<"div">;
|
|
|
6
6
|
const { class: className, ...rest } = Astro.props;
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
<div class:list={["starwind-dialog", className]} {...rest}>
|
|
9
|
+
<div class:list={["starwind-dialog", className]} data-slot="dialog" {...rest}>
|
|
10
10
|
<slot />
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<script>
|
|
14
14
|
class DialogHandler {
|
|
15
15
|
private triggers: HTMLButtonElement[] = [];
|
|
16
|
-
private dialog: HTMLDialogElement;
|
|
16
|
+
private dialog: HTMLDialogElement | null = null;
|
|
17
17
|
private closeButtons: HTMLButtonElement[] = [];
|
|
18
|
-
private backdrop: HTMLElement;
|
|
18
|
+
private backdrop: HTMLElement | null = null;
|
|
19
19
|
private dialogId: string;
|
|
20
20
|
/**
|
|
21
21
|
* The duration of the animation in milliseconds. This is used to calculate the
|
|
@@ -24,8 +24,11 @@ const { class: className, ...rest } = Astro.props;
|
|
|
24
24
|
private animationDuration: number;
|
|
25
25
|
|
|
26
26
|
constructor(dialogWrapper: HTMLElement, dialogNumber: number) {
|
|
27
|
-
this.dialog = dialogWrapper.querySelector("dialog")
|
|
28
|
-
this.backdrop = dialogWrapper.querySelector(".starwind-dialog-backdrop")
|
|
27
|
+
this.dialog = dialogWrapper.querySelector("dialog");
|
|
28
|
+
this.backdrop = dialogWrapper.querySelector(".starwind-dialog-backdrop");
|
|
29
|
+
if (!this.dialog || !this.backdrop) {
|
|
30
|
+
throw new Error("Dialog: dialog or backdrop not found");
|
|
31
|
+
}
|
|
29
32
|
|
|
30
33
|
// if no ID was provided for the wrapper, generate one
|
|
31
34
|
if (dialogWrapper.id) {
|
|
@@ -36,6 +39,7 @@ const { class: className, ...rest } = Astro.props;
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// animationDuration is set with inline styles through passed prop to DialogContent
|
|
42
|
+
// if no animationDuration, check data-close-duration
|
|
39
43
|
const animationDurationString = this.dialog.style.animationDuration;
|
|
40
44
|
if (animationDurationString.endsWith("ms")) {
|
|
41
45
|
this.animationDuration = parseFloat(animationDurationString);
|
|
@@ -43,7 +47,9 @@ const { class: className, ...rest } = Astro.props;
|
|
|
43
47
|
// using something like @playform/compress might optimize to use "s" instead of "ms"
|
|
44
48
|
this.animationDuration = parseFloat(animationDurationString) * 1000;
|
|
45
49
|
} else {
|
|
46
|
-
this.animationDuration =
|
|
50
|
+
this.animationDuration = this.dialog.dataset.closeDuration
|
|
51
|
+
? parseFloat(this.dialog.dataset.closeDuration)
|
|
52
|
+
: 200;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
// Find internal triggers and handle them
|
|
@@ -95,12 +101,12 @@ const { class: className, ...rest } = Astro.props;
|
|
|
95
101
|
|
|
96
102
|
private setupAccessibility(dialogNumber: number): void {
|
|
97
103
|
// get the first heading element in the dialog
|
|
98
|
-
const firstHeading = this.dialog
|
|
104
|
+
const firstHeading = this.dialog?.querySelector("h1, h2, h3, h4, h5, h6");
|
|
99
105
|
if (firstHeading) {
|
|
100
106
|
// create a unique ID for the heading
|
|
101
107
|
firstHeading.id = `starwind-dialog${dialogNumber}-heading`;
|
|
102
108
|
// set the aria-labelledby attribute to the first heading element
|
|
103
|
-
this.dialog
|
|
109
|
+
this.dialog?.setAttribute("aria-labelledby", firstHeading.id);
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
|
|
@@ -133,6 +139,7 @@ const { class: className, ...rest } = Astro.props;
|
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
private setupEvents(): void {
|
|
142
|
+
if (!this.dialog) return;
|
|
136
143
|
// Add click listeners to all triggers
|
|
137
144
|
this.triggers.forEach((trigger) => {
|
|
138
145
|
trigger.addEventListener("click", () => {
|
|
@@ -153,6 +160,7 @@ const { class: className, ...rest } = Astro.props;
|
|
|
153
160
|
|
|
154
161
|
// Close on click outside
|
|
155
162
|
this.dialog.addEventListener("click", (e) => {
|
|
163
|
+
if (!this.dialog) return;
|
|
156
164
|
const dialogDimensions = this.dialog.getBoundingClientRect();
|
|
157
165
|
const clickedInDialog =
|
|
158
166
|
e.clientX >= dialogDimensions.left &&
|
|
@@ -206,6 +214,7 @@ const { class: className, ...rest } = Astro.props;
|
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
private open(): void {
|
|
217
|
+
if (!this.dialog || !this.backdrop) return;
|
|
209
218
|
this.dialog.showModal();
|
|
210
219
|
document.body.classList.add("overflow-hidden");
|
|
211
220
|
this.backdrop.classList.remove("hidden");
|
|
@@ -214,14 +223,18 @@ const { class: className, ...rest } = Astro.props;
|
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
private close(): void {
|
|
217
|
-
|
|
226
|
+
if (!this.dialog || !this.backdrop) return;
|
|
218
227
|
this.dialog.dataset.state = "closed";
|
|
219
228
|
this.backdrop.dataset.state = "closed";
|
|
220
229
|
|
|
221
230
|
// Wait for animation to finish before hiding backdrop
|
|
222
231
|
setTimeout(() => {
|
|
223
|
-
this.backdrop
|
|
224
|
-
this.dialog
|
|
232
|
+
this.backdrop?.classList.add("hidden");
|
|
233
|
+
this.dialog?.close();
|
|
234
|
+
const stillOpen = document.querySelectorAll("dialog[open]").length;
|
|
235
|
+
if (stillOpen === 0) {
|
|
236
|
+
document.body.classList.remove("overflow-hidden");
|
|
237
|
+
}
|
|
225
238
|
}, this.animationDuration);
|
|
226
239
|
}
|
|
227
240
|
}
|
|
@@ -19,11 +19,16 @@ if (Astro.slots.has("default")) {
|
|
|
19
19
|
|
|
20
20
|
{
|
|
21
21
|
asChild && hasChildren ? (
|
|
22
|
-
<div class="starwind-dialog-close" data-as-child>
|
|
22
|
+
<div class="starwind-dialog-close" data-slot="dialog-close" data-as-child>
|
|
23
23
|
<slot />
|
|
24
24
|
</div>
|
|
25
25
|
) : (
|
|
26
|
-
<button
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
class:list={["starwind-dialog-close", className]}
|
|
29
|
+
data-slot="dialog-close"
|
|
30
|
+
{...rest}
|
|
31
|
+
>
|
|
27
32
|
<slot>Demo close button</slot>
|
|
28
33
|
</button>
|
|
29
34
|
)
|
|
@@ -10,7 +10,7 @@ type Props = HTMLAttributes<"dialog"> & {
|
|
|
10
10
|
animationDuration?: number;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
const dialogBackdrop = tv({
|
|
13
|
+
export const dialogBackdrop = tv({
|
|
14
14
|
base: [
|
|
15
15
|
"starwind-dialog-backdrop fixed inset-0 top-0 left-0 z-50 hidden h-screen w-screen bg-black/80",
|
|
16
16
|
"data-[state=open]:animate-in fade-in",
|
|
@@ -18,7 +18,7 @@ const dialogBackdrop = tv({
|
|
|
18
18
|
],
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const dialogContent = tv({
|
|
21
|
+
export const dialogContent = tv({
|
|
22
22
|
base: [
|
|
23
23
|
"fixed top-16 left-[50%] z-50 translate-x-[-50%] sm:top-[50%] sm:translate-y-[-50%]",
|
|
24
24
|
"bg-background w-full max-w-md border p-8 shadow-lg sm:rounded-lg",
|
|
@@ -28,7 +28,7 @@ const dialogContent = tv({
|
|
|
28
28
|
],
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const dialogCloseButton = tv({
|
|
31
|
+
export const dialogCloseButton = tv({
|
|
32
32
|
base: [
|
|
33
33
|
"starwind-dialog-close text-muted-foreground",
|
|
34
34
|
"absolute top-5.5 right-5.5 rounded-sm opacity-70 transition-opacity hover:opacity-100 disabled:pointer-events-none",
|
|
@@ -43,6 +43,7 @@ const { class: className, animationDuration = 200, ...rest } = Astro.props;
|
|
|
43
43
|
<div
|
|
44
44
|
class={dialogBackdrop()}
|
|
45
45
|
data-state="closed"
|
|
46
|
+
data-slot="dialog-backdrop"
|
|
46
47
|
style={{ animationDuration: `${animationDuration}ms` }}
|
|
47
48
|
>
|
|
48
49
|
</div>
|
|
@@ -50,11 +51,13 @@ const { class: className, animationDuration = 200, ...rest } = Astro.props;
|
|
|
50
51
|
<dialog
|
|
51
52
|
class={dialogContent({ class: className })}
|
|
52
53
|
data-state="closed"
|
|
54
|
+
data-slot="dialog-content"
|
|
53
55
|
{...rest}
|
|
54
56
|
style={{ animationDuration: `${animationDuration}ms` }}
|
|
55
57
|
>
|
|
56
58
|
<slot />
|
|
57
59
|
<button type="button" class={dialogCloseButton()} data-dialog-close aria-label="Close dialog">
|
|
58
60
|
<X class="size-5" />
|
|
61
|
+
<span class="sr-only">Close</span>
|
|
59
62
|
</button>
|
|
60
63
|
</dialog>
|
|
@@ -4,11 +4,11 @@ import { tv } from "tailwind-variants";
|
|
|
4
4
|
|
|
5
5
|
type Props = HTMLAttributes<"p">;
|
|
6
6
|
|
|
7
|
-
const dialogDescription = tv({ base: "text-muted-foreground" });
|
|
7
|
+
export const dialogDescription = tv({ base: "text-muted-foreground" });
|
|
8
8
|
|
|
9
9
|
const { class: className, ...rest } = Astro.props;
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<p class={dialogDescription({ class: className })} {...rest}>
|
|
12
|
+
<p class={dialogDescription({ class: className })} data-slot="dialog-description" {...rest}>
|
|
13
13
|
<slot />
|
|
14
14
|
</p>
|
|
@@ -4,11 +4,11 @@ import { tv } from "tailwind-variants";
|
|
|
4
4
|
|
|
5
5
|
type Props = HTMLAttributes<"div">;
|
|
6
6
|
|
|
7
|
-
const dialogFooter = tv({ base: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end" });
|
|
7
|
+
export const dialogFooter = tv({ base: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end" });
|
|
8
8
|
|
|
9
9
|
const { class: className, ...rest } = Astro.props;
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<div class={dialogFooter({ class: className })} {...rest}>
|
|
12
|
+
<div class={dialogFooter({ class: className })} data-slot="dialog-footer" {...rest}>
|
|
13
13
|
<slot />
|
|
14
14
|
</div>
|
|
@@ -4,11 +4,11 @@ import { tv } from "tailwind-variants";
|
|
|
4
4
|
|
|
5
5
|
type Props = HTMLAttributes<"div">;
|
|
6
6
|
|
|
7
|
-
const dialogHeader = tv({ base: "flex flex-col space-y-2 text-center sm:text-left" });
|
|
7
|
+
export const dialogHeader = tv({ base: "flex flex-col space-y-2 text-center sm:text-left" });
|
|
8
8
|
|
|
9
9
|
const { class: className, ...rest } = Astro.props;
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<div class={dialogHeader({ class: className })} {...rest}>
|
|
12
|
+
<div class={dialogHeader({ class: className })} data-slot="dialog-header" {...rest}>
|
|
13
13
|
<slot />
|
|
14
14
|
</div>
|
|
@@ -10,11 +10,11 @@ type Props = Omit<HTMLAttributes<"h2">, "id"> & {
|
|
|
10
10
|
children: any;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
const dialogTitle = tv({ base: "text-2xl leading-none font-semibold tracking-tight" });
|
|
13
|
+
export const dialogTitle = tv({ base: "text-2xl leading-none font-semibold tracking-tight" });
|
|
14
14
|
|
|
15
15
|
const { class: className, ...rest } = Astro.props;
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
<h2 class={dialogTitle({ class: className })} {...rest}>
|
|
18
|
+
<h2 class={dialogTitle({ class: className })} data-slot="dialog-title" {...rest}>
|
|
19
19
|
<slot />
|
|
20
20
|
</h2>
|