@rimelight/ui 0.0.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 (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/i18n-data.json +186 -0
  4. package/package.json +49 -0
  5. package/src/components/FormattedDate.astro +22 -0
  6. package/src/components/HeaderLink.astro +24 -0
  7. package/src/components/HttpObservatory.astro +103 -0
  8. package/src/components/LighthouseScores.astro +204 -0
  9. package/src/components/MissingTranslationBanner.astro +39 -0
  10. package/src/components/TableOfContents.astro +106 -0
  11. package/src/components/ThemeToggle.astro +71 -0
  12. package/src/components/accordion/Accordion.astro +257 -0
  13. package/src/components/accordion/AccordionContent.astro +34 -0
  14. package/src/components/accordion/AccordionItem.astro +29 -0
  15. package/src/components/accordion/AccordionTrigger.astro +33 -0
  16. package/src/components/alert/Alert.astro +34 -0
  17. package/src/components/alert/AlertDescription.astro +15 -0
  18. package/src/components/alert/AlertTitle.astro +17 -0
  19. package/src/components/alert-dialog/AlertDialog.astro +275 -0
  20. package/src/components/alert-dialog/AlertDialogAction.astro +46 -0
  21. package/src/components/alert-dialog/AlertDialogCancel.astro +46 -0
  22. package/src/components/alert-dialog/AlertDialogContent.astro +52 -0
  23. package/src/components/alert-dialog/AlertDialogDescription.astro +18 -0
  24. package/src/components/alert-dialog/AlertDialogFooter.astro +16 -0
  25. package/src/components/alert-dialog/AlertDialogHeader.astro +14 -0
  26. package/src/components/alert-dialog/AlertDialogTitle.astro +20 -0
  27. package/src/components/alert-dialog/AlertDialogTrigger.astro +47 -0
  28. package/src/components/aspect-ratio/AspectRatio.astro +32 -0
  29. package/src/components/avatar/Avatar.astro +29 -0
  30. package/src/components/avatar/AvatarFallback.astro +18 -0
  31. package/src/components/avatar/AvatarImage.astro +66 -0
  32. package/src/components/badge/Badge.astro +57 -0
  33. package/src/components/breadcrumb/Breadcrumb.astro +12 -0
  34. package/src/components/breadcrumb/BreadcrumbEllipsis.astro +29 -0
  35. package/src/components/breadcrumb/BreadcrumbItem.astro +15 -0
  36. package/src/components/breadcrumb/BreadcrumbLink.astro +24 -0
  37. package/src/components/breadcrumb/BreadcrumbList.astro +17 -0
  38. package/src/components/breadcrumb/BreadcrumbPage.astro +22 -0
  39. package/src/components/breadcrumb/BreadcrumbSeparator.astro +24 -0
  40. package/src/components/button/Button.astro +58 -0
  41. package/src/components/button-group/ButtonGroup.astro +64 -0
  42. package/src/components/button-group/ButtonGroupSeparator.astro +20 -0
  43. package/src/components/button-group/ButtonGroupText.astro +19 -0
  44. package/src/components/card/Card.astro +36 -0
  45. package/src/components/card/CardAction.astro +16 -0
  46. package/src/components/card/CardContent.astro +16 -0
  47. package/src/components/card/CardDescription.astro +16 -0
  48. package/src/components/card/CardFooter.astro +16 -0
  49. package/src/components/card/CardHeader.astro +19 -0
  50. package/src/components/card/CardTitle.astro +16 -0
  51. package/src/components/carousel/Carousel.astro +55 -0
  52. package/src/components/carousel/CarouselContent.astro +26 -0
  53. package/src/components/carousel/CarouselItem.astro +26 -0
  54. package/src/components/carousel/CarouselNext.astro +39 -0
  55. package/src/components/carousel/CarouselPrevious.astro +41 -0
  56. package/src/components/carousel/carousel-script.ts +188 -0
  57. package/src/components/carousel/index.ts +1 -0
  58. package/src/components/checkbox/Checkbox.astro +133 -0
  59. package/src/components/collapsible/Collapsible.astro +161 -0
  60. package/src/components/collapsible/CollapsibleContent.astro +22 -0
  61. package/src/components/collapsible/CollapsibleTrigger.astro +44 -0
  62. package/src/components/dialog/Dialog.astro +389 -0
  63. package/src/components/dialog/DialogClose.astro +35 -0
  64. package/src/components/dialog/DialogContent.astro +78 -0
  65. package/src/components/dialog/DialogDescription.astro +14 -0
  66. package/src/components/dialog/DialogFooter.astro +14 -0
  67. package/src/components/dialog/DialogHeader.astro +14 -0
  68. package/src/components/dialog/DialogTitle.astro +18 -0
  69. package/src/components/dialog/DialogTrigger.astro +47 -0
  70. package/src/components/dropdown/Dropdown.astro +604 -0
  71. package/src/components/dropdown/DropdownContent.astro +118 -0
  72. package/src/components/dropdown/DropdownItem.astro +48 -0
  73. package/src/components/dropdown/DropdownLabel.astro +29 -0
  74. package/src/components/dropdown/DropdownSeparator.astro +21 -0
  75. package/src/components/dropdown/DropdownShortcut.astro +17 -0
  76. package/src/components/dropdown/DropdownSub.astro +15 -0
  77. package/src/components/dropdown/DropdownSubContent.astro +39 -0
  78. package/src/components/dropdown/DropdownSubTrigger.astro +34 -0
  79. package/src/components/dropdown/DropdownTrigger.astro +52 -0
  80. package/src/components/dropzone/Dropzone.astro +236 -0
  81. package/src/components/dropzone/DropzoneFilesList.astro +26 -0
  82. package/src/components/dropzone/DropzoneLoadingIndicator.astro +10 -0
  83. package/src/components/dropzone/DropzoneUploadIndicator.astro +10 -0
  84. package/src/components/embeds/YoutubeEmbed.astro +94 -0
  85. package/src/components/hero/Hero.astro +166 -0
  86. package/src/components/icons/Flag.astro +62 -0
  87. package/src/components/image/ExtendedImage.astro +46 -0
  88. package/src/components/image/Image.astro +25 -0
  89. package/src/components/input/Input.astro +27 -0
  90. package/src/components/input-group/InputGroup.astro +28 -0
  91. package/src/components/input-group/InputGroupAddon.astro +76 -0
  92. package/src/components/input-group/InputGroupButton.astro +36 -0
  93. package/src/components/input-group/InputGroupInput.astro +18 -0
  94. package/src/components/input-group/InputGroupText.astro +17 -0
  95. package/src/components/input-group/InputGroupTextarea.astro +22 -0
  96. package/src/components/input-otp/InputOtp.astro +352 -0
  97. package/src/components/input-otp/InputOtpGroup.astro +16 -0
  98. package/src/components/input-otp/InputOtpSeparator.astro +25 -0
  99. package/src/components/input-otp/InputOtpSlot.astro +50 -0
  100. package/src/components/input-otp/InputOtpTypes.ts +6 -0
  101. package/src/components/item/Item.astro +52 -0
  102. package/src/components/item/ItemActions.astro +16 -0
  103. package/src/components/item/ItemContent.astro +16 -0
  104. package/src/components/item/ItemDescription.astro +19 -0
  105. package/src/components/item/ItemFooter.astro +16 -0
  106. package/src/components/item/ItemGroup.astro +16 -0
  107. package/src/components/item/ItemHeader.astro +16 -0
  108. package/src/components/item/ItemMedia.astro +40 -0
  109. package/src/components/item/ItemSeparator.astro +23 -0
  110. package/src/components/item/ItemTitle.astro +16 -0
  111. package/src/components/kbd/Kbd.astro +21 -0
  112. package/src/components/kbd/KbdGroup.astro +16 -0
  113. package/src/components/label/Label.astro +24 -0
  114. package/src/components/marquee/Marquee.astro +73 -0
  115. package/src/components/native-select/NativeSelect.astro +68 -0
  116. package/src/components/native-select/NativeSelectOptGroup.astro +15 -0
  117. package/src/components/native-select/NativeSelectOption.astro +15 -0
  118. package/src/components/pagination/Pagination.astro +20 -0
  119. package/src/components/pagination/PaginationContent.astro +16 -0
  120. package/src/components/pagination/PaginationEllipsis.astro +37 -0
  121. package/src/components/pagination/PaginationItem.astro +16 -0
  122. package/src/components/pagination/PaginationLink.astro +27 -0
  123. package/src/components/pagination/PaginationNext.astro +33 -0
  124. package/src/components/pagination/PaginationPrevious.astro +33 -0
  125. package/src/components/popover/Popover.astro +717 -0
  126. package/src/components/popover/PopoverContent.astro +102 -0
  127. package/src/components/popover/PopoverDescription.astro +14 -0
  128. package/src/components/popover/PopoverHeader.astro +14 -0
  129. package/src/components/popover/PopoverTitle.astro +14 -0
  130. package/src/components/popover/PopoverTrigger.astro +51 -0
  131. package/src/components/progress/Progress.astro +154 -0
  132. package/src/components/prose/Prose.astro +619 -0
  133. package/src/components/radio-group/RadioGroup.astro +162 -0
  134. package/src/components/radio-group/RadioGroupItem.astro +133 -0
  135. package/src/components/radio-group/RadioGroupTypes.ts +6 -0
  136. package/src/components/section/Section.astro +167 -0
  137. package/src/components/select/Select.astro +752 -0
  138. package/src/components/select/SelectContent.astro +94 -0
  139. package/src/components/select/SelectGroup.astro +9 -0
  140. package/src/components/select/SelectItem.astro +51 -0
  141. package/src/components/select/SelectLabel.astro +14 -0
  142. package/src/components/select/SelectSearch.astro +49 -0
  143. package/src/components/select/SelectSeparator.astro +12 -0
  144. package/src/components/select/SelectTrigger.astro +56 -0
  145. package/src/components/select/SelectTypes.ts +13 -0
  146. package/src/components/select/SelectValue.astro +19 -0
  147. package/src/components/separator/Separator.astro +36 -0
  148. package/src/components/sheet/Sheet.astro +15 -0
  149. package/src/components/sheet/SheetClose.astro +15 -0
  150. package/src/components/sheet/SheetContent.astro +89 -0
  151. package/src/components/sheet/SheetDescription.astro +16 -0
  152. package/src/components/sheet/SheetFooter.astro +16 -0
  153. package/src/components/sheet/SheetHeader.astro +16 -0
  154. package/src/components/sheet/SheetTitle.astro +16 -0
  155. package/src/components/sheet/SheetTrigger.astro +15 -0
  156. package/src/components/sidebar/Sidebar.astro +231 -0
  157. package/src/components/sidebar/SidebarContent.astro +25 -0
  158. package/src/components/sidebar/SidebarFooter.astro +22 -0
  159. package/src/components/sidebar/SidebarGroup.astro +22 -0
  160. package/src/components/sidebar/SidebarGroupContent.astro +22 -0
  161. package/src/components/sidebar/SidebarGroupLabel.astro +53 -0
  162. package/src/components/sidebar/SidebarHeader.astro +22 -0
  163. package/src/components/sidebar/SidebarInput.astro +24 -0
  164. package/src/components/sidebar/SidebarInset.astro +22 -0
  165. package/src/components/sidebar/SidebarMenu.astro +22 -0
  166. package/src/components/sidebar/SidebarMenuAction.astro +63 -0
  167. package/src/components/sidebar/SidebarMenuBadge.astro +31 -0
  168. package/src/components/sidebar/SidebarMenuButton.astro +124 -0
  169. package/src/components/sidebar/SidebarMenuItem.astro +22 -0
  170. package/src/components/sidebar/SidebarMenuSkeleton.astro +43 -0
  171. package/src/components/sidebar/SidebarMenuSub.astro +25 -0
  172. package/src/components/sidebar/SidebarMenuSubButton.astro +52 -0
  173. package/src/components/sidebar/SidebarMenuSubItem.astro +17 -0
  174. package/src/components/sidebar/SidebarProvider.astro +215 -0
  175. package/src/components/sidebar/SidebarRail.astro +72 -0
  176. package/src/components/sidebar/SidebarSeparator.astro +25 -0
  177. package/src/components/sidebar/SidebarTrigger.astro +69 -0
  178. package/src/components/skeleton/Skeleton.astro +14 -0
  179. package/src/components/slider/Slider.astro +425 -0
  180. package/src/components/spinner/Spinner.astro +23 -0
  181. package/src/components/switch/Switch.astro +192 -0
  182. package/src/components/switch/SwitchTypes.ts +6 -0
  183. package/src/components/table/Table.astro +20 -0
  184. package/src/components/table/TableBody.astro +18 -0
  185. package/src/components/table/TableCaption.astro +18 -0
  186. package/src/components/table/TableCell.astro +18 -0
  187. package/src/components/table/TableFoot.astro +18 -0
  188. package/src/components/table/TableHead.astro +18 -0
  189. package/src/components/table/TableHeader.astro +18 -0
  190. package/src/components/table/TableRow.astro +17 -0
  191. package/src/components/tabs/Tabs.astro +270 -0
  192. package/src/components/tabs/TabsContent.astro +28 -0
  193. package/src/components/tabs/TabsList.astro +23 -0
  194. package/src/components/tabs/TabsTrigger.astro +34 -0
  195. package/src/components/textarea/Textarea.astro +31 -0
  196. package/src/components/toast/ToastDescription.astro +22 -0
  197. package/src/components/toast/ToastItem.astro +56 -0
  198. package/src/components/toast/ToastTemplate.astro +27 -0
  199. package/src/components/toast/ToastTitle.astro +59 -0
  200. package/src/components/toast/Toaster.astro +981 -0
  201. package/src/components/toast/index.ts +1 -0
  202. package/src/components/toast/toast-manager.ts +234 -0
  203. package/src/components/toggle/Toggle.astro +174 -0
  204. package/src/components/toggle/ToggleTypes.ts +14 -0
  205. package/src/components/tooltip/Tooltip.astro +422 -0
  206. package/src/components/tooltip/TooltipContent.astro +83 -0
  207. package/src/components/tooltip/TooltipTrigger.astro +13 -0
  208. package/src/components/video/Video.astro +121 -0
  209. package/src/config/cookies.ts +5 -0
  210. package/src/config/index.ts +3 -0
  211. package/src/config/links.ts +4 -0
  212. package/src/config/site.config.ts +45 -0
  213. package/src/domain/cms/index.ts +1 -0
  214. package/src/domain/cms/types.ts +20 -0
  215. package/src/domain/i18n/constants.ts +21 -0
  216. package/src/domain/i18n/cookie-storage.ts +13 -0
  217. package/src/domain/i18n/country-to-locale-map.ts +67 -0
  218. package/src/domain/i18n/fallbacks.generated.ts +230 -0
  219. package/src/domain/i18n/fetcher.ts +268 -0
  220. package/src/domain/i18n/flags.ts +16 -0
  221. package/src/domain/i18n/format.ts +90 -0
  222. package/src/domain/i18n/index.ts +10 -0
  223. package/src/domain/i18n/middleware/i18n.ts +160 -0
  224. package/src/domain/i18n/resolve-locale.ts +55 -0
  225. package/src/domain/i18n/runtime-constants.ts +14 -0
  226. package/src/domain/i18n/schema.ts +29 -0
  227. package/src/domain/index.ts +4 -0
  228. package/src/domain/seo/constants.ts +20 -0
  229. package/src/domain/seo/index.ts +3 -0
  230. package/src/domain/seo/services/index.ts +3 -0
  231. package/src/domain/seo/services/llms.ts +53 -0
  232. package/src/domain/seo/services/robots.ts +12 -0
  233. package/src/domain/seo/services/sitemap.ts +56 -0
  234. package/src/domain/seo/utils.ts +37 -0
  235. package/src/domain/theme/constants.ts +6 -0
  236. package/src/domain/theme/index.ts +2 -0
  237. package/src/domain/theme/schema.ts +10 -0
  238. package/src/env.d.ts +49 -0
  239. package/src/i18n.generated.d.ts +202 -0
  240. package/src/lib/component-preview.ts +73 -0
  241. package/src/lib/index.ts +6 -0
  242. package/src/lib/remark/remark-modified-time.ts +13 -0
  243. package/src/lib/showcase-preview-elements.ts +408 -0
  244. package/src/lib/showcase-renderer.ts +218 -0
  245. package/src/lib/showcase-utils.ts +189 -0
  246. package/src/lib/turnstile.ts +35 -0
  247. package/src/lib/utils/starwind/positioning.ts +318 -0
  248. package/src/locales/de/blog.json +9 -0
  249. package/src/locales/de/common.json +19 -0
  250. package/src/locales/de/errors.json +6 -0
  251. package/src/locales/de/home.json +20 -0
  252. package/src/locales/de/messages.json +8 -0
  253. package/src/locales/de/nav.json +13 -0
  254. package/src/locales/de/projects.json +9 -0
  255. package/src/locales/de/services.json +43 -0
  256. package/src/locales/en/blog.json +9 -0
  257. package/src/locales/en/chat.json +29 -0
  258. package/src/locales/en/common.json +19 -0
  259. package/src/locales/en/cookie_notice.json +11 -0
  260. package/src/locales/en/editor.json +24 -0
  261. package/src/locales/en/errors.json +6 -0
  262. package/src/locales/en/home.json +20 -0
  263. package/src/locales/en/messages.json +8 -0
  264. package/src/locales/en/nav.json +13 -0
  265. package/src/locales/en/projects.json +9 -0
  266. package/src/locales/en/services.json +43 -0
  267. package/src/locales/es/blog.json +9 -0
  268. package/src/locales/es/common.json +19 -0
  269. package/src/locales/es/errors.json +6 -0
  270. package/src/locales/es/home.json +20 -0
  271. package/src/locales/es/messages.json +8 -0
  272. package/src/locales/es/nav.json +13 -0
  273. package/src/locales/es/projects.json +9 -0
  274. package/src/locales/es/services.json +43 -0
  275. package/src/locales/ja/blog.json +9 -0
  276. package/src/locales/ja/common.json +19 -0
  277. package/src/locales/ja/errors.json +6 -0
  278. package/src/locales/ja/home.json +20 -0
  279. package/src/locales/ja/messages.json +8 -0
  280. package/src/locales/ja/nav.json +13 -0
  281. package/src/locales/ja/projects.json +9 -0
  282. package/src/locales/ja/services.json +43 -0
  283. package/src/locales/pt/blog.json +9 -0
  284. package/src/locales/pt/chat.json +29 -0
  285. package/src/locales/pt/common.json +19 -0
  286. package/src/locales/pt/cookie_notice.json +11 -0
  287. package/src/locales/pt/editor.json +24 -0
  288. package/src/locales/pt/errors.json +6 -0
  289. package/src/locales/pt/home.json +20 -0
  290. package/src/locales/pt/messages.json +8 -0
  291. package/src/locales/pt/nav.json +13 -0
  292. package/src/locales/pt/projects.json +9 -0
  293. package/src/locales/pt/services.json +43 -0
  294. package/src/utils/index.ts +2 -0
  295. package/src/utils/server/cookies.ts +98 -0
  296. package/src/utils/server/index.ts +1 -0
  297. package/src/utils/shared/deep-merge.ts +36 -0
  298. package/src/utils/shared/embed.ts +105 -0
  299. package/src/utils/shared/index.ts +3 -0
  300. package/src/utils/shared/parse-boolean.ts +14 -0
  301. package/worker-configuration.d.ts +14791 -0
@@ -0,0 +1,422 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cv } from "css-variants";
4
+
5
+ export interface TooltipProps extends HTMLAttributes<"div"> {
6
+ /**
7
+ * Time in milliseconds to wait before showing the tooltip
8
+ */
9
+ openDelay?: number;
10
+ /**
11
+ * Time in milliseconds to wait before hiding the tooltip
12
+ */
13
+ closeDelay?: number;
14
+ /**
15
+ * When true, prevents the tooltip from staying open when hovering over its content
16
+ */
17
+ disableHoverableContent?: boolean;
18
+ }
19
+
20
+ type Props = TooltipProps;
21
+
22
+ export const tooltip = cv({ base: "starwind-tooltip relative i-block" });
23
+
24
+ const {
25
+ openDelay = 200,
26
+ closeDelay = 200,
27
+ disableHoverableContent = false,
28
+ class: className,
29
+ } = Astro.props;
30
+ ---
31
+
32
+ <div
33
+ class:list={[tooltip(), className]}
34
+ data-state="closed"
35
+ data-open-delay={openDelay}
36
+ data-close-delay={closeDelay}
37
+ {...!disableHoverableContent && { "data-content-hoverable": "" }}
38
+ data-slot="tooltip"
39
+ >
40
+ <slot />
41
+ </div>
42
+
43
+ <script>
44
+ import {
45
+ type FloatingAlign,
46
+ type FloatingSide,
47
+ getTransformOrigin,
48
+ resolvePlacement,
49
+ } from "../../lib/utils/starwind/positioning";
50
+
51
+ class TooltipHandler {
52
+ private tooltip: HTMLElement;
53
+ private trigger: HTMLElement | null;
54
+ private content: HTMLElement | null;
55
+ private openTimerRef: number | null = null;
56
+ private closeTimerRef: number | null = null;
57
+ private hideAnimationTimerRef: number | null = null;
58
+ private contentId: string;
59
+ private animationDuration = 150;
60
+ private side: FloatingSide = "top";
61
+ private align: FloatingAlign = "center";
62
+ private sideOffset = 8;
63
+ private avoidCollisions = true;
64
+ private isOpen = false;
65
+ private contentPlaceholder: Comment | null = null;
66
+ private cleanupAutoUpdate: (() => void) | null = null;
67
+ private abortController: AbortController;
68
+
69
+ constructor(tooltip: HTMLElement, idx: number) {
70
+ this.contentId = `starwind-tooltip${idx}`;
71
+ this.tooltip = tooltip;
72
+ this.abortController = new AbortController();
73
+ this.content = tooltip.querySelector(".starwind-tooltip-content");
74
+
75
+ // if tooltip.firstElementChild is this.content, then get the next element
76
+ this.trigger = tooltip.firstElementChild as HTMLElement;
77
+ if (this.trigger === this.content) {
78
+ this.trigger = this.trigger.nextElementSibling as HTMLElement;
79
+ }
80
+
81
+ if (!this.trigger || !this.content) return;
82
+
83
+ this.trigger.classList.add("starwind-tooltip-trigger");
84
+
85
+ // animationDuration is set with inline styles through passed prop to TooltipContent
86
+ const animationDurationString = this.content.style.animationDuration;
87
+ if (animationDurationString.endsWith("ms")) {
88
+ this.animationDuration = parseFloat(animationDurationString);
89
+ } else if (animationDurationString.endsWith("s")) {
90
+ // using something like @playform/compress might optimize to use "s" instead of "ms"
91
+ this.animationDuration = parseFloat(animationDurationString) * 1000;
92
+ }
93
+
94
+ const contentSide = this.content.getAttribute("data-side");
95
+ if (
96
+ contentSide === "top" ||
97
+ contentSide === "bottom" ||
98
+ contentSide === "left" ||
99
+ contentSide === "right"
100
+ ) {
101
+ this.side = contentSide;
102
+ }
103
+
104
+ const contentAlign = this.content.getAttribute("data-align");
105
+ if (contentAlign === "start" || contentAlign === "center" || contentAlign === "end") {
106
+ this.align = contentAlign;
107
+ }
108
+
109
+ const contentSideOffset = parseFloat(this.content.getAttribute("data-side-offset") || "8");
110
+ if (Number.isFinite(contentSideOffset)) {
111
+ this.sideOffset = contentSideOffset;
112
+ }
113
+
114
+ this.avoidCollisions = this.content.hasAttribute("data-avoid-collisions");
115
+ this.isOpen = this.tooltip.getAttribute("data-state") === "open";
116
+
117
+ this.init();
118
+ }
119
+
120
+ private init() {
121
+ this.setupAccessibility();
122
+ this.setupEvents();
123
+ this.setInitialState();
124
+ }
125
+
126
+ private setupAccessibility() {
127
+ if (!this.trigger || !this.content) return;
128
+ this.trigger.setAttribute("aria-describedby", this.contentId);
129
+ this.content.id = this.contentId;
130
+ }
131
+
132
+ private setInitialState() {
133
+ if (!this.content || !this.trigger) return;
134
+
135
+ if (this.isOpen) {
136
+ this.tooltip.setAttribute("data-state", "open");
137
+ this.trigger.setAttribute("data-state", "open");
138
+ this.content.setAttribute("data-state", "open");
139
+ this.content.classList.remove("hidden");
140
+ this.portalContent();
141
+ this.positionTooltip();
142
+ requestAnimationFrame(() => this.positionTooltip());
143
+ } else {
144
+ this.tooltip.setAttribute("data-state", "closed");
145
+ this.trigger.setAttribute("data-state", "closed");
146
+ this.content.setAttribute("data-state", "closed");
147
+ this.content.classList.add("hidden");
148
+ }
149
+ }
150
+
151
+ private setupEvents() {
152
+ if (!this.trigger || !this.content) return;
153
+
154
+ // Trigger events
155
+ this.trigger.addEventListener("mouseenter", () => this.show(), {
156
+ signal: this.abortController.signal,
157
+ });
158
+ this.trigger.addEventListener("mouseleave", () => this.hide(), {
159
+ signal: this.abortController.signal,
160
+ });
161
+ this.trigger.addEventListener("focus", () => this.show(true), {
162
+ signal: this.abortController.signal,
163
+ });
164
+ this.trigger.addEventListener("blur", () => this.hide(true), {
165
+ signal: this.abortController.signal,
166
+ });
167
+
168
+ // Content events
169
+ if (this.tooltip.hasAttribute("data-content-hoverable")) {
170
+ this.content.addEventListener("mouseenter", () => this.show(), {
171
+ signal: this.abortController.signal,
172
+ });
173
+ this.content.addEventListener("mouseleave", () => this.hide(), {
174
+ signal: this.abortController.signal,
175
+ });
176
+ }
177
+
178
+ // Document events
179
+ document.addEventListener(
180
+ "keydown",
181
+ (e) => {
182
+ if (e.key === "Escape" && this.isOpen) {
183
+ this.hide(true);
184
+ }
185
+ },
186
+ { signal: this.abortController.signal },
187
+ );
188
+
189
+ document.addEventListener(
190
+ "pointerdown",
191
+ (e) => {
192
+ if (
193
+ this.isOpen &&
194
+ !this.tooltip.contains(e.target as Node) &&
195
+ !this.content?.contains(e.target as Node)
196
+ ) {
197
+ this.hide(true);
198
+ }
199
+ },
200
+ { signal: this.abortController.signal },
201
+ );
202
+ }
203
+
204
+ private portalContent() {
205
+ if (!this.content) return;
206
+ if (this.contentPlaceholder || this.content.parentElement === document.body) return;
207
+
208
+ this.contentPlaceholder = document.createComment("tooltip-content-placeholder");
209
+ this.content.parentNode?.insertBefore(this.contentPlaceholder, this.content);
210
+
211
+ document.body.appendChild(this.content);
212
+
213
+ this.content.style.position = "fixed";
214
+ this.content.style.zIndex = "50";
215
+
216
+ const updatePosition = () => this.positionTooltip();
217
+ window.addEventListener("scroll", updatePosition, true);
218
+ window.addEventListener("resize", updatePosition);
219
+
220
+ const resizeObserver = new ResizeObserver(() => this.positionTooltip());
221
+ resizeObserver.observe(this.content);
222
+ if (this.trigger) {
223
+ resizeObserver.observe(this.trigger);
224
+ }
225
+
226
+ this.cleanupAutoUpdate = () => {
227
+ window.removeEventListener("scroll", updatePosition, true);
228
+ window.removeEventListener("resize", updatePosition);
229
+ resizeObserver.disconnect();
230
+ };
231
+ }
232
+
233
+ private unportalContent() {
234
+ if (!this.content) return;
235
+
236
+ this.cleanupAutoUpdate?.();
237
+ this.cleanupAutoUpdate = null;
238
+
239
+ if (this.contentPlaceholder) {
240
+ this.contentPlaceholder.parentNode?.insertBefore(this.content, this.contentPlaceholder);
241
+ this.contentPlaceholder.remove();
242
+ this.contentPlaceholder = null;
243
+ }
244
+
245
+ this.content.style.removeProperty("position");
246
+ this.content.style.removeProperty("z-index");
247
+ this.content.style.removeProperty("top");
248
+ this.content.style.removeProperty("left");
249
+ this.content.style.removeProperty("transform-origin");
250
+ }
251
+
252
+ private openNow() {
253
+ if (!this.content || !this.trigger) return;
254
+ if (this.abortController.signal.aborted) return;
255
+
256
+ this.clearHideAnimationTimer();
257
+ this.isOpen = true;
258
+ this.tooltip.setAttribute("data-state", "open");
259
+ this.trigger.setAttribute("data-state", "open");
260
+ this.content.setAttribute("data-state", "open");
261
+ this.content.classList.remove("hidden");
262
+ this.portalContent();
263
+ this.positionTooltip();
264
+ requestAnimationFrame(() => this.positionTooltip());
265
+ }
266
+
267
+ private show(immediate: boolean = false) {
268
+ if (!this.content || !this.trigger) return;
269
+
270
+ this.clearCloseTimer();
271
+ this.clearHideAnimationTimer();
272
+ this.clearOpenTimer();
273
+
274
+ if (immediate) {
275
+ this.openNow();
276
+ this.clearOpenTimer();
277
+ return;
278
+ }
279
+
280
+ const delay = parseInt(this.tooltip.getAttribute("data-open-delay") || "200");
281
+ this.openTimerRef = window.setTimeout(() => {
282
+ if (this.abortController.signal.aborted) return;
283
+ this.openNow();
284
+ this.openTimerRef = null;
285
+ }, delay);
286
+ }
287
+
288
+ private positionTooltip() {
289
+ if (!this.content || !this.trigger || !this.isOpen) return;
290
+
291
+ const triggerRect = this.trigger.getBoundingClientRect();
292
+ const contentWidth = this.content.offsetWidth;
293
+ const contentHeight = this.content.offsetHeight;
294
+ if (contentWidth === 0 || contentHeight === 0) return;
295
+
296
+ const resolvedPlacement = resolvePlacement({
297
+ side: this.side,
298
+ align: this.align,
299
+ sideOffset: this.sideOffset,
300
+ triggerRect,
301
+ contentWidth,
302
+ contentHeight,
303
+ viewportWidth: window.innerWidth,
304
+ viewportHeight: window.innerHeight,
305
+ viewportPadding: 8,
306
+ avoidCollisions: this.avoidCollisions,
307
+ });
308
+
309
+ this.content.style.left = `${Math.round(resolvedPlacement.left)}px`;
310
+ this.content.style.top = `${Math.round(resolvedPlacement.top)}px`;
311
+ this.content.style.transformOrigin = getTransformOrigin(
312
+ resolvedPlacement.side,
313
+ resolvedPlacement.align,
314
+ );
315
+ this.content.setAttribute("data-side", resolvedPlacement.side);
316
+ this.content.setAttribute("data-align", resolvedPlacement.align);
317
+ }
318
+
319
+ private closeNow() {
320
+ if (!this.content || !this.trigger) return;
321
+ this.isOpen = false;
322
+ this.tooltip.setAttribute("data-state", "closed");
323
+ this.trigger.setAttribute("data-state", "closed");
324
+ this.content.setAttribute("data-state", "closed");
325
+
326
+ this.clearHideAnimationTimer();
327
+ this.hideAnimationTimerRef = window.setTimeout(() => {
328
+ if (this.abortController.signal.aborted) return;
329
+ if (!this.content) return;
330
+ this.content.classList.add("hidden");
331
+ this.unportalContent();
332
+ }, this.animationDuration);
333
+ }
334
+
335
+ private hide(immediate: boolean = false) {
336
+ if (!this.content || !this.trigger) return;
337
+ this.clearOpenTimer();
338
+ this.clearCloseTimer();
339
+
340
+ if (immediate) {
341
+ this.closeNow();
342
+ return;
343
+ }
344
+
345
+ this.closeTimerRef = window.setTimeout(
346
+ () => {
347
+ if (this.abortController.signal.aborted) return;
348
+ this.closeNow();
349
+ this.closeTimerRef = null;
350
+ },
351
+ parseInt(this.tooltip.getAttribute("data-close-delay") || "200"),
352
+ );
353
+ }
354
+
355
+ destroy() {
356
+ if (this.abortController.signal.aborted) return;
357
+
358
+ this.abortController.abort();
359
+ this.clearOpenTimer();
360
+ this.clearCloseTimer();
361
+ this.clearHideAnimationTimer();
362
+ this.unportalContent();
363
+ this.isOpen = false;
364
+ }
365
+
366
+ private clearOpenTimer() {
367
+ if (this.openTimerRef) {
368
+ window.clearTimeout(this.openTimerRef);
369
+ this.openTimerRef = null;
370
+ }
371
+ }
372
+
373
+ private clearCloseTimer() {
374
+ if (this.closeTimerRef) {
375
+ window.clearTimeout(this.closeTimerRef);
376
+ this.closeTimerRef = null;
377
+ }
378
+ }
379
+
380
+ private clearHideAnimationTimer() {
381
+ if (this.hideAnimationTimerRef) {
382
+ window.clearTimeout(this.hideAnimationTimerRef);
383
+ this.hideAnimationTimerRef = null;
384
+ }
385
+ }
386
+ }
387
+
388
+ const tooltipInstances = new Map<HTMLElement, TooltipHandler>();
389
+ let tooltipCounter = 0;
390
+
391
+ const cleanupStaleTooltips = () => {
392
+ for (const [tooltipEl, tooltipHandler] of tooltipInstances.entries()) {
393
+ if (!document.contains(tooltipEl)) {
394
+ tooltipHandler.destroy();
395
+ tooltipInstances.delete(tooltipEl);
396
+ }
397
+ }
398
+ };
399
+
400
+ const destroyAllTooltips = () => {
401
+ for (const tooltipHandler of tooltipInstances.values()) {
402
+ tooltipHandler.destroy();
403
+ }
404
+
405
+ tooltipInstances.clear();
406
+ };
407
+
408
+ const setupTooltips = () => {
409
+ cleanupStaleTooltips();
410
+
411
+ document.querySelectorAll<HTMLElement>(".starwind-tooltip").forEach((tooltip) => {
412
+ if (!tooltipInstances.has(tooltip)) {
413
+ tooltipInstances.set(tooltip, new TooltipHandler(tooltip, tooltipCounter++));
414
+ }
415
+ });
416
+ };
417
+
418
+ setupTooltips();
419
+ document.addEventListener("astro:before-swap", destroyAllTooltips);
420
+ document.addEventListener("astro:after-swap", setupTooltips);
421
+ document.addEventListener("starwind:init", setupTooltips);
422
+ </script>
@@ -0,0 +1,83 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cv } from "css-variants";
4
+ import IconCaretUp from "~icons/lucide/caret-up";
5
+
6
+ export interface TooltipContentProps extends HTMLAttributes<"div"> {
7
+ /**
8
+ * Side of the tooltip
9
+ * @default top
10
+ */
11
+ side?: "top" | "right" | "bottom" | "left";
12
+ /**
13
+ * Alignment of the tooltip
14
+ * @default center
15
+ */
16
+ align?: "start" | "center" | "end";
17
+ /**
18
+ * Offset distance in pixels
19
+ * @default 8
20
+ */
21
+ sideOffset?: number;
22
+ /**
23
+ * Prevent the tooltip from colliding with other elements
24
+ * @default true
25
+ */
26
+ avoidCollisions?: boolean;
27
+ /**
28
+ * Open and close animation duration in milliseconds
29
+ * @default 150
30
+ */
31
+ animationDuration?: number;
32
+ }
33
+
34
+ type Props = TooltipContentProps;
35
+
36
+ export const tooltipContent = cv({
37
+ base: [
38
+ "starwind-tooltip-content",
39
+ "group fixed z-50 hidden w-fit px-xs py-5xs",
40
+ "bg-foreground text-background rounded-md",
41
+ "animate-in fade-in zoom-in-95",
42
+ "data-[state=closed]:animate-out data-[state=closed]:fill-mode-forwards fade-out zoom-out-95",
43
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
44
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
45
+ ],
46
+ });
47
+
48
+ export const tooltipCaret = cv({
49
+ base: [
50
+ "text-foreground absolute z-50 size-4",
51
+ "group-data-[side=top]:bottom-0 group-data-[side=top]:left-1/2 group-data-[side=top]:-translate-x-1/2 group-data-[side=top]:translate-y-[calc(50%+1px)] group-data-[side=top]:rotate-180",
52
+ "group-data-[side=bottom]:top-0 group-data-[side=bottom]:left-1/2 group-data-[side=bottom]:-translate-x-1/2 group-data-[side=bottom]:-translate-y-[calc(50%+1px)]",
53
+ "group-data-[side=left]:top-1/2 group-data-[side=left]:right-0 group-data-[side=left]:translate-x-[calc(50%+1px)] group-data-[side=left]:-translate-y-1/2 group-data-[side=left]:rotate-90",
54
+ "group-data-[side=right]:top-1/2 group-data-[side=right]:left-0 group-data-[side=right]:-translate-x-[calc(50%+1px)] group-data-[side=right]:-translate-y-1/2 group-data-[side=right]:-rotate-90",
55
+ ],
56
+ });
57
+
58
+ const {
59
+ side = "top",
60
+ align = "center",
61
+ sideOffset = 8,
62
+ avoidCollisions = true,
63
+ animationDuration = 150,
64
+ class: className,
65
+ } = Astro.props;
66
+ ---
67
+
68
+ <div
69
+ class:list={[tooltipContent(), className]}
70
+ data-state="closed"
71
+ data-side={side}
72
+ data-align={align}
73
+ data-side-offset={sideOffset}
74
+ {...avoidCollisions && { "data-avoid-collisions": "" }}
75
+ role="tooltip"
76
+ style={{ animationDuration: `${animationDuration}ms` }}
77
+ data-slot="tooltip-content"
78
+ >
79
+ <slot> My tooltip! </slot>
80
+ <span class={tooltipCaret()}>
81
+ <IconCaretUp />
82
+ </span>
83
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ /**
3
+ * This component is used to trigger the tooltip
4
+ * All it does is require a slot
5
+ */
6
+
7
+ export interface TooltipTriggerProps {
8
+ children: any;
9
+ }
10
+ type Props = TooltipTriggerProps;
11
+ ---
12
+
13
+ <slot />
@@ -0,0 +1,121 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cv } from "css-variants";
4
+
5
+ export const video = cv({
6
+ base: "starwind-video aspect-video h-auto w-full",
7
+ });
8
+
9
+ export interface VideoProps extends HTMLAttributes<"video">, HTMLAttributes<"iframe"> {
10
+ src: string;
11
+ title?: string;
12
+ autoplay?: boolean;
13
+ muted?: boolean;
14
+ loop?: boolean;
15
+ controls?: boolean;
16
+ poster?: string;
17
+ }
18
+
19
+ type Props = VideoProps;
20
+
21
+ const {
22
+ class: className,
23
+ src,
24
+ title = "Video",
25
+ autoplay = false,
26
+ muted = false,
27
+ loop = false,
28
+ controls = true,
29
+ poster,
30
+ ...rest
31
+ } = Astro.props;
32
+
33
+ /**
34
+ * Detects the video type from the src URL
35
+ */
36
+ function getVideoType(url: string): "youtube" | "youtube-shorts" | "native" {
37
+ if (url.includes("youtube.com/shorts/") || url.includes("youtu.be/shorts/")) {
38
+ return "youtube-shorts";
39
+ }
40
+ if (
41
+ url.includes("youtube.com") ||
42
+ url.includes("youtu.be") ||
43
+ url.includes("youtube-nocookie.com")
44
+ ) {
45
+ return "youtube";
46
+ }
47
+ return "native";
48
+ }
49
+
50
+ /**
51
+ * Extracts YouTube video ID from various URL formats
52
+ */
53
+ function getYouTubeId(url: string): string | null {
54
+ const patterns = [
55
+ /youtube\.com\/shorts\/([^?&]+)/,
56
+ /youtube\.com\/watch\?v=([^&]+)/,
57
+ /youtube\.com\/embed\/([^?&]+)/,
58
+ /youtu\.be\/([^?&]+)/,
59
+ /youtube-nocookie\.com\/embed\/([^?&]+)/,
60
+ ];
61
+
62
+ for (const pattern of patterns) {
63
+ const match = url.match(pattern);
64
+ if (match) return match[1] || null;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * Builds YouTube embed URL with parameters
71
+ */
72
+ function buildYouTubeEmbedUrl(videoId: string, _isShort: boolean): string {
73
+ const params = new URLSearchParams();
74
+ if (autoplay) params.set("autoplay", "1");
75
+ if (muted) params.set("mute", "1");
76
+ if (loop) {
77
+ params.set("loop", "1");
78
+ params.set("playlist", videoId);
79
+ }
80
+ if (!controls) params.set("controls", "0");
81
+
82
+ const baseUrl = `https://www.youtube-nocookie.com/embed/${videoId}`;
83
+ const queryString = params.toString();
84
+ return queryString ? `${baseUrl}?${queryString}` : baseUrl;
85
+ }
86
+
87
+ const videoType = getVideoType(src);
88
+ const youtubeId = videoType !== "native" ? getYouTubeId(src) : null;
89
+ const isShort = videoType === "youtube-shorts";
90
+ const embedUrl = youtubeId ? buildYouTubeEmbedUrl(youtubeId, isShort) : null;
91
+ ---
92
+
93
+ {
94
+ videoType === "native" || !embedUrl ? (
95
+ <video
96
+ class:list={[video(), className]}
97
+ src={src}
98
+ autoplay={autoplay}
99
+ muted={muted}
100
+ loop={loop}
101
+ controls={controls}
102
+ poster={poster}
103
+ {...rest}
104
+ data-slot="video"
105
+ >
106
+ <track kind="captions" />
107
+ </video>
108
+ ) : (
109
+ <iframe
110
+ class:list={[video(), className]}
111
+ src={embedUrl}
112
+ title={title}
113
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
114
+ referrerpolicy="strict-origin-when-cross-origin"
115
+ allowfullscreen
116
+ data-video-type={isShort ? "youtube-shorts" : "youtube"}
117
+ {...rest}
118
+ data-slot="video"
119
+ />
120
+ )
121
+ }
@@ -0,0 +1,5 @@
1
+ // Cookies / runtime-policy
2
+
3
+ export const COOKIE_TTL = {
4
+ oneYear: 60 * 60 * 24 * 365
5
+ } as const
@@ -0,0 +1,3 @@
1
+ export * from "./site.config"
2
+ export * from "./links"
3
+ export * from "./cookies"
@@ -0,0 +1,4 @@
1
+ export const LINKS = {
2
+ edgeKitsSite: "https://edgekits.dev",
3
+ twitter: `https://x.com/GaryEdgeKits`
4
+ } as const
@@ -0,0 +1,45 @@
1
+ export interface SiteConfig {
2
+ id: string
3
+ name: string
4
+ description: string
5
+ url: string
6
+ ogImage: string
7
+ author: string
8
+ email: string
9
+ branding: {
10
+ logo: {
11
+ alt: string
12
+ }
13
+ favicon: {
14
+ svg: string
15
+ }
16
+ colors: {
17
+ themeColor: string
18
+ backgroundColor: string
19
+ }
20
+ }
21
+ }
22
+
23
+ export const SITE_CONFIG: SiteConfig = {
24
+ id: "danielmarchi.dev",
25
+ name: "Daniel Marchi",
26
+ description: "A portfolio website built with Astro.",
27
+ url: "https://danielmarchi.dev",
28
+ ogImage: "/og-default.png",
29
+ author: "Daniel Marchi",
30
+ email: "hello@danielmarchi.dev",
31
+ branding: {
32
+ logo: {
33
+ alt: "Daniel Marchi"
34
+ },
35
+ favicon: {
36
+ svg: "/favicon.svg"
37
+ },
38
+ colors: {
39
+ themeColor: "#F94C10",
40
+ backgroundColor: "#ffffff"
41
+ }
42
+ }
43
+ }
44
+
45
+ export default SITE_CONFIG
@@ -0,0 +1 @@
1
+ export * from "./types"