@starwind-ui/core 1.11.2 → 1.12.1

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 (158) hide show
  1. package/package.json +1 -1
  2. package/dist/index.d.ts +0 -28
  3. package/dist/index.js +0 -74
  4. package/dist/index.js.map +0 -1
  5. package/dist/src/components/accordion/Accordion.astro +0 -247
  6. package/dist/src/components/accordion/AccordionContent.astro +0 -33
  7. package/dist/src/components/accordion/AccordionItem.astro +0 -27
  8. package/dist/src/components/accordion/AccordionTrigger.astro +0 -32
  9. package/dist/src/components/accordion/index.ts +0 -15
  10. package/dist/src/components/alert/Alert.astro +0 -31
  11. package/dist/src/components/alert/AlertDescription.astro +0 -14
  12. package/dist/src/components/alert/AlertTitle.astro +0 -16
  13. package/dist/src/components/alert/index.ts +0 -13
  14. package/dist/src/components/alert-dialog/AlertDialog.astro +0 -273
  15. package/dist/src/components/alert-dialog/AlertDialogAction.astro +0 -44
  16. package/dist/src/components/alert-dialog/AlertDialogCancel.astro +0 -45
  17. package/dist/src/components/alert-dialog/AlertDialogContent.astro +0 -52
  18. package/dist/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
  19. package/dist/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
  20. package/dist/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
  21. package/dist/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
  22. package/dist/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
  23. package/dist/src/components/alert-dialog/index.ts +0 -46
  24. package/dist/src/components/aspect-ratio/AspectRatio.astro +0 -32
  25. package/dist/src/components/aspect-ratio/index.ts +0 -7
  26. package/dist/src/components/avatar/Avatar.astro +0 -29
  27. package/dist/src/components/avatar/AvatarFallback.astro +0 -18
  28. package/dist/src/components/avatar/AvatarImage.astro +0 -49
  29. package/dist/src/components/avatar/index.ts +0 -13
  30. package/dist/src/components/badge/Badge.astro +0 -51
  31. package/dist/src/components/badge/index.ts +0 -7
  32. package/dist/src/components/breadcrumb/Breadcrumb.astro +0 -11
  33. package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -28
  34. package/dist/src/components/breadcrumb/BreadcrumbItem.astro +0 -14
  35. package/dist/src/components/breadcrumb/BreadcrumbLink.astro +0 -22
  36. package/dist/src/components/breadcrumb/BreadcrumbList.astro +0 -16
  37. package/dist/src/components/breadcrumb/BreadcrumbPage.astro +0 -21
  38. package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -23
  39. package/dist/src/components/breadcrumb/index.ts +0 -37
  40. package/dist/src/components/button/Button.astro +0 -53
  41. package/dist/src/components/button/index.ts +0 -7
  42. package/dist/src/components/card/Card.astro +0 -14
  43. package/dist/src/components/card/CardContent.astro +0 -14
  44. package/dist/src/components/card/CardDescription.astro +0 -14
  45. package/dist/src/components/card/CardFooter.astro +0 -14
  46. package/dist/src/components/card/CardHeader.astro +0 -14
  47. package/dist/src/components/card/CardTitle.astro +0 -14
  48. package/dist/src/components/card/index.ts +0 -26
  49. package/dist/src/components/carousel/Carousel.astro +0 -55
  50. package/dist/src/components/carousel/CarouselContent.astro +0 -26
  51. package/dist/src/components/carousel/CarouselItem.astro +0 -26
  52. package/dist/src/components/carousel/CarouselNext.astro +0 -37
  53. package/dist/src/components/carousel/CarouselPrevious.astro +0 -37
  54. package/dist/src/components/carousel/carousel-script.ts +0 -191
  55. package/dist/src/components/carousel/index.ts +0 -32
  56. package/dist/src/components/checkbox/Checkbox.astro +0 -127
  57. package/dist/src/components/checkbox/index.ts +0 -7
  58. package/dist/src/components/dialog/Dialog.astro +0 -263
  59. package/dist/src/components/dialog/DialogClose.astro +0 -35
  60. package/dist/src/components/dialog/DialogContent.astro +0 -67
  61. package/dist/src/components/dialog/DialogDescription.astro +0 -14
  62. package/dist/src/components/dialog/DialogFooter.astro +0 -14
  63. package/dist/src/components/dialog/DialogHeader.astro +0 -14
  64. package/dist/src/components/dialog/DialogTitle.astro +0 -20
  65. package/dist/src/components/dialog/DialogTrigger.astro +0 -47
  66. package/dist/src/components/dialog/index.ts +0 -45
  67. package/dist/src/components/dropdown/Dropdown.astro +0 -375
  68. package/dist/src/components/dropdown/DropdownContent.astro +0 -81
  69. package/dist/src/components/dropdown/DropdownItem.astro +0 -48
  70. package/dist/src/components/dropdown/DropdownLabel.astro +0 -29
  71. package/dist/src/components/dropdown/DropdownSeparator.astro +0 -21
  72. package/dist/src/components/dropdown/DropdownTrigger.astro +0 -52
  73. package/dist/src/components/dropdown/index.ts +0 -33
  74. package/dist/src/components/dropzone/Dropzone.astro +0 -233
  75. package/dist/src/components/dropzone/DropzoneFilesList.astro +0 -26
  76. package/dist/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
  77. package/dist/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
  78. package/dist/src/components/dropzone/index.ts +0 -24
  79. package/dist/src/components/input/Input.astro +0 -24
  80. package/dist/src/components/input/index.ts +0 -7
  81. package/dist/src/components/item/Item.astro +0 -52
  82. package/dist/src/components/item/ItemActions.astro +0 -16
  83. package/dist/src/components/item/ItemContent.astro +0 -16
  84. package/dist/src/components/item/ItemDescription.astro +0 -19
  85. package/dist/src/components/item/ItemFooter.astro +0 -16
  86. package/dist/src/components/item/ItemGroup.astro +0 -16
  87. package/dist/src/components/item/ItemHeader.astro +0 -16
  88. package/dist/src/components/item/ItemMedia.astro +0 -40
  89. package/dist/src/components/item/ItemSeparator.astro +0 -21
  90. package/dist/src/components/item/ItemTitle.astro +0 -16
  91. package/dist/src/components/item/index.ts +0 -50
  92. package/dist/src/components/kbd/Kbd.astro +0 -21
  93. package/dist/src/components/kbd/KbdGroup.astro +0 -16
  94. package/dist/src/components/kbd/index.ts +0 -11
  95. package/dist/src/components/label/Label.astro +0 -22
  96. package/dist/src/components/label/index.ts +0 -7
  97. package/dist/src/components/pagination/Pagination.astro +0 -20
  98. package/dist/src/components/pagination/PaginationContent.astro +0 -16
  99. package/dist/src/components/pagination/PaginationEllipsis.astro +0 -25
  100. package/dist/src/components/pagination/PaginationItem.astro +0 -16
  101. package/dist/src/components/pagination/PaginationLink.astro +0 -24
  102. package/dist/src/components/pagination/PaginationNext.astro +0 -26
  103. package/dist/src/components/pagination/PaginationPrevious.astro +0 -26
  104. package/dist/src/components/pagination/index.ts +0 -38
  105. package/dist/src/components/progress/Progress.astro +0 -154
  106. package/dist/src/components/progress/index.ts +0 -10
  107. package/dist/src/components/radio-group/RadioGroup.astro +0 -157
  108. package/dist/src/components/radio-group/RadioGroupItem.astro +0 -129
  109. package/dist/src/components/radio-group/RadioGroupTypes.ts +0 -6
  110. package/dist/src/components/radio-group/index.ts +0 -23
  111. package/dist/src/components/select/Select.astro +0 -534
  112. package/dist/src/components/select/SelectContent.astro +0 -83
  113. package/dist/src/components/select/SelectGroup.astro +0 -9
  114. package/dist/src/components/select/SelectItem.astro +0 -49
  115. package/dist/src/components/select/SelectLabel.astro +0 -14
  116. package/dist/src/components/select/SelectSeparator.astro +0 -12
  117. package/dist/src/components/select/SelectTrigger.astro +0 -48
  118. package/dist/src/components/select/SelectTypes.ts +0 -13
  119. package/dist/src/components/select/SelectValue.astro +0 -19
  120. package/dist/src/components/select/index.ts +0 -45
  121. package/dist/src/components/separator/Separator.astro +0 -36
  122. package/dist/src/components/separator/index.ts +0 -7
  123. package/dist/src/components/sheet/Sheet.astro +0 -13
  124. package/dist/src/components/sheet/SheetClose.astro +0 -13
  125. package/dist/src/components/sheet/SheetContent.astro +0 -92
  126. package/dist/src/components/sheet/SheetDescription.astro +0 -16
  127. package/dist/src/components/sheet/SheetFooter.astro +0 -16
  128. package/dist/src/components/sheet/SheetHeader.astro +0 -16
  129. package/dist/src/components/sheet/SheetTitle.astro +0 -16
  130. package/dist/src/components/sheet/SheetTrigger.astro +0 -13
  131. package/dist/src/components/sheet/index.ts +0 -41
  132. package/dist/src/components/skeleton/Skeleton.astro +0 -14
  133. package/dist/src/components/skeleton/index.ts +0 -9
  134. package/dist/src/components/spinner/Spinner.astro +0 -21
  135. package/dist/src/components/spinner/index.ts +0 -7
  136. package/dist/src/components/switch/Switch.astro +0 -191
  137. package/dist/src/components/switch/SwitchTypes.ts +0 -6
  138. package/dist/src/components/switch/index.ts +0 -12
  139. package/dist/src/components/table/Table.astro +0 -18
  140. package/dist/src/components/table/TableBody.astro +0 -16
  141. package/dist/src/components/table/TableCaption.astro +0 -16
  142. package/dist/src/components/table/TableCell.astro +0 -16
  143. package/dist/src/components/table/TableFoot.astro +0 -16
  144. package/dist/src/components/table/TableHead.astro +0 -16
  145. package/dist/src/components/table/TableHeader.astro +0 -16
  146. package/dist/src/components/table/TableRow.astro +0 -16
  147. package/dist/src/components/table/index.ts +0 -42
  148. package/dist/src/components/tabs/Tabs.astro +0 -269
  149. package/dist/src/components/tabs/TabsContent.astro +0 -28
  150. package/dist/src/components/tabs/TabsList.astro +0 -22
  151. package/dist/src/components/tabs/TabsTrigger.astro +0 -34
  152. package/dist/src/components/tabs/index.ts +0 -20
  153. package/dist/src/components/textarea/Textarea.astro +0 -28
  154. package/dist/src/components/textarea/index.ts +0 -9
  155. package/dist/src/components/tooltip/Tooltip.astro +0 -237
  156. package/dist/src/components/tooltip/TooltipContent.astro +0 -114
  157. package/dist/src/components/tooltip/TooltipTrigger.astro +0 -10
  158. package/dist/src/components/tooltip/index.ts +0 -16
@@ -1,67 +0,0 @@
1
- ---
2
- import X from "@tabler/icons/outline/x.svg";
3
- import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
5
-
6
- type Props = HTMLAttributes<"dialog"> & {
7
- /**
8
- * Open and close animation duration in milliseconds
9
- */
10
- animationDuration?: number;
11
- };
12
-
13
- export const dialogBackdrop = tv({
14
- base: [
15
- "starwind-dialog-backdrop fixed inset-0 top-0 left-0 z-50 hidden h-screen w-screen bg-black/80",
16
- "data-[state=open]:animate-in fade-in",
17
- "data-[state=closed]:animate-out data-[state=closed]:fill-mode-forwards fade-out",
18
- ],
19
- });
20
-
21
- export const dialogContent = tv({
22
- base: [
23
- "fixed top-16 left-[50%] z-50 translate-x-[-50%] sm:top-[50%] sm:translate-y-[-50%]",
24
- "bg-background w-full max-w-md border p-8 shadow-lg sm:rounded-lg",
25
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fill-mode-forwards",
26
- "fade-in zoom-in-95 slide-in-from-bottom-2",
27
- "fade-out zoom-out-95 slide-out-to-bottom-2",
28
- ],
29
- });
30
-
31
- export const dialogCloseButton = tv({
32
- base: [
33
- "starwind-dialog-close text-muted-foreground",
34
- "absolute top-5.5 right-5.5 rounded-sm [&>svg]:opacity-70 hover:[&>svg]:opacity-100",
35
- "focus-visible:ring-outline/50 transition-[color,box-shadow] outline-none focus-visible:ring-3",
36
- ],
37
- });
38
-
39
- const { class: className, animationDuration = 200, ...rest } = Astro.props;
40
- ---
41
-
42
- <!-- dialog overlay -->
43
- <slot name="backdrop">
44
- <div
45
- class={dialogBackdrop()}
46
- data-state="closed"
47
- data-slot="dialog-backdrop"
48
- style={{ animationDuration: `${animationDuration}ms` }}
49
- >
50
- </div>
51
- </slot>
52
-
53
- <dialog
54
- class={dialogContent({ class: className })}
55
- data-state="closed"
56
- data-slot="dialog-content"
57
- {...rest}
58
- style={{ animationDuration: `${animationDuration}ms` }}
59
- >
60
- <slot />
61
- <button type="button" class={dialogCloseButton()} data-dialog-close aria-label="Close dialog">
62
- <slot name="icon">
63
- <X class="size-5 transition-opacity" />
64
- </slot>
65
- <span class="sr-only">Close</span>
66
- </button>
67
- </dialog>
@@ -1,14 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"p">;
6
-
7
- export const dialogDescription = tv({ base: "text-muted-foreground" });
8
-
9
- const { class: className, ...rest } = Astro.props;
10
- ---
11
-
12
- <p class={dialogDescription({ class: className })} data-slot="dialog-description" {...rest}>
13
- <slot />
14
- </p>
@@ -1,14 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div">;
6
-
7
- export const dialogFooter = tv({ base: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end" });
8
-
9
- const { class: className, ...rest } = Astro.props;
10
- ---
11
-
12
- <div class={dialogFooter({ class: className })} data-slot="dialog-footer" {...rest}>
13
- <slot />
14
- </div>
@@ -1,14 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div">;
6
-
7
- export const dialogHeader = tv({ base: "flex flex-col space-y-2 text-center sm:text-left" });
8
-
9
- const { class: className, ...rest } = Astro.props;
10
- ---
11
-
12
- <div class={dialogHeader({ class: className })} data-slot="dialog-header" {...rest}>
13
- <slot />
14
- </div>
@@ -1,20 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = Omit<HTMLAttributes<"h2">, "id"> & {
6
- /**
7
- * The content to be rendered inside the dialog title
8
- */
9
-
10
- children: any;
11
- };
12
-
13
- export const dialogTitle = tv({ base: "text-xl leading-none font-semibold tracking-tight" });
14
-
15
- const { class: className, ...rest } = Astro.props;
16
- ---
17
-
18
- <h2 class={dialogTitle({ class: className })} data-slot="dialog-title" {...rest}>
19
- <slot />
20
- </h2>
@@ -1,47 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
-
4
- type Props = HTMLAttributes<"button"> & {
5
- /**
6
- * When true, the component will render its child element with a simple wrapper instead of a button component
7
- */
8
- asChild?: boolean;
9
- /**
10
- * Optional ID of the dialog to trigger. If not provided and the trigger is inside a Dialog component,
11
- * it will automatically target that dialog. Required when used outside a Dialog component.
12
- */
13
- for?: string;
14
- };
15
-
16
- const { class: className, asChild = false, for: dialogFor, ...rest } = Astro.props;
17
-
18
- // Get the first child element if asChild is true
19
- let hasChildren = false;
20
- if (Astro.slots.has("default")) {
21
- hasChildren = true;
22
- }
23
- ---
24
-
25
- {
26
- asChild && hasChildren ? (
27
- <div
28
- class:list={["starwind-dialog-trigger", className]}
29
- data-slot="dialog-trigger"
30
- data-as-child
31
- data-dialog-for={dialogFor}
32
- >
33
- <slot />
34
- </div>
35
- ) : (
36
- <button
37
- type="button"
38
- aria-haspopup="dialog"
39
- class:list={["starwind-dialog-trigger", className]}
40
- data-slot="dialog-trigger"
41
- data-dialog-for={dialogFor}
42
- {...rest}
43
- >
44
- <slot />
45
- </button>
46
- )
47
- }
@@ -1,45 +0,0 @@
1
- import Dialog from "./Dialog.astro";
2
- import DialogClose from "./DialogClose.astro";
3
- import DialogContent, {
4
- dialogBackdrop,
5
- dialogCloseButton,
6
- dialogContent,
7
- } from "./DialogContent.astro";
8
- import DialogDescription, { dialogDescription } from "./DialogDescription.astro";
9
- import DialogFooter, { dialogFooter } from "./DialogFooter.astro";
10
- import DialogHeader, { dialogHeader } from "./DialogHeader.astro";
11
- import DialogTitle, { dialogTitle } from "./DialogTitle.astro";
12
- import DialogTrigger from "./DialogTrigger.astro";
13
-
14
- const DialogVariants = {
15
- dialogBackdrop,
16
- dialogContent,
17
- dialogCloseButton,
18
- dialogDescription,
19
- dialogFooter,
20
- dialogHeader,
21
- dialogTitle,
22
- };
23
-
24
- export {
25
- Dialog,
26
- DialogClose,
27
- DialogContent,
28
- DialogDescription,
29
- DialogFooter,
30
- DialogHeader,
31
- DialogTitle,
32
- DialogTrigger,
33
- DialogVariants,
34
- };
35
-
36
- export default {
37
- Root: Dialog,
38
- Trigger: DialogTrigger,
39
- Content: DialogContent,
40
- Header: DialogHeader,
41
- Footer: DialogFooter,
42
- Title: DialogTitle,
43
- Description: DialogDescription,
44
- Close: DialogClose,
45
- };
@@ -1,375 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
-
4
- type Props = HTMLAttributes<"div"> & {
5
- /**
6
- * When true, the dropdown will open on hover in addition to click
7
- */
8
- openOnHover?: boolean;
9
- /**
10
- * Time in milliseconds to wait before closing when hover open is enabled
11
- * @default 200
12
- */
13
- closeDelay?: number;
14
-
15
- children: any;
16
- };
17
-
18
- const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Astro.props;
19
- ---
20
-
21
- <div
22
- class:list={["starwind-dropdown", "relative", className]}
23
- data-open-on-hover={openOnHover ? "true" : undefined}
24
- data-close-delay={closeDelay}
25
- data-slot="dropdown"
26
- {...rest}
27
- >
28
- <slot />
29
- </div>
30
-
31
- <script>
32
- class DropdownHandler {
33
- private dropdown: HTMLElement;
34
- private trigger: HTMLButtonElement | null;
35
- private content: HTMLElement | null;
36
- private items: HTMLElement[] = [];
37
- private currentFocusIndex: number = -1;
38
- private isOpen: boolean = false;
39
- private isClosing: boolean = false;
40
- private animationDuration = 150;
41
- private openOnHover: boolean;
42
- private closeDelay: number;
43
- private closeTimerRef: number | null = null;
44
- private lastOpenSource: "keyboard" | "mouse" = "keyboard";
45
- private lastCloseSource: "keyboard" | "mouse" = "keyboard";
46
-
47
- constructor(dropdown: HTMLElement, dropdownIdx: number) {
48
- this.dropdown = dropdown;
49
- this.openOnHover = dropdown.getAttribute("data-open-on-hover") === "true";
50
- this.closeDelay = parseInt(dropdown.getAttribute("data-close-delay") || "200");
51
-
52
- // Get the temporary trigger element
53
- const tempTrigger = dropdown.querySelector(".starwind-dropdown-trigger") as HTMLElement;
54
-
55
- // if trigger is set with asChild, use the first child element for trigger button
56
- if (tempTrigger?.hasAttribute("data-as-child")) {
57
- this.trigger = tempTrigger.firstElementChild as HTMLButtonElement;
58
- } else {
59
- this.trigger = tempTrigger as HTMLButtonElement;
60
- }
61
-
62
- this.content = dropdown.querySelector(".starwind-dropdown-content");
63
-
64
- if (!this.trigger || !this.content) return;
65
-
66
- // Get animation duration from inline styles if available
67
- const animationDurationString = this.content.style.animationDuration;
68
- if (animationDurationString.endsWith("ms")) {
69
- this.animationDuration = parseFloat(animationDurationString);
70
- } else if (animationDurationString.endsWith("s")) {
71
- this.animationDuration = parseFloat(animationDurationString) * 1000;
72
- }
73
-
74
- this.init(dropdownIdx);
75
- }
76
-
77
- private init(dropdownIdx: number) {
78
- this.setupAccessibility(dropdownIdx);
79
- this.setupEvents();
80
- }
81
-
82
- private setupAccessibility(dropdownIdx: number) {
83
- if (!this.trigger || !this.content) return;
84
-
85
- // Generate unique IDs for accessibility
86
- this.trigger.id = `starwind-dropdown${dropdownIdx}-trigger`;
87
- this.content.id = `starwind-dropdown${dropdownIdx}-content`;
88
-
89
- // Set up additional ARIA attributes
90
- this.trigger.setAttribute("aria-controls", this.content.id);
91
- this.content.setAttribute("aria-labelledby", this.trigger.id);
92
- }
93
-
94
- private setupEvents() {
95
- if (!this.trigger || !this.content) return;
96
-
97
- // Handle trigger click
98
- this.trigger.addEventListener("click", (e) => {
99
- e.preventDefault();
100
- this.lastOpenSource = e.detail === 0 ? "keyboard" : "mouse";
101
- this.toggleDropdown();
102
- });
103
-
104
- // Handle keyboard navigation
105
- this.trigger.addEventListener("keydown", (e) => {
106
- if (e.key === "Enter" || e.key === " ") {
107
- e.preventDefault();
108
- this.lastOpenSource = "keyboard";
109
- this.toggleDropdown();
110
- } else if (e.key === "Escape" && this.isOpen) {
111
- e.preventDefault();
112
- this.lastCloseSource = "keyboard";
113
- this.closeDropdown();
114
- } else if (this.isOpen && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
115
- e.preventDefault();
116
- this.lastOpenSource = "keyboard";
117
- this.updateDropdownItems();
118
- if (e.key === "ArrowDown") {
119
- this.focusItem(0); // Focus first item when opening with arrow down
120
- } else {
121
- this.focusItem(this.items.length - 1); // Focus last item when opening with arrow up
122
- }
123
- }
124
- });
125
-
126
- // Close dropdown when clicking outside for mouse
127
- document.addEventListener("pointerdown", (e) => {
128
- if (this.isOpen && !this.dropdown.contains(e.target as Node)) {
129
- // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
130
- // but not when the control key is pressed (avoiding MacOS right click); also not for touch
131
- // devices because that would open the menu on scroll. (pen devices behave as touch on iOS).
132
- if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
133
- this.closeDropdown();
134
- }
135
- }
136
- });
137
-
138
- // Handle click outside select content to close for mobile
139
- document.addEventListener("click", (e) => {
140
- if (
141
- !(this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)) &&
142
- this.isOpen
143
- ) {
144
- this.closeDropdown();
145
- }
146
- });
147
-
148
- // Handle keyboard navigation and item selection within dropdown
149
- this.content.addEventListener("keydown", (e) => {
150
- if (e.key === "Escape") {
151
- e.preventDefault();
152
- this.closeDropdown();
153
- this.trigger?.focus();
154
- } else if (this.isOpen) {
155
- this.handleMenuKeydown(e);
156
- }
157
- });
158
-
159
- // Handle item selection
160
- this.content.addEventListener("click", (e) => {
161
- const target = e.target as HTMLElement;
162
- const item = target.closest('[role="menuitem"]');
163
- if (item && !(item as HTMLElement).hasAttribute("data-disabled")) {
164
- // Close the dropdown after item selection
165
- this.closeDropdown();
166
- // console.log("click closing");
167
- }
168
- });
169
-
170
- // Handle hover on dropdown items
171
- this.content.addEventListener("mouseover", (e) => {
172
- const target = e.target as HTMLElement;
173
- const menuItem = target.closest('[role="menuitem"]');
174
- if (menuItem && menuItem instanceof HTMLElement && this.isOpen === true) {
175
- // Update items list before focusing to ensure the index is correct
176
- this.updateDropdownItems();
177
-
178
- // Focus the item when hovering
179
- menuItem.focus();
180
-
181
- // Update the current focus index
182
- this.currentFocusIndex = this.items.indexOf(menuItem);
183
- }
184
- });
185
-
186
- if (this.openOnHover) {
187
- this.trigger.addEventListener("pointerenter", (e) => {
188
- if (e.pointerType !== "mouse") return;
189
- if (this.isClosing) return;
190
- if (!this.isOpen) {
191
- this.lastOpenSource = "mouse";
192
- this.openDropdown();
193
- } else {
194
- // If the dropdown is already open, make sure to clear any close timer
195
- this.clearCloseTimer();
196
- }
197
- });
198
-
199
- this.dropdown.addEventListener("pointerleave", (e) => {
200
- if (e.pointerType !== "mouse") return;
201
- if (this.isOpen) {
202
- this.lastCloseSource = "mouse";
203
- this.closeDropdownDelayed();
204
- }
205
- });
206
-
207
- this.content.addEventListener("pointerenter", (e) => {
208
- if (e.pointerType !== "mouse") return;
209
- // If the user moves the mouse to the content, cancel the close timer
210
- this.clearCloseTimer();
211
- });
212
- }
213
- }
214
-
215
- private handleMenuKeydown(e: KeyboardEvent) {
216
- // Make sure we've got an updated list of menu items
217
- this.updateDropdownItems();
218
-
219
- // Skip if no items
220
- if (this.items.length === 0) return;
221
-
222
- const currentIdx = this.currentFocusIndex;
223
-
224
- switch (e.key) {
225
- case "ArrowDown":
226
- e.preventDefault();
227
- this.focusItem(currentIdx === -1 ? 0 : currentIdx + 1);
228
- break;
229
- case "ArrowUp":
230
- e.preventDefault();
231
- this.focusItem(currentIdx === -1 ? this.items.length - 1 : currentIdx - 1);
232
- break;
233
- case "Home":
234
- e.preventDefault();
235
- this.focusItem(0);
236
- break;
237
- case "End":
238
- e.preventDefault();
239
- this.focusItem(this.items.length - 1);
240
- break;
241
- case "Enter":
242
- case " ":
243
- if (currentIdx !== -1) {
244
- e.preventDefault();
245
- this.items[currentIdx].click();
246
- }
247
- break;
248
- }
249
- }
250
-
251
- private updateDropdownItems() {
252
- if (!this.content) return;
253
- // Get all interactive menuitem elements
254
- this.items = Array.from(
255
- this.content.querySelectorAll('[role="menuitem"]:not([data-disabled="true"])'),
256
- ) as HTMLElement[];
257
- }
258
-
259
- private focusItem(idx: number) {
260
- // Ensure the index wraps around properly
261
- const targetIdx = (idx + this.items.length) % this.items.length;
262
-
263
- if (this.items[targetIdx]) {
264
- this.items[targetIdx].focus();
265
- this.currentFocusIndex = targetIdx;
266
- }
267
- }
268
-
269
- private toggleDropdown() {
270
- if (this.isOpen) {
271
- this.closeDropdown();
272
- } else {
273
- this.openDropdown();
274
- }
275
- }
276
-
277
- private openDropdown() {
278
- if (this.isClosing) return;
279
- if (!this.content || !this.trigger || this.trigger.disabled) return;
280
-
281
- this.isOpen = true;
282
- this.content.setAttribute("data-state", "open");
283
- this.trigger.setAttribute("aria-expanded", "true");
284
- this.content.style.removeProperty("display");
285
-
286
- // Update the list of dropdown items
287
- this.updateDropdownItems();
288
-
289
- // Reset focus index when opening
290
- this.currentFocusIndex = -1;
291
-
292
- this.positionContent();
293
- }
294
-
295
- private closeDropdown() {
296
- if (!this.content || !this.trigger) return;
297
-
298
- this.isClosing = true;
299
- this.isOpen = false;
300
- this.content.setAttribute("data-state", "closed");
301
-
302
- // Set focus back on trigger only if opened or closed by keyboard
303
- if (
304
- !this.openOnHover ||
305
- this.lastOpenSource === "keyboard" ||
306
- this.lastCloseSource === "keyboard"
307
- ) {
308
- requestAnimationFrame(() => {
309
- if (!this.trigger) return;
310
- this.trigger.focus();
311
- });
312
- }
313
-
314
- // Give the content time to animate before hiding
315
- setTimeout(() => {
316
- if (!this.content) return;
317
- this.content.style.display = "none";
318
- this.isClosing = false;
319
- }, this.animationDuration);
320
-
321
- this.trigger.setAttribute("aria-expanded", "false");
322
-
323
- // Reset focus index when closing
324
- this.currentFocusIndex = -1;
325
- }
326
-
327
- private closeDropdownDelayed() {
328
- if (!this.content || !this.trigger) return;
329
-
330
- // Clear any existing close timer
331
- this.clearCloseTimer();
332
-
333
- // Set a new timer to close the dropdown after the delay
334
- this.closeTimerRef = window.setTimeout(() => {
335
- if (this.isOpen) {
336
- this.closeDropdown();
337
- }
338
- this.closeTimerRef = null;
339
- }, this.closeDelay);
340
- }
341
-
342
- private clearCloseTimer() {
343
- if (this.closeTimerRef !== null) {
344
- window.clearTimeout(this.closeTimerRef);
345
- this.closeTimerRef = null;
346
- }
347
- }
348
-
349
- private positionContent() {
350
- if (!this.content || !this.trigger) return;
351
-
352
- // Set content width to match trigger width
353
- this.content.style.width = "var(--starwind-dropdown-trigger-width)";
354
- this.content.style.setProperty(
355
- "--starwind-dropdown-trigger-width",
356
- `${this.trigger.offsetWidth}px`,
357
- );
358
- }
359
- }
360
-
361
- // Store instances in a WeakMap to avoid memory leaks
362
- const dropdownInstances = new WeakMap<HTMLElement, DropdownHandler>();
363
-
364
- // Initialize dropdowns
365
- const initDropdowns = () => {
366
- document.querySelectorAll(".starwind-dropdown").forEach((dropdown, idx) => {
367
- if (dropdown instanceof HTMLElement && !dropdownInstances.has(dropdown)) {
368
- dropdownInstances.set(dropdown, new DropdownHandler(dropdown, idx));
369
- }
370
- });
371
- };
372
-
373
- initDropdowns();
374
- document.addEventListener("astro:after-swap", initDropdowns);
375
- </script>
@@ -1,81 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div"> & {
6
- /**
7
- * Side of the dropdown
8
- * @default bottom
9
- */
10
- side?: "top" | "bottom";
11
- /**
12
- * Alignment of the dropdown
13
- * @default start
14
- */
15
- align?: "start" | "center" | "end";
16
- /**
17
- * Offset distance in pixels
18
- * @default 4
19
- */
20
- sideOffset?: number;
21
- /**
22
- * Open and close animation duration in milliseconds
23
- * @default 150
24
- */
25
- animationDuration?: number;
26
- };
27
-
28
- export const dropdownContent = tv({
29
- base: [
30
- "starwind-dropdown-content",
31
- "bg-popover text-popover-foreground z-50 min-w-[9rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
32
- "data-[state=open]:animate-in fade-in zoom-in-95",
33
- "data-[state=closed]:animate-out data-[state=closed]:fill-mode-forwards fade-out zoom-out-95",
34
- "absolute will-change-transform",
35
- ],
36
- variants: {
37
- side: {
38
- bottom: "slide-in-from-top-2 slide-out-to-top-2 top-full",
39
- top: "slide-in-from-bottom-2 slide-out-to-bottom-2 bottom-full",
40
- },
41
- align: {
42
- start: "slide-in-from-left-1 slide-out-to-left-1 left-0",
43
- center: "left-1/2 -translate-x-1/2",
44
- end: "slide-in-from-right-1 slide-out-to-right-1 right-0",
45
- },
46
- },
47
- defaultVariants: {
48
- side: "bottom",
49
- align: "start",
50
- },
51
- });
52
-
53
- const {
54
- class: className,
55
- side = "bottom",
56
- align = "start",
57
- sideOffset = 4,
58
- animationDuration = 150,
59
- ...rest
60
- } = Astro.props;
61
- ---
62
-
63
- <div
64
- class={dropdownContent({ side, align, class: className })}
65
- role="menu"
66
- data-side={side}
67
- data-align={align}
68
- data-state="closed"
69
- data-slot="dropdown-content"
70
- tabindex="-1"
71
- aria-orientation="vertical"
72
- style={{
73
- display: "none",
74
- animationDuration: `${animationDuration}ms`,
75
- marginTop: side === "bottom" ? `${sideOffset}px` : undefined,
76
- marginBottom: side === "top" ? `${sideOffset}px` : undefined,
77
- }}
78
- {...rest}
79
- >
80
- <slot />
81
- </div>