@orbitkit/components 0.0.1 → 0.2.0-beta.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 (132) hide show
  1. package/dist/astro/accordion/Accordion.astro +34 -0
  2. package/dist/astro/accordion/AccordionItem.astro +16 -0
  3. package/dist/astro/accordion/AccordionTrigger.astro +33 -0
  4. package/dist/astro/accordion/AcordionContent.astro +26 -0
  5. package/dist/astro/accordion/accordion.ts +125 -0
  6. package/dist/astro/accordion/index.ts +6 -0
  7. package/dist/astro/alert/Alert.astro +30 -30
  8. package/dist/astro/alert/AlertDescription.astro +10 -10
  9. package/dist/astro/alert/AlertTitle.astro +15 -15
  10. package/dist/astro/alert/alertVariants.ts +51 -51
  11. package/dist/astro/alert/index.ts +6 -6
  12. package/dist/astro/avatar/Avatar.astro +16 -16
  13. package/dist/astro/avatar/AvatarFallback.astro +18 -18
  14. package/dist/astro/avatar/AvatarImage.astro +14 -14
  15. package/dist/astro/avatar/avatarVariants.ts +23 -23
  16. package/dist/astro/avatar/index.ts +6 -6
  17. package/dist/astro/badge/Badge.astro +22 -22
  18. package/dist/astro/badge/badgeVariants.ts +37 -37
  19. package/dist/astro/badge/index.ts +4 -4
  20. package/dist/astro/breadcrumb/Breadcrumb.astro +12 -12
  21. package/dist/astro/breadcrumb/BreadcrumbEllipsis.astro +20 -20
  22. package/dist/astro/breadcrumb/BreadcrumbItem.astro +15 -15
  23. package/dist/astro/breadcrumb/BreadcrumbLink.astro +18 -18
  24. package/dist/astro/breadcrumb/BreadcrumbList.astro +18 -18
  25. package/dist/astro/breadcrumb/BreadcrumbPage.astro +18 -18
  26. package/dist/astro/breadcrumb/BreadcrumbSeparator.astro +17 -17
  27. package/dist/astro/breadcrumb/index.ts +17 -17
  28. package/dist/astro/button/Button.astro +29 -29
  29. package/dist/astro/button/buttonVariants.ts +61 -61
  30. package/dist/astro/button/index.ts +4 -4
  31. package/dist/astro/card/Card.astro +18 -18
  32. package/dist/astro/card/CardContent.astro +12 -12
  33. package/dist/astro/card/CardDescription.astro +12 -12
  34. package/dist/astro/card/CardFooter.astro +15 -15
  35. package/dist/astro/card/CardHeader.astro +12 -12
  36. package/dist/astro/card/CardTitle.astro +18 -18
  37. package/dist/astro/card/index.ts +15 -15
  38. package/dist/astro/checkbox/Checkbox.astro +38 -38
  39. package/dist/astro/checkbox/index.ts +3 -3
  40. package/dist/astro/collapsible/Collapsible.astro +34 -0
  41. package/dist/astro/collapsible/CollapsibleContent.astro +20 -0
  42. package/dist/astro/collapsible/collapsible.ts +81 -0
  43. package/dist/astro/collapsible/index.ts +4 -0
  44. package/dist/astro/divider/Divider.astro +22 -0
  45. package/dist/astro/divider/index.ts +3 -0
  46. package/dist/astro/drawer/Drawer.astro +19 -0
  47. package/dist/astro/drawer/DrawerContent.astro +74 -0
  48. package/dist/astro/drawer/DrawerDescription.astro +12 -0
  49. package/dist/astro/drawer/DrawerFooter.astro +15 -0
  50. package/dist/astro/drawer/DrawerHeader.astro +12 -0
  51. package/dist/astro/drawer/DrawerTitle.astro +18 -0
  52. package/dist/astro/drawer/drawer.ts +104 -0
  53. package/dist/astro/drawer/drawerVariants.ts +83 -0
  54. package/dist/astro/drawer/index.ts +15 -0
  55. package/dist/astro/dropdown/DropdownMenu.astro +19 -0
  56. package/dist/astro/dropdown/DropdownMenuContent.astro +42 -0
  57. package/dist/astro/dropdown/DropdownMenuGroup.astro +3 -0
  58. package/dist/astro/dropdown/DropdownMenuItem.astro +27 -0
  59. package/dist/astro/dropdown/DropdownMenuLabel.astro +3 -0
  60. package/dist/astro/dropdown/DropdownMenuSeparator.astro +6 -0
  61. package/dist/astro/dropdown/dropdown.ts +157 -0
  62. package/dist/astro/dropdown/dropdownVariants.ts +134 -0
  63. package/dist/astro/dropdown/index.ts +15 -0
  64. package/dist/astro/input/Input.astro +18 -18
  65. package/dist/astro/input/index.ts +4 -4
  66. package/dist/astro/input/inputVariants.ts +30 -30
  67. package/dist/astro/kbd/Kbd.astro +18 -0
  68. package/dist/astro/kbd/index.ts +3 -0
  69. package/dist/astro/label/Label.astro +14 -14
  70. package/dist/astro/label/index.ts +3 -3
  71. package/dist/astro/list/List.astro +25 -0
  72. package/dist/astro/list/ListItem.astro +39 -0
  73. package/dist/astro/list/ListVariants.ts +65 -0
  74. package/dist/astro/list/index.ts +5 -0
  75. package/dist/astro/modal/Modal.astro +19 -0
  76. package/dist/astro/modal/ModalContent.astro +71 -0
  77. package/dist/astro/modal/ModalDescription.astro +12 -0
  78. package/dist/astro/modal/ModalFooter.astro +15 -0
  79. package/dist/astro/modal/ModalHeader.astro +12 -0
  80. package/dist/astro/modal/ModalTitle.astro +18 -0
  81. package/dist/astro/modal/index.ts +15 -0
  82. package/dist/astro/modal/modal.ts +101 -0
  83. package/dist/astro/pagination/Pagination.astro +12 -0
  84. package/dist/astro/pagination/PaginationContent.astro +15 -0
  85. package/dist/astro/pagination/PaginationEllipsis.astro +33 -0
  86. package/dist/astro/pagination/PaginationItem.astro +12 -0
  87. package/dist/astro/pagination/PaginationLink.astro +21 -0
  88. package/dist/astro/pagination/PaginationNext.astro +29 -0
  89. package/dist/astro/pagination/PaginationPrevious.astro +34 -0
  90. package/dist/astro/pagination/index.ts +13 -0
  91. package/dist/astro/pagination/paginationVariants.ts +26 -0
  92. package/dist/astro/popover/Popover.astro +17 -0
  93. package/dist/astro/popover/PopoverContent.astro +39 -0
  94. package/dist/astro/popover/index.ts +4 -0
  95. package/dist/astro/popover/popover.ts +113 -0
  96. package/dist/astro/popover/popoverVariants.ts +115 -0
  97. package/dist/astro/progress/Progress.astro +23 -23
  98. package/dist/astro/progress/index.ts +4 -4
  99. package/dist/astro/progress/progressContainer.astro +17 -17
  100. package/dist/astro/radio/Radio.astro +26 -26
  101. package/dist/astro/radio/index.ts +3 -3
  102. package/dist/astro/select/Option.astro +11 -11
  103. package/dist/astro/select/Select.astro +39 -39
  104. package/dist/astro/select/index.ts +5 -5
  105. package/dist/astro/select/selectVariants.ts +30 -30
  106. package/dist/astro/skeleton/Skeleton.astro +12 -0
  107. package/dist/astro/skeleton/SkeletonItem.astro +18 -0
  108. package/dist/astro/skeleton/index.ts +4 -0
  109. package/dist/astro/stat/Stat.astro +12 -0
  110. package/dist/astro/stat/StatDescription.astro +12 -0
  111. package/dist/astro/stat/StatTitle.astro +18 -0
  112. package/dist/astro/stat/StatValue.astro +12 -0
  113. package/dist/astro/stat/index.ts +6 -0
  114. package/dist/astro/switch/Switch.astro +19 -19
  115. package/dist/astro/switch/index.ts +3 -3
  116. package/dist/astro/tab/Tab.astro +33 -0
  117. package/dist/astro/tab/TabContent.astro +19 -0
  118. package/dist/astro/tab/TabList.astro +19 -0
  119. package/dist/astro/tab/TabTrigger.astro +24 -0
  120. package/dist/astro/tab/index.ts +6 -0
  121. package/dist/astro/tab/tab.ts +142 -0
  122. package/dist/astro/textarea/Textarea.astro +19 -19
  123. package/dist/astro/textarea/TextareaVariants.ts +30 -30
  124. package/dist/astro/textarea/index.ts +4 -4
  125. package/dist/astro/tooltip/Tooltip.astro +40 -0
  126. package/dist/astro/tooltip/TooltipContent.astro +39 -0
  127. package/dist/astro/tooltip/index.ts +6 -0
  128. package/dist/astro/tooltip/tooltip.ts +137 -0
  129. package/dist/astro/tooltip/tooltipVariants.ts +115 -0
  130. package/dist/index.js +57 -1
  131. package/dist/index.js.map +1 -1
  132. package/package.json +9 -2
@@ -1,19 +1,19 @@
1
- ---
2
- import { cn } from "@/utils/cn";
3
- import { type HTMLAttributes } from "astro/types";
4
-
5
- interface Props extends Omit<HTMLAttributes<"input">, "type"> {}
6
-
7
- const { class: className, id, ...attrs } = Astro.props;
8
- ---
9
-
10
- <label class="inline-flex cursor-pointer items-center" id={id}>
11
- <input {...attrs} id={id} type="checkbox" class="peer sr-only" />
12
- <span
13
- class={cn(
14
- "relative w-11 h-6 bg-input-border rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full after:content-[''] after:absolute after:top-0.5 after:start-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
15
- className,
16
- )}
17
- >
18
- </span>
19
- </label>
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import { type HTMLAttributes } from "astro/types";
4
+
5
+ interface Props extends Omit<HTMLAttributes<"input">, "type"> {}
6
+
7
+ const { class: className, id, ...attrs } = Astro.props;
8
+ ---
9
+
10
+ <label class="inline-flex cursor-pointer items-center" id={id}>
11
+ <input {...attrs} id={id} type="checkbox" class="peer sr-only" />
12
+ <span
13
+ class={cn(
14
+ "relative w-11 h-6 bg-input-border rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full after:content-[''] after:absolute after:top-0.5 after:start-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
15
+ className,
16
+ )}
17
+ >
18
+ </span>
19
+ </label>
@@ -1,3 +1,3 @@
1
- import Switch from "./Switch.astro";
2
-
3
- export { Switch };
1
+ import Switch from "./Switch.astro";
2
+
3
+ export { Switch };
@@ -0,0 +1,33 @@
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import type { HTMLAttributes } from "astro/types";
4
+
5
+ interface Props extends HTMLAttributes<"div"> {
6
+ defaultValue?: string;
7
+ }
8
+
9
+ const { class: className, defaultValue, ...attrs } = Astro.props;
10
+ ---
11
+
12
+ <div
13
+ {...attrs}
14
+ data-tab
15
+ data-default-value={defaultValue}
16
+ class={cn(className)}
17
+ >
18
+ <slot />
19
+ </div>
20
+
21
+ <script>
22
+ import { Tab } from "./tab";
23
+
24
+ function init() {
25
+ const tabs = document.querySelectorAll<HTMLElement>("[data-tab]");
26
+ tabs.forEach((tab) => {
27
+ new Tab(tab);
28
+ });
29
+ }
30
+
31
+ init();
32
+ document.addEventListener("astro:page-load", () => init());
33
+ </script>
@@ -0,0 +1,19 @@
1
+ ---
2
+ interface Props {
3
+ value: string;
4
+ }
5
+
6
+ const { value } = Astro.props;
7
+ ---
8
+
9
+ <div
10
+ role="tabpanel"
11
+ aria-orientation="horizontal"
12
+ data-tab-content
13
+ data-state="inactive"
14
+ data-value={value}
15
+ hidden
16
+ class="mt-2 w-full"
17
+ >
18
+ <slot />
19
+ </div>
@@ -0,0 +1,19 @@
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import type { HTMLAttributes } from "astro/types";
4
+
5
+ interface Props extends HTMLAttributes<"div"> {}
6
+
7
+ const { class: className } = Astro.props;
8
+ ---
9
+
10
+ <div
11
+ role="tablist"
12
+ data-tab-list
13
+ class={cn(
14
+ "flex flex-row justify-between flex-wrap bg-surface border border-border p-1 rounded-lg w-fit",
15
+ className,
16
+ )}
17
+ >
18
+ <slot />
19
+ </div>
@@ -0,0 +1,24 @@
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import type { HTMLAttributes } from "astro/types";
4
+
5
+ interface Props extends HTMLAttributes<"button"> {
6
+ value: string;
7
+ }
8
+
9
+ const { class: className, value } = Astro.props;
10
+ ---
11
+
12
+ <button
13
+ role="tab"
14
+ aria-orientation="horizontal"
15
+ data-selected="false"
16
+ data-value={value}
17
+ data-tab-trigger
18
+ class={cn(
19
+ "rounded-sm px-3 py-1.5 flex justify-center gap-1.5 items-center font-medium cursor-pointer outline-none [&_svg]:pointer-events-none [&_svg]:shrink-0 transition-colors duration-100 ease-in data-[selected=true]:bg-secondary",
20
+ className,
21
+ )}
22
+ >
23
+ <slot />
24
+ </button>
@@ -0,0 +1,6 @@
1
+ import Tab from "./Tab.astro";
2
+ import TabContent from "./TabContent.astro";
3
+ import TabList from "./TabList.astro";
4
+ import TabTrigger from "./TabTrigger.astro";
5
+
6
+ export { Tab, TabContent, TabList, TabTrigger };
@@ -0,0 +1,142 @@
1
+ export class Tab {
2
+ private tab: HTMLElement;
3
+ private tabList: HTMLElement | null;
4
+ private tabsTrigger: NodeListOf<HTMLElement> | null;
5
+ private tabContents: NodeListOf<HTMLElement> | null;
6
+ private contentMap: Map<string, HTMLElement> = new Map();
7
+
8
+ constructor(tabWrapper: HTMLElement) {
9
+ this.tab = tabWrapper;
10
+ this.tabList = this.tab.querySelector("[data-tab-list]");
11
+ this.tabsTrigger =
12
+ this.tabList?.querySelectorAll("[data-tab-trigger]") || null;
13
+ this.tabContents = this.tab.querySelectorAll("[data-tab-content]");
14
+
15
+ this.initializeContentMap();
16
+ this.init();
17
+ }
18
+
19
+ private init() {
20
+ this.setupAccessibility();
21
+ this.setupEventListeners();
22
+ this.setDefaultTab();
23
+ }
24
+
25
+ private initializeContentMap(): void {
26
+ this.tabContents?.forEach((content) => {
27
+ const value = content.dataset.value?.toLowerCase();
28
+ if (value) {
29
+ if (this.contentMap.has(value)) {
30
+ console.warn(
31
+ `Duplicate data-value "${value}" found in content elements`,
32
+ );
33
+ }
34
+ this.contentMap.set(value, content);
35
+ }
36
+ });
37
+ }
38
+
39
+ private setupAccessibility() {
40
+ this.tabsTrigger?.forEach((trigger) => {
41
+ const triggerValue = trigger.dataset.value?.toLowerCase();
42
+ if (!triggerValue) return;
43
+
44
+ const content = this.contentMap.get(triggerValue);
45
+ if (!content) {
46
+ console.warn(`No content found for trigger value: ${triggerValue}`);
47
+ return;
48
+ }
49
+
50
+ const uniqueId = Math.random().toString(36).substring(2, 9);
51
+ const triggerId = trigger.id || `tab-trigger-${triggerValue}-${uniqueId}`;
52
+ const contentId = content.id || `tab-content-${triggerValue}-${uniqueId}`;
53
+
54
+ trigger.setAttribute("id", triggerId);
55
+ trigger.setAttribute("aria-controls", contentId);
56
+
57
+ content.setAttribute("id", contentId);
58
+ content.setAttribute("aria-labelledby", triggerId);
59
+ });
60
+ }
61
+
62
+ private setupEventListeners() {
63
+ this.tabsTrigger?.forEach((trigger) => {
64
+ trigger.addEventListener("click", () => this.activateTab(trigger));
65
+ trigger.addEventListener("keydown", (e) =>
66
+ this.handleKeydown(e, trigger),
67
+ );
68
+ });
69
+ }
70
+
71
+ private handleKeydown(event: KeyboardEvent, item: HTMLElement) {
72
+ const items = Array.from(this.tabsTrigger || []);
73
+ const currentItemIndex = items.indexOf(item);
74
+
75
+ const keyActions: Record<string, () => void> = {
76
+ ArrowRight: () => this.setFocusItem(items, currentItemIndex + 1),
77
+ ArrowLeft: () => this.setFocusItem(items, currentItemIndex - 1),
78
+ Home: () => this.setFocusItem(items, 0),
79
+ End: () => this.setFocusItem(items, items.length - 1),
80
+ };
81
+
82
+ const action = keyActions[event.key];
83
+ if (action) {
84
+ event.preventDefault();
85
+ action();
86
+ }
87
+ }
88
+
89
+ private setFocusItem(items: HTMLElement[], index: number) {
90
+ const newIndex = index % items.length;
91
+ if (items[newIndex]) {
92
+ this.activateTab(items[newIndex]);
93
+ }
94
+ }
95
+
96
+ private setDefaultTab() {
97
+ const defaultTrigger = this.getDefaultTrigger();
98
+ if (defaultTrigger) {
99
+ this.activateTab(defaultTrigger);
100
+ }
101
+ }
102
+
103
+ private getDefaultTrigger(): HTMLElement | undefined {
104
+ if (!this.tabsTrigger) return;
105
+
106
+ const defaultValue = this.tab.dataset.defaultValue?.toLowerCase();
107
+ if (defaultValue) {
108
+ return Array.from(this.tabsTrigger || []).find(
109
+ (trigger) => trigger.dataset.value?.toLowerCase() === defaultValue,
110
+ );
111
+ }
112
+ return this.tabsTrigger[0];
113
+ }
114
+
115
+ private activateTab(trigger: HTMLElement) {
116
+ this.tabsTrigger?.forEach((t) => {
117
+ t.setAttribute("aria-selected", "false");
118
+ t.setAttribute("data-selected", "false");
119
+ t.setAttribute("tabindex", "-1");
120
+ });
121
+
122
+ this.tabContents?.forEach((c) => {
123
+ c.setAttribute("data-state", "active");
124
+ c.setAttribute("hidden", "");
125
+ });
126
+
127
+ const triggerValue = trigger.dataset.value?.toLowerCase();
128
+ if (triggerValue) {
129
+ const content = this.contentMap.get(triggerValue);
130
+ if (content) {
131
+ content.removeAttribute("hidden");
132
+ setTimeout(() => {
133
+ content.setAttribute("data-state", "active");
134
+ trigger.setAttribute("aria-selected", "true");
135
+ trigger.setAttribute("data-selected", "true");
136
+ trigger.setAttribute("tabindex", "0");
137
+ }, 100);
138
+ trigger.focus();
139
+ }
140
+ }
141
+ }
142
+ }
@@ -1,19 +1,19 @@
1
- ---
2
- import { cn } from "@/utils/cn";
3
- import { type HTMLAttributes } from "astro/types";
4
- import type { VariantProps } from "class-variance-authority";
5
- import { textareaVariants } from "./TextareaVariants";
6
-
7
- interface Props
8
- extends Omit<HTMLAttributes<"textarea">, "disabled">,
9
- VariantProps<typeof textareaVariants> {}
10
-
11
- const { class: className, variant, disabled, ...attrs } = Astro.props;
12
- ---
13
-
14
- <textarea
15
- {...attrs}
16
- disabled={disabled}
17
- class={cn(textareaVariants({ variant, disabled, className }))}
18
- >
19
- </textarea>
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import { type HTMLAttributes } from "astro/types";
4
+ import type { VariantProps } from "class-variance-authority";
5
+ import { textareaVariants } from "./TextareaVariants";
6
+
7
+ interface Props
8
+ extends Omit<HTMLAttributes<"textarea">, "disabled">,
9
+ VariantProps<typeof textareaVariants> {}
10
+
11
+ const { class: className, variant, disabled, ...attrs } = Astro.props;
12
+ ---
13
+
14
+ <textarea
15
+ {...attrs}
16
+ disabled={disabled}
17
+ class={cn(textareaVariants({ variant, disabled, className }))}
18
+ >
19
+ </textarea>
@@ -1,30 +1,30 @@
1
- import { cva } from "class-variance-authority";
2
-
3
- const baseClass =
4
- "block w-full p-2.5 mb-2 text-sm rounded-lg outline-1 -outline-offset-1";
5
-
6
- const textareaVariants = cva(baseClass, {
7
- variants: {
8
- variant: {
9
- default:
10
- "text-foreground bg-input outline-input-border placeholder-input-placeholder",
11
- },
12
- disabled: {
13
- false: null,
14
- true: "bg-input/40 outline-input-border/40 text-muted-foreground cursor-not-allowed ",
15
- },
16
- },
17
- compoundVariants: [
18
- {
19
- variant: "default",
20
- disabled: false,
21
- class: "focus:outline-2 focus:-outline-offset-2 focus:outline-primary",
22
- },
23
- ],
24
- defaultVariants: {
25
- variant: "default",
26
- disabled: false,
27
- },
28
- });
29
-
30
- export { textareaVariants };
1
+ import { cva } from "class-variance-authority";
2
+
3
+ const baseClass =
4
+ "block w-full p-2.5 mb-2 text-sm rounded-lg outline-1 -outline-offset-1";
5
+
6
+ const textareaVariants = cva(baseClass, {
7
+ variants: {
8
+ variant: {
9
+ default:
10
+ "text-foreground bg-input outline-input-border placeholder-input-placeholder",
11
+ },
12
+ disabled: {
13
+ false: null,
14
+ true: "bg-input/40 outline-input-border/40 text-muted-foreground cursor-not-allowed ",
15
+ },
16
+ },
17
+ compoundVariants: [
18
+ {
19
+ variant: "default",
20
+ disabled: false,
21
+ class: "focus:outline-2 focus:-outline-offset-2 focus:outline-primary",
22
+ },
23
+ ],
24
+ defaultVariants: {
25
+ variant: "default",
26
+ disabled: false,
27
+ },
28
+ });
29
+
30
+ export { textareaVariants };
@@ -1,4 +1,4 @@
1
- import Textarea from "./Textarea.astro";
2
- import { textareaVariants } from "./TextareaVariants";
3
-
4
- export { Textarea, textareaVariants };
1
+ import Textarea from "./Textarea.astro";
2
+ import { textareaVariants } from "./TextareaVariants";
3
+
4
+ export { Textarea, textareaVariants };
@@ -0,0 +1,40 @@
1
+ ---
2
+ interface Props {
3
+ disableHoverableContent?: boolean;
4
+ openDelay?: number;
5
+ closeDelay?: number;
6
+ duration?: number;
7
+ }
8
+
9
+ const {
10
+ openDelay = 150,
11
+ closeDelay = 150,
12
+ duration = 200,
13
+ disableHoverableContent = false,
14
+ } = Astro.props;
15
+ ---
16
+
17
+ <div
18
+ class="relative inline-flex"
19
+ data-tooltip
20
+ data-open-delay={openDelay}
21
+ data-close-delay={closeDelay}
22
+ data-duration={duration}
23
+ data-disable-hoverable-content={disableHoverableContent}
24
+ >
25
+ <slot />
26
+ </div>
27
+
28
+ <script>
29
+ import { Tooltip } from "./tooltip";
30
+
31
+ function init() {
32
+ const tooltips = document.querySelectorAll<HTMLElement>("[data-tooltip]");
33
+ tooltips.forEach((tooltip) => {
34
+ new Tooltip(tooltip);
35
+ });
36
+ }
37
+
38
+ init();
39
+ document.addEventListener("astro:page-load", () => init());
40
+ </script>
@@ -0,0 +1,39 @@
1
+ ---
2
+ import { cn } from "@/utils/cn";
3
+ import type { HTMLAttributes } from "astro/types";
4
+ import type { VariantProps } from "class-variance-authority";
5
+ import { tooltipArrowVariants, tooltipVariants } from "./tooltipVariants";
6
+
7
+ interface Props
8
+ extends HTMLAttributes<"div">,
9
+ VariantProps<typeof tooltipVariants> {
10
+ sideOffset?: number;
11
+ arrow?: boolean;
12
+ }
13
+
14
+ const {
15
+ class: className,
16
+ side,
17
+ alignment,
18
+ sideOffset = 2,
19
+ arrow = true,
20
+ ...attrs
21
+ } = Astro.props;
22
+ ---
23
+
24
+ <div
25
+ {...attrs}
26
+ role="tooltip"
27
+ data-tooltip-content
28
+ aria-hidden="false"
29
+ data-state="closed"
30
+ data-side={sideOffset}
31
+ data-alignment={alignment}
32
+ class={cn(tooltipVariants({ side, alignment, className }))}
33
+ style={{
34
+ "--tooltip-offset": `calc(var(--spacing) * ${sideOffset})`,
35
+ }}
36
+ >
37
+ <slot />
38
+ {arrow && <span class={cn(tooltipArrowVariants({ side, alignment }))} />}
39
+ </div>
@@ -0,0 +1,6 @@
1
+ import Tooltip from "./Tooltip.astro";
2
+ import TooltipContent from "./TooltipContent.astro";
3
+ import TooltipTrigger from "./TooltipTrigger.astro";
4
+ import { tooltipVariants } from "./tooltipVariants";
5
+
6
+ export { Tooltip, TooltipContent, TooltipTrigger, tooltipVariants };
@@ -0,0 +1,137 @@
1
+ export class Tooltip {
2
+ // References to tooltip elements
3
+ private tooltip: HTMLElement;
4
+ private trigger: HTMLElement | null;
5
+ private content: HTMLElement | null;
6
+
7
+ // Tooltip configuration options
8
+ private duration: number;
9
+ private openDelay: number;
10
+ private closeDelay: number;
11
+
12
+ // Timers for managing tooltip open/close/hide delays
13
+ private openTimerId: number | null = null;
14
+ private closeTimerId: number | null = null;
15
+ private hideTimerId: number | null = null;
16
+
17
+ constructor(tooltip: HTMLElement) {
18
+ this.tooltip = tooltip;
19
+ this.content = this.tooltip.querySelector("[data-tooltip-content]");
20
+ this.trigger = this.tooltip.querySelector("[data-trigger]");
21
+
22
+ this.duration = parseFloat(tooltip.dataset.duration || "200");
23
+ this.openDelay = parseFloat(tooltip.dataset.openDelay || "0");
24
+ this.closeDelay = parseFloat(tooltip.dataset.closeDelay || "0");
25
+
26
+ if (!this.tooltip || !this.content || !this.trigger) {
27
+ console.error("Tooltip not initialized properly", {
28
+ container: this.tooltip,
29
+ trigger: this.trigger,
30
+ content: this.content,
31
+ });
32
+ return;
33
+ }
34
+
35
+ // If you want to use animations instead of transitions
36
+ // set animation duration instead of transition duration.
37
+ this.content.style.transitionDuration = `${this.duration}ms`;
38
+
39
+ this.init();
40
+ }
41
+
42
+ private init() {
43
+ this.setupAccessibility();
44
+ this.setupEventListeners();
45
+ }
46
+
47
+ private setupAccessibility() {
48
+ if (!this.trigger || !this.content) return;
49
+
50
+ const id =
51
+ this.content.id ||
52
+ `tooltip-id-${Math.random().toString(36).substring(2, 9)}`;
53
+ this.content.id = id;
54
+ this.trigger.setAttribute("aria-describedby", id);
55
+ this.setState("closed");
56
+ }
57
+
58
+ private setupEventListeners() {
59
+ if (!this.trigger || !this.content) return;
60
+
61
+ this.trigger!.addEventListener("mouseenter", () => this.showTooltip());
62
+ this.trigger!.addEventListener("mouseleave", () => this.hideTooltip());
63
+ this.trigger.addEventListener("focus", () => this.showTooltip(true));
64
+ this.trigger.addEventListener("blur", () => this.hideTooltip(true));
65
+
66
+ if (this.tooltip.dataset.disableHoverableContent === "false") {
67
+ this.content.addEventListener("mouseenter", () => this.showTooltip());
68
+ this.content.addEventListener("mouseleave", () => this.hideTooltip());
69
+ }
70
+
71
+ document.addEventListener("keydown", (e) => this.handleKeyDown(e));
72
+ }
73
+
74
+ private showTooltip(instant: boolean = false) {
75
+ this.clearCloseTimer();
76
+ this.content?.classList.remove("hidden");
77
+
78
+ if (instant) {
79
+ this.setState("open");
80
+ return;
81
+ }
82
+
83
+ this.openTimerId = window.setTimeout(() => {
84
+ this.setState("open");
85
+ this.openTimerId = null;
86
+ }, this.openDelay);
87
+ }
88
+
89
+ private hideTooltip(instant: boolean = false) {
90
+ this.clearOpenTimer();
91
+
92
+ if (instant) {
93
+ this.setState("closed");
94
+ this.content?.classList.add("hidden");
95
+ return;
96
+ }
97
+
98
+ this.closeTimerId = window.setTimeout(() => {
99
+ this.setState("closed");
100
+ this.hideTimerId = window.setTimeout(() => {
101
+ this.content?.classList.add("hidden");
102
+ this.closeTimerId = null;
103
+ this.hideTimerId = null;
104
+ }, this.duration);
105
+ }, this.closeDelay);
106
+ }
107
+
108
+ private setState(state: "open" | "closed") {
109
+ this.content?.setAttribute("aria-hidden", `${state === "closed"}`);
110
+ this.content?.setAttribute("data-state", state);
111
+ }
112
+
113
+ private clearOpenTimer() {
114
+ if (this.openTimerId) {
115
+ window.clearTimeout(this.openTimerId);
116
+ this.openTimerId = null;
117
+ }
118
+ }
119
+
120
+ private clearCloseTimer() {
121
+ if (this.closeTimerId) {
122
+ window.clearTimeout(this.closeTimerId);
123
+ this.closeTimerId = null;
124
+ }
125
+ if (this.hideTimerId) {
126
+ window.clearTimeout(this.hideTimerId);
127
+ this.hideTimerId = null;
128
+ }
129
+ }
130
+
131
+ private handleKeyDown = (event: KeyboardEvent) => {
132
+ if (event.key === "Escape" && this.content!.dataset.status === "open") {
133
+ this.hideTooltip(true);
134
+ event.preventDefault();
135
+ }
136
+ };
137
+ }