@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.
Files changed (134) hide show
  1. package/dist/index.js +41 -23
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/accordion/Accordion.astro +8 -2
  4. package/dist/src/components/accordion/AccordionContent.astro +2 -1
  5. package/dist/src/components/accordion/AccordionItem.astro +8 -2
  6. package/dist/src/components/accordion/AccordionTrigger.astro +2 -1
  7. package/dist/src/components/accordion/index.ts +7 -5
  8. package/dist/src/components/alert/Alert.astro +5 -2
  9. package/dist/src/components/alert/AlertDescription.astro +4 -4
  10. package/dist/src/components/alert/AlertTitle.astro +3 -3
  11. package/dist/src/components/alert/index.ts +6 -4
  12. package/dist/src/components/alert-dialog/AlertDialog.astro +273 -0
  13. package/dist/src/components/alert-dialog/AlertDialogAction.astro +44 -0
  14. package/dist/src/components/alert-dialog/AlertDialogCancel.astro +45 -0
  15. package/dist/src/components/alert-dialog/AlertDialogContent.astro +50 -0
  16. package/dist/src/components/alert-dialog/AlertDialogDescription.astro +18 -0
  17. package/dist/src/components/alert-dialog/AlertDialogFooter.astro +16 -0
  18. package/dist/src/components/alert-dialog/AlertDialogHeader.astro +14 -0
  19. package/dist/src/components/alert-dialog/AlertDialogTitle.astro +20 -0
  20. package/dist/src/components/alert-dialog/AlertDialogTrigger.astro +47 -0
  21. package/dist/src/components/alert-dialog/index.ts +46 -0
  22. package/dist/src/components/avatar/Avatar.astro +2 -2
  23. package/dist/src/components/avatar/AvatarFallback.astro +2 -2
  24. package/dist/src/components/avatar/AvatarImage.astro +13 -2
  25. package/dist/src/components/avatar/index.ts +6 -4
  26. package/dist/src/components/badge/Badge.astro +2 -2
  27. package/dist/src/components/badge/index.ts +4 -2
  28. package/dist/src/components/breadcrumb/Breadcrumb.astro +1 -1
  29. package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +4 -1
  30. package/dist/src/components/breadcrumb/BreadcrumbItem.astro +2 -2
  31. package/dist/src/components/breadcrumb/BreadcrumbLink.astro +2 -2
  32. package/dist/src/components/breadcrumb/BreadcrumbList.astro +2 -2
  33. package/dist/src/components/breadcrumb/BreadcrumbPage.astro +2 -1
  34. package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +2 -1
  35. package/dist/src/components/breadcrumb/index.ts +16 -6
  36. package/dist/src/components/button/Button.astro +2 -2
  37. package/dist/src/components/button/index.ts +4 -2
  38. package/dist/src/components/card/Card.astro +2 -2
  39. package/dist/src/components/card/CardContent.astro +2 -2
  40. package/dist/src/components/card/CardDescription.astro +2 -2
  41. package/dist/src/components/card/CardFooter.astro +2 -2
  42. package/dist/src/components/card/CardHeader.astro +2 -2
  43. package/dist/src/components/card/CardTitle.astro +2 -2
  44. package/dist/src/components/card/index.ts +16 -7
  45. package/dist/src/components/carousel/Carousel.astro +55 -0
  46. package/dist/src/components/carousel/CarouselContent.astro +26 -0
  47. package/dist/src/components/carousel/CarouselItem.astro +26 -0
  48. package/dist/src/components/carousel/CarouselNext.astro +33 -0
  49. package/dist/src/components/carousel/CarouselPrevious.astro +33 -0
  50. package/dist/src/components/carousel/carousel-script.ts +191 -0
  51. package/dist/src/components/carousel/index.ts +32 -0
  52. package/dist/src/components/checkbox/Checkbox.astro +10 -3
  53. package/dist/src/components/checkbox/index.ts +4 -2
  54. package/dist/src/components/dialog/Dialog.astro +24 -11
  55. package/dist/src/components/dialog/DialogClose.astro +7 -2
  56. package/dist/src/components/dialog/DialogContent.astro +6 -3
  57. package/dist/src/components/dialog/DialogDescription.astro +2 -2
  58. package/dist/src/components/dialog/DialogFooter.astro +2 -2
  59. package/dist/src/components/dialog/DialogHeader.astro +2 -2
  60. package/dist/src/components/dialog/DialogTitle.astro +2 -2
  61. package/dist/src/components/dialog/DialogTrigger.astro +7 -1
  62. package/dist/src/components/dialog/index.ts +20 -5
  63. package/dist/src/components/dropdown/Dropdown.astro +1 -0
  64. package/dist/src/components/dropdown/DropdownContent.astro +2 -1
  65. package/dist/src/components/dropdown/DropdownItem.astro +2 -1
  66. package/dist/src/components/dropdown/DropdownLabel.astro +2 -2
  67. package/dist/src/components/dropdown/DropdownSeparator.astro +2 -1
  68. package/dist/src/components/dropdown/DropdownTrigger.astro +7 -2
  69. package/dist/src/components/dropdown/index.ts +14 -5
  70. package/dist/src/components/dropzone/Dropzone.astro +3 -2
  71. package/dist/src/components/dropzone/DropzoneFilesList.astro +3 -2
  72. package/dist/src/components/dropzone/DropzoneLoadingIndicator.astro +1 -1
  73. package/dist/src/components/dropzone/DropzoneUploadIndicator.astro +1 -1
  74. package/dist/src/components/dropzone/index.ts +14 -3
  75. package/dist/src/components/input/Input.astro +2 -2
  76. package/dist/src/components/input/index.ts +4 -2
  77. package/dist/src/components/label/Label.astro +2 -2
  78. package/dist/src/components/label/index.ts +4 -2
  79. package/dist/src/components/pagination/Pagination.astro +8 -2
  80. package/dist/src/components/pagination/PaginationContent.astro +2 -2
  81. package/dist/src/components/pagination/PaginationEllipsis.astro +7 -2
  82. package/dist/src/components/pagination/PaginationItem.astro +2 -2
  83. package/dist/src/components/pagination/PaginationLink.astro +2 -1
  84. package/dist/src/components/pagination/PaginationNext.astro +2 -1
  85. package/dist/src/components/pagination/PaginationPrevious.astro +2 -1
  86. package/dist/src/components/pagination/index.ts +18 -7
  87. package/dist/src/components/progress/Progress.astro +5 -2
  88. package/dist/src/components/progress/index.ts +7 -2
  89. package/dist/src/components/radio-group/RadioGroup.astro +2 -1
  90. package/dist/src/components/radio-group/RadioGroupItem.astro +7 -6
  91. package/dist/src/components/radio-group/index.ts +16 -3
  92. package/dist/src/components/select/Select.astro +1 -0
  93. package/dist/src/components/select/SelectContent.astro +3 -2
  94. package/dist/src/components/select/SelectGroup.astro +1 -1
  95. package/dist/src/components/select/SelectItem.astro +3 -2
  96. package/dist/src/components/select/SelectLabel.astro +2 -2
  97. package/dist/src/components/select/SelectSeparator.astro +2 -2
  98. package/dist/src/components/select/SelectTrigger.astro +2 -1
  99. package/dist/src/components/select/SelectValue.astro +2 -2
  100. package/dist/src/components/select/index.ts +18 -6
  101. package/dist/src/components/sheet/Sheet.astro +13 -0
  102. package/dist/src/components/sheet/SheetClose.astro +13 -0
  103. package/dist/src/components/sheet/SheetContent.astro +90 -0
  104. package/dist/src/components/sheet/SheetDescription.astro +16 -0
  105. package/dist/src/components/sheet/SheetFooter.astro +16 -0
  106. package/dist/src/components/sheet/SheetHeader.astro +16 -0
  107. package/dist/src/components/sheet/SheetTitle.astro +16 -0
  108. package/dist/src/components/sheet/SheetTrigger.astro +13 -0
  109. package/dist/src/components/sheet/index.ts +41 -0
  110. package/dist/src/components/skeleton/Skeleton.astro +2 -2
  111. package/dist/src/components/skeleton/index.ts +6 -2
  112. package/dist/src/components/switch/Switch.astro +6 -4
  113. package/dist/src/components/switch/index.ts +8 -2
  114. package/dist/src/components/table/Table.astro +3 -3
  115. package/dist/src/components/table/TableBody.astro +2 -2
  116. package/dist/src/components/table/TableCaption.astro +2 -2
  117. package/dist/src/components/table/TableCell.astro +2 -2
  118. package/dist/src/components/table/TableFoot.astro +2 -2
  119. package/dist/src/components/table/TableHead.astro +2 -2
  120. package/dist/src/components/table/TableHeader.astro +2 -2
  121. package/dist/src/components/table/TableRow.astro +2 -2
  122. package/dist/src/components/table/index.ts +30 -9
  123. package/dist/src/components/tabs/Tabs.astro +2 -1
  124. package/dist/src/components/tabs/TabsContent.astro +4 -1
  125. package/dist/src/components/tabs/TabsList.astro +8 -2
  126. package/dist/src/components/tabs/TabsTrigger.astro +2 -1
  127. package/dist/src/components/tabs/index.ts +12 -5
  128. package/dist/src/components/textarea/Textarea.astro +2 -2
  129. package/dist/src/components/textarea/index.ts +6 -2
  130. package/dist/src/components/tooltip/Tooltip.astro +2 -1
  131. package/dist/src/components/tooltip/TooltipContent.astro +2 -1
  132. package/dist/src/components/tooltip/TooltipTrigger.astro +1 -1
  133. package/dist/src/components/tooltip/index.ts +8 -3
  134. 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 type="checkbox" id={id} class={input({ class: className })} {checked} {...rest} />
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
  )
@@ -1,5 +1,7 @@
1
- import Checkbox from "./Checkbox.astro";
1
+ import Checkbox, { checkbox } from "./Checkbox.astro";
2
2
 
3
- export { Checkbox };
3
+ const CheckboxVariants = { checkbox };
4
+
5
+ export { Checkbox, CheckboxVariants };
4
6
 
5
7
  export default Checkbox;
@@ -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 = 200;
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.querySelector("h1, h2, h3, h4, h5, h6");
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.setAttribute("aria-labelledby", firstHeading.id);
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
- document.body.classList.remove("overflow-hidden");
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.classList.add("hidden");
224
- this.dialog.close();
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 type="button" class:list={["starwind-dialog-close", className]} {...rest}>
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>