@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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/i18n-data.json +186 -0
- package/package.json +49 -0
- package/src/components/FormattedDate.astro +22 -0
- package/src/components/HeaderLink.astro +24 -0
- package/src/components/HttpObservatory.astro +103 -0
- package/src/components/LighthouseScores.astro +204 -0
- package/src/components/MissingTranslationBanner.astro +39 -0
- package/src/components/TableOfContents.astro +106 -0
- package/src/components/ThemeToggle.astro +71 -0
- package/src/components/accordion/Accordion.astro +257 -0
- package/src/components/accordion/AccordionContent.astro +34 -0
- package/src/components/accordion/AccordionItem.astro +29 -0
- package/src/components/accordion/AccordionTrigger.astro +33 -0
- package/src/components/alert/Alert.astro +34 -0
- package/src/components/alert/AlertDescription.astro +15 -0
- package/src/components/alert/AlertTitle.astro +17 -0
- package/src/components/alert-dialog/AlertDialog.astro +275 -0
- package/src/components/alert-dialog/AlertDialogAction.astro +46 -0
- package/src/components/alert-dialog/AlertDialogCancel.astro +46 -0
- package/src/components/alert-dialog/AlertDialogContent.astro +52 -0
- package/src/components/alert-dialog/AlertDialogDescription.astro +18 -0
- package/src/components/alert-dialog/AlertDialogFooter.astro +16 -0
- package/src/components/alert-dialog/AlertDialogHeader.astro +14 -0
- package/src/components/alert-dialog/AlertDialogTitle.astro +20 -0
- package/src/components/alert-dialog/AlertDialogTrigger.astro +47 -0
- package/src/components/aspect-ratio/AspectRatio.astro +32 -0
- package/src/components/avatar/Avatar.astro +29 -0
- package/src/components/avatar/AvatarFallback.astro +18 -0
- package/src/components/avatar/AvatarImage.astro +66 -0
- package/src/components/badge/Badge.astro +57 -0
- package/src/components/breadcrumb/Breadcrumb.astro +12 -0
- package/src/components/breadcrumb/BreadcrumbEllipsis.astro +29 -0
- package/src/components/breadcrumb/BreadcrumbItem.astro +15 -0
- package/src/components/breadcrumb/BreadcrumbLink.astro +24 -0
- package/src/components/breadcrumb/BreadcrumbList.astro +17 -0
- package/src/components/breadcrumb/BreadcrumbPage.astro +22 -0
- package/src/components/breadcrumb/BreadcrumbSeparator.astro +24 -0
- package/src/components/button/Button.astro +58 -0
- package/src/components/button-group/ButtonGroup.astro +64 -0
- package/src/components/button-group/ButtonGroupSeparator.astro +20 -0
- package/src/components/button-group/ButtonGroupText.astro +19 -0
- package/src/components/card/Card.astro +36 -0
- package/src/components/card/CardAction.astro +16 -0
- package/src/components/card/CardContent.astro +16 -0
- package/src/components/card/CardDescription.astro +16 -0
- package/src/components/card/CardFooter.astro +16 -0
- package/src/components/card/CardHeader.astro +19 -0
- package/src/components/card/CardTitle.astro +16 -0
- package/src/components/carousel/Carousel.astro +55 -0
- package/src/components/carousel/CarouselContent.astro +26 -0
- package/src/components/carousel/CarouselItem.astro +26 -0
- package/src/components/carousel/CarouselNext.astro +39 -0
- package/src/components/carousel/CarouselPrevious.astro +41 -0
- package/src/components/carousel/carousel-script.ts +188 -0
- package/src/components/carousel/index.ts +1 -0
- package/src/components/checkbox/Checkbox.astro +133 -0
- package/src/components/collapsible/Collapsible.astro +161 -0
- package/src/components/collapsible/CollapsibleContent.astro +22 -0
- package/src/components/collapsible/CollapsibleTrigger.astro +44 -0
- package/src/components/dialog/Dialog.astro +389 -0
- package/src/components/dialog/DialogClose.astro +35 -0
- package/src/components/dialog/DialogContent.astro +78 -0
- package/src/components/dialog/DialogDescription.astro +14 -0
- package/src/components/dialog/DialogFooter.astro +14 -0
- package/src/components/dialog/DialogHeader.astro +14 -0
- package/src/components/dialog/DialogTitle.astro +18 -0
- package/src/components/dialog/DialogTrigger.astro +47 -0
- package/src/components/dropdown/Dropdown.astro +604 -0
- package/src/components/dropdown/DropdownContent.astro +118 -0
- package/src/components/dropdown/DropdownItem.astro +48 -0
- package/src/components/dropdown/DropdownLabel.astro +29 -0
- package/src/components/dropdown/DropdownSeparator.astro +21 -0
- package/src/components/dropdown/DropdownShortcut.astro +17 -0
- package/src/components/dropdown/DropdownSub.astro +15 -0
- package/src/components/dropdown/DropdownSubContent.astro +39 -0
- package/src/components/dropdown/DropdownSubTrigger.astro +34 -0
- package/src/components/dropdown/DropdownTrigger.astro +52 -0
- package/src/components/dropzone/Dropzone.astro +236 -0
- package/src/components/dropzone/DropzoneFilesList.astro +26 -0
- package/src/components/dropzone/DropzoneLoadingIndicator.astro +10 -0
- package/src/components/dropzone/DropzoneUploadIndicator.astro +10 -0
- package/src/components/embeds/YoutubeEmbed.astro +94 -0
- package/src/components/hero/Hero.astro +166 -0
- package/src/components/icons/Flag.astro +62 -0
- package/src/components/image/ExtendedImage.astro +46 -0
- package/src/components/image/Image.astro +25 -0
- package/src/components/input/Input.astro +27 -0
- package/src/components/input-group/InputGroup.astro +28 -0
- package/src/components/input-group/InputGroupAddon.astro +76 -0
- package/src/components/input-group/InputGroupButton.astro +36 -0
- package/src/components/input-group/InputGroupInput.astro +18 -0
- package/src/components/input-group/InputGroupText.astro +17 -0
- package/src/components/input-group/InputGroupTextarea.astro +22 -0
- package/src/components/input-otp/InputOtp.astro +352 -0
- package/src/components/input-otp/InputOtpGroup.astro +16 -0
- package/src/components/input-otp/InputOtpSeparator.astro +25 -0
- package/src/components/input-otp/InputOtpSlot.astro +50 -0
- package/src/components/input-otp/InputOtpTypes.ts +6 -0
- package/src/components/item/Item.astro +52 -0
- package/src/components/item/ItemActions.astro +16 -0
- package/src/components/item/ItemContent.astro +16 -0
- package/src/components/item/ItemDescription.astro +19 -0
- package/src/components/item/ItemFooter.astro +16 -0
- package/src/components/item/ItemGroup.astro +16 -0
- package/src/components/item/ItemHeader.astro +16 -0
- package/src/components/item/ItemMedia.astro +40 -0
- package/src/components/item/ItemSeparator.astro +23 -0
- package/src/components/item/ItemTitle.astro +16 -0
- package/src/components/kbd/Kbd.astro +21 -0
- package/src/components/kbd/KbdGroup.astro +16 -0
- package/src/components/label/Label.astro +24 -0
- package/src/components/marquee/Marquee.astro +73 -0
- package/src/components/native-select/NativeSelect.astro +68 -0
- package/src/components/native-select/NativeSelectOptGroup.astro +15 -0
- package/src/components/native-select/NativeSelectOption.astro +15 -0
- package/src/components/pagination/Pagination.astro +20 -0
- package/src/components/pagination/PaginationContent.astro +16 -0
- package/src/components/pagination/PaginationEllipsis.astro +37 -0
- package/src/components/pagination/PaginationItem.astro +16 -0
- package/src/components/pagination/PaginationLink.astro +27 -0
- package/src/components/pagination/PaginationNext.astro +33 -0
- package/src/components/pagination/PaginationPrevious.astro +33 -0
- package/src/components/popover/Popover.astro +717 -0
- package/src/components/popover/PopoverContent.astro +102 -0
- package/src/components/popover/PopoverDescription.astro +14 -0
- package/src/components/popover/PopoverHeader.astro +14 -0
- package/src/components/popover/PopoverTitle.astro +14 -0
- package/src/components/popover/PopoverTrigger.astro +51 -0
- package/src/components/progress/Progress.astro +154 -0
- package/src/components/prose/Prose.astro +619 -0
- package/src/components/radio-group/RadioGroup.astro +162 -0
- package/src/components/radio-group/RadioGroupItem.astro +133 -0
- package/src/components/radio-group/RadioGroupTypes.ts +6 -0
- package/src/components/section/Section.astro +167 -0
- package/src/components/select/Select.astro +752 -0
- package/src/components/select/SelectContent.astro +94 -0
- package/src/components/select/SelectGroup.astro +9 -0
- package/src/components/select/SelectItem.astro +51 -0
- package/src/components/select/SelectLabel.astro +14 -0
- package/src/components/select/SelectSearch.astro +49 -0
- package/src/components/select/SelectSeparator.astro +12 -0
- package/src/components/select/SelectTrigger.astro +56 -0
- package/src/components/select/SelectTypes.ts +13 -0
- package/src/components/select/SelectValue.astro +19 -0
- package/src/components/separator/Separator.astro +36 -0
- package/src/components/sheet/Sheet.astro +15 -0
- package/src/components/sheet/SheetClose.astro +15 -0
- package/src/components/sheet/SheetContent.astro +89 -0
- package/src/components/sheet/SheetDescription.astro +16 -0
- package/src/components/sheet/SheetFooter.astro +16 -0
- package/src/components/sheet/SheetHeader.astro +16 -0
- package/src/components/sheet/SheetTitle.astro +16 -0
- package/src/components/sheet/SheetTrigger.astro +15 -0
- package/src/components/sidebar/Sidebar.astro +231 -0
- package/src/components/sidebar/SidebarContent.astro +25 -0
- package/src/components/sidebar/SidebarFooter.astro +22 -0
- package/src/components/sidebar/SidebarGroup.astro +22 -0
- package/src/components/sidebar/SidebarGroupContent.astro +22 -0
- package/src/components/sidebar/SidebarGroupLabel.astro +53 -0
- package/src/components/sidebar/SidebarHeader.astro +22 -0
- package/src/components/sidebar/SidebarInput.astro +24 -0
- package/src/components/sidebar/SidebarInset.astro +22 -0
- package/src/components/sidebar/SidebarMenu.astro +22 -0
- package/src/components/sidebar/SidebarMenuAction.astro +63 -0
- package/src/components/sidebar/SidebarMenuBadge.astro +31 -0
- package/src/components/sidebar/SidebarMenuButton.astro +124 -0
- package/src/components/sidebar/SidebarMenuItem.astro +22 -0
- package/src/components/sidebar/SidebarMenuSkeleton.astro +43 -0
- package/src/components/sidebar/SidebarMenuSub.astro +25 -0
- package/src/components/sidebar/SidebarMenuSubButton.astro +52 -0
- package/src/components/sidebar/SidebarMenuSubItem.astro +17 -0
- package/src/components/sidebar/SidebarProvider.astro +215 -0
- package/src/components/sidebar/SidebarRail.astro +72 -0
- package/src/components/sidebar/SidebarSeparator.astro +25 -0
- package/src/components/sidebar/SidebarTrigger.astro +69 -0
- package/src/components/skeleton/Skeleton.astro +14 -0
- package/src/components/slider/Slider.astro +425 -0
- package/src/components/spinner/Spinner.astro +23 -0
- package/src/components/switch/Switch.astro +192 -0
- package/src/components/switch/SwitchTypes.ts +6 -0
- package/src/components/table/Table.astro +20 -0
- package/src/components/table/TableBody.astro +18 -0
- package/src/components/table/TableCaption.astro +18 -0
- package/src/components/table/TableCell.astro +18 -0
- package/src/components/table/TableFoot.astro +18 -0
- package/src/components/table/TableHead.astro +18 -0
- package/src/components/table/TableHeader.astro +18 -0
- package/src/components/table/TableRow.astro +17 -0
- package/src/components/tabs/Tabs.astro +270 -0
- package/src/components/tabs/TabsContent.astro +28 -0
- package/src/components/tabs/TabsList.astro +23 -0
- package/src/components/tabs/TabsTrigger.astro +34 -0
- package/src/components/textarea/Textarea.astro +31 -0
- package/src/components/toast/ToastDescription.astro +22 -0
- package/src/components/toast/ToastItem.astro +56 -0
- package/src/components/toast/ToastTemplate.astro +27 -0
- package/src/components/toast/ToastTitle.astro +59 -0
- package/src/components/toast/Toaster.astro +981 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast-manager.ts +234 -0
- package/src/components/toggle/Toggle.astro +174 -0
- package/src/components/toggle/ToggleTypes.ts +14 -0
- package/src/components/tooltip/Tooltip.astro +422 -0
- package/src/components/tooltip/TooltipContent.astro +83 -0
- package/src/components/tooltip/TooltipTrigger.astro +13 -0
- package/src/components/video/Video.astro +121 -0
- package/src/config/cookies.ts +5 -0
- package/src/config/index.ts +3 -0
- package/src/config/links.ts +4 -0
- package/src/config/site.config.ts +45 -0
- package/src/domain/cms/index.ts +1 -0
- package/src/domain/cms/types.ts +20 -0
- package/src/domain/i18n/constants.ts +21 -0
- package/src/domain/i18n/cookie-storage.ts +13 -0
- package/src/domain/i18n/country-to-locale-map.ts +67 -0
- package/src/domain/i18n/fallbacks.generated.ts +230 -0
- package/src/domain/i18n/fetcher.ts +268 -0
- package/src/domain/i18n/flags.ts +16 -0
- package/src/domain/i18n/format.ts +90 -0
- package/src/domain/i18n/index.ts +10 -0
- package/src/domain/i18n/middleware/i18n.ts +160 -0
- package/src/domain/i18n/resolve-locale.ts +55 -0
- package/src/domain/i18n/runtime-constants.ts +14 -0
- package/src/domain/i18n/schema.ts +29 -0
- package/src/domain/index.ts +4 -0
- package/src/domain/seo/constants.ts +20 -0
- package/src/domain/seo/index.ts +3 -0
- package/src/domain/seo/services/index.ts +3 -0
- package/src/domain/seo/services/llms.ts +53 -0
- package/src/domain/seo/services/robots.ts +12 -0
- package/src/domain/seo/services/sitemap.ts +56 -0
- package/src/domain/seo/utils.ts +37 -0
- package/src/domain/theme/constants.ts +6 -0
- package/src/domain/theme/index.ts +2 -0
- package/src/domain/theme/schema.ts +10 -0
- package/src/env.d.ts +49 -0
- package/src/i18n.generated.d.ts +202 -0
- package/src/lib/component-preview.ts +73 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/remark/remark-modified-time.ts +13 -0
- package/src/lib/showcase-preview-elements.ts +408 -0
- package/src/lib/showcase-renderer.ts +218 -0
- package/src/lib/showcase-utils.ts +189 -0
- package/src/lib/turnstile.ts +35 -0
- package/src/lib/utils/starwind/positioning.ts +318 -0
- package/src/locales/de/blog.json +9 -0
- package/src/locales/de/common.json +19 -0
- package/src/locales/de/errors.json +6 -0
- package/src/locales/de/home.json +20 -0
- package/src/locales/de/messages.json +8 -0
- package/src/locales/de/nav.json +13 -0
- package/src/locales/de/projects.json +9 -0
- package/src/locales/de/services.json +43 -0
- package/src/locales/en/blog.json +9 -0
- package/src/locales/en/chat.json +29 -0
- package/src/locales/en/common.json +19 -0
- package/src/locales/en/cookie_notice.json +11 -0
- package/src/locales/en/editor.json +24 -0
- package/src/locales/en/errors.json +6 -0
- package/src/locales/en/home.json +20 -0
- package/src/locales/en/messages.json +8 -0
- package/src/locales/en/nav.json +13 -0
- package/src/locales/en/projects.json +9 -0
- package/src/locales/en/services.json +43 -0
- package/src/locales/es/blog.json +9 -0
- package/src/locales/es/common.json +19 -0
- package/src/locales/es/errors.json +6 -0
- package/src/locales/es/home.json +20 -0
- package/src/locales/es/messages.json +8 -0
- package/src/locales/es/nav.json +13 -0
- package/src/locales/es/projects.json +9 -0
- package/src/locales/es/services.json +43 -0
- package/src/locales/ja/blog.json +9 -0
- package/src/locales/ja/common.json +19 -0
- package/src/locales/ja/errors.json +6 -0
- package/src/locales/ja/home.json +20 -0
- package/src/locales/ja/messages.json +8 -0
- package/src/locales/ja/nav.json +13 -0
- package/src/locales/ja/projects.json +9 -0
- package/src/locales/ja/services.json +43 -0
- package/src/locales/pt/blog.json +9 -0
- package/src/locales/pt/chat.json +29 -0
- package/src/locales/pt/common.json +19 -0
- package/src/locales/pt/cookie_notice.json +11 -0
- package/src/locales/pt/editor.json +24 -0
- package/src/locales/pt/errors.json +6 -0
- package/src/locales/pt/home.json +20 -0
- package/src/locales/pt/messages.json +8 -0
- package/src/locales/pt/nav.json +13 -0
- package/src/locales/pt/projects.json +9 -0
- package/src/locales/pt/services.json +43 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/server/cookies.ts +98 -0
- package/src/utils/server/index.ts +1 -0
- package/src/utils/shared/deep-merge.ts +36 -0
- package/src/utils/shared/embed.ts +105 -0
- package/src/utils/shared/index.ts +3 -0
- package/src/utils/shared/parse-boolean.ts +14 -0
- package/worker-configuration.d.ts +14791 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* LighthouseScores.astro
|
|
4
|
+
* A callout component displaying perfect Lighthouse scores
|
|
5
|
+
* Designed to match the authentic Lighthouse report aesthetic
|
|
6
|
+
*/
|
|
7
|
+
export interface LighthouseScore {
|
|
8
|
+
label: string;
|
|
9
|
+
value: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LighthouseScoresProps {
|
|
13
|
+
performance?: number;
|
|
14
|
+
accessibility?: number;
|
|
15
|
+
bestPractices?: number;
|
|
16
|
+
seo?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
performance = 100,
|
|
21
|
+
accessibility = 100,
|
|
22
|
+
bestPractices = 100,
|
|
23
|
+
seo = 100,
|
|
24
|
+
} = Astro.props;
|
|
25
|
+
|
|
26
|
+
const scores = [
|
|
27
|
+
{ label: 'Performance', value: performance },
|
|
28
|
+
{ label: 'Accessibility', value: accessibility },
|
|
29
|
+
{ label: 'Best Practices', value: bestPractices },
|
|
30
|
+
{ label: 'SEO', value: seo },
|
|
31
|
+
];
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<div class="lighthouse-container">
|
|
35
|
+
<!-- Header -->
|
|
36
|
+
<div class="mb-4xl text-center">
|
|
37
|
+
<h3 class="font-heading text-foreground text-3xl font-bold md:text-4xl">
|
|
38
|
+
Lighthouse Report
|
|
39
|
+
</h3>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Scores Grid -->
|
|
43
|
+
<div
|
|
44
|
+
class="lighthouse-scores mx-auto grid max-w-2xl grid-cols-2 gap-4xl sm:grid-cols-4"
|
|
45
|
+
role="list"
|
|
46
|
+
aria-label="Lighthouse scores"
|
|
47
|
+
>
|
|
48
|
+
{
|
|
49
|
+
scores.map((score) => {
|
|
50
|
+
const radius = 54;
|
|
51
|
+
const circumference = 2 * Math.PI * radius;
|
|
52
|
+
const offset = circumference - (score.value / 100) * circumference;
|
|
53
|
+
const color = score.value >= 90 ? "#0cce6b" : score.value >= 50 ? "#ffa400" : "#ff4e42";
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
class="lighthouse-score group flex flex-col items-center"
|
|
58
|
+
role="listitem"
|
|
59
|
+
>
|
|
60
|
+
<div class="relative">
|
|
61
|
+
<svg
|
|
62
|
+
class="lighthouse-gauge"
|
|
63
|
+
width="120"
|
|
64
|
+
height="120"
|
|
65
|
+
viewBox="0 0 120 120"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
style="width: 96px; height: 96px;"
|
|
68
|
+
>
|
|
69
|
+
<style>
|
|
70
|
+
@media (min-width: 640px) {
|
|
71
|
+
.lighthouse-gauge {
|
|
72
|
+
width: 112px !important;
|
|
73
|
+
height: 112px !important;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
@media (min-width: 768px) {
|
|
77
|
+
.lighthouse-gauge {
|
|
78
|
+
width: 144px !important;
|
|
79
|
+
height: 144px !important;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
<circle
|
|
84
|
+
cx="60"
|
|
85
|
+
cy="60"
|
|
86
|
+
r={radius}
|
|
87
|
+
fill="none"
|
|
88
|
+
stroke="#e0e0e0"
|
|
89
|
+
stroke-width="8"
|
|
90
|
+
/>
|
|
91
|
+
<circle
|
|
92
|
+
cx="60"
|
|
93
|
+
cy="60"
|
|
94
|
+
r={radius}
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke={color}
|
|
97
|
+
stroke-width="8"
|
|
98
|
+
stroke-linecap="round"
|
|
99
|
+
stroke-dasharray={circumference}
|
|
100
|
+
stroke-dashoffset={offset}
|
|
101
|
+
class="lighthouse-progress"
|
|
102
|
+
transform="rotate(-90 60 60)"
|
|
103
|
+
style={`--target-offset: ${offset}`}
|
|
104
|
+
/>
|
|
105
|
+
<text
|
|
106
|
+
x="60"
|
|
107
|
+
y="60"
|
|
108
|
+
text-anchor="middle"
|
|
109
|
+
dominant-baseline="central"
|
|
110
|
+
class="lighthouse-number font-semibold font-sans"
|
|
111
|
+
fill={color}
|
|
112
|
+
style="font-size: 2rem;"
|
|
113
|
+
>
|
|
114
|
+
{score.value}
|
|
115
|
+
</text>
|
|
116
|
+
</svg>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<span class="text-muted-foreground mt-2xl text-center text-sm font-medium">
|
|
120
|
+
{score.label}
|
|
121
|
+
</span>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Footer note -->
|
|
129
|
+
<p class="text-muted-foreground/60 mt-4xl text-center text-xs italic">
|
|
130
|
+
*Results represent the starting template. Changes to styling and features may affect final results.
|
|
131
|
+
</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<style>
|
|
135
|
+
/* Entrance animations */
|
|
136
|
+
.lighthouse-score {
|
|
137
|
+
animation: score-enter 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.lighthouse-progress {
|
|
141
|
+
animation: progress-fill 1.2s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Staggered delays via nth-child to avoid inline style attributes */
|
|
145
|
+
.lighthouse-score:nth-child(1) { animation-delay: 100ms; }
|
|
146
|
+
.lighthouse-score:nth-child(2) { animation-delay: 180ms; }
|
|
147
|
+
.lighthouse-score:nth-child(3) { animation-delay: 260ms; }
|
|
148
|
+
.lighthouse-score:nth-child(4) { animation-delay: 340ms; }
|
|
149
|
+
|
|
150
|
+
.lighthouse-progress:nth-child(1) { animation-delay: 200ms; }
|
|
151
|
+
.lighthouse-progress:nth-child(2) { animation-delay: 280ms; }
|
|
152
|
+
.lighthouse-progress:nth-child(3) { animation-delay: 360ms; }
|
|
153
|
+
.lighthouse-progress:nth-child(4) { animation-delay: 440ms; }
|
|
154
|
+
|
|
155
|
+
@keyframes score-enter {
|
|
156
|
+
from {
|
|
157
|
+
opacity: 0;
|
|
158
|
+
transform: scale(0.9) translateY(10px);
|
|
159
|
+
}
|
|
160
|
+
to {
|
|
161
|
+
opacity: 1;
|
|
162
|
+
transform: scale(1) translateY(0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@keyframes progress-fill {
|
|
167
|
+
from {
|
|
168
|
+
stroke-dashoffset: 339.292; /* Approximate circumference */
|
|
169
|
+
}
|
|
170
|
+
to {
|
|
171
|
+
stroke-dashoffset: var(--target-offset, 0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (prefers-reduced-motion: reduce) {
|
|
176
|
+
.lighthouse-score,
|
|
177
|
+
.lighthouse-progress {
|
|
178
|
+
animation: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.lighthouse-progress {
|
|
182
|
+
stroke-dashoffset: 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
186
|
+
|
|
187
|
+
<script>
|
|
188
|
+
// Run animations immediately
|
|
189
|
+
const initLighthouse = () => {
|
|
190
|
+
const scoresContainer = document.querySelector('.lighthouse-scores');
|
|
191
|
+
if (scoresContainer) {
|
|
192
|
+
const elements = scoresContainer.querySelectorAll<HTMLElement>('.lighthouse-score, .lighthouse-progress');
|
|
193
|
+
elements.forEach((el) => {
|
|
194
|
+
el.style.animationPlayState = 'running';
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Run on initial load
|
|
200
|
+
initLighthouse();
|
|
201
|
+
|
|
202
|
+
// Run on View Transitions navigate
|
|
203
|
+
document.addEventListener('astro:after-swap', initLighthouse);
|
|
204
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { checkMissingTranslation } from '../domain/i18n/resolve-locale'
|
|
3
|
+
import { fetchTranslations } from '../domain/i18n/fetcher'
|
|
4
|
+
|
|
5
|
+
export interface MissingTranslationBannerProps {}
|
|
6
|
+
type Props = MissingTranslationBannerProps;
|
|
7
|
+
|
|
8
|
+
const missingType = checkMissingTranslation(
|
|
9
|
+
Astro.locals.uiLocale,
|
|
10
|
+
Astro.locals.isMissingContent
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
let bannerText: string | null = null
|
|
14
|
+
|
|
15
|
+
if (missingType) {
|
|
16
|
+
const { cfContext, translationLocale } = Astro.locals
|
|
17
|
+
const runtime = { cf: Astro.request.cf, cfContext }
|
|
18
|
+
const { messages } = await fetchTranslations(
|
|
19
|
+
runtime,
|
|
20
|
+
translationLocale,
|
|
21
|
+
['messages']
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
bannerText =
|
|
25
|
+
missingType === 'content'
|
|
26
|
+
? messages.errors.ui.MISSING_TRANSLATED_CONTENT ||
|
|
27
|
+
'Sorry, this article is not yet available in your selected language.'
|
|
28
|
+
: messages.errors.ui.MISSING_TRANSLATED_UI ||
|
|
29
|
+
'Sorry, this page is not yet fully available in your selected language.'
|
|
30
|
+
}
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
bannerText && (
|
|
35
|
+
<div class='rounded px-4 py-2 mb-8 max-w-160 mx-auto border border-amber-300 bg-amber-50 text-amber-900 dark:border-amber-600 dark:bg-amber-900/20 dark:text-amber-600 text-center'>
|
|
36
|
+
{bannerText}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface TableOfContentsHeading {
|
|
3
|
+
depth: number; // The heading level (h1 = 1, h2 = 2, etc)
|
|
4
|
+
slug: string; // The ID of the heading for linking
|
|
5
|
+
text: string; // The text content of the heading
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TableOfContentsProps {
|
|
9
|
+
headings: TableOfContentsHeading[]; // Array of headings from your markdown content
|
|
10
|
+
levels?: 1 | 2 | 3; // How many levels of headers to show in TOC (default: 2)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Props = TableOfContentsProps;
|
|
14
|
+
|
|
15
|
+
const { headings, levels = 2 } = Astro.props as Props;
|
|
16
|
+
|
|
17
|
+
// Filter headings to only show up to the specified level
|
|
18
|
+
const filteredHeadings = headings.filter((heading) => heading.depth <= levels);
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div class="border-base-400 rounded-xs border p-3xs text-sm leading-tight">
|
|
22
|
+
<h4 class="text-xl font-medium">Table of Contents</h4>
|
|
23
|
+
<ul class="mt-2xl flex flex-col gap-3xs">
|
|
24
|
+
{
|
|
25
|
+
filteredHeadings.map((heading) => (
|
|
26
|
+
<li
|
|
27
|
+
class:list={{
|
|
28
|
+
"pl-3xs": heading.depth === 2,
|
|
29
|
+
"pl-6": heading.depth === 3,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<a
|
|
33
|
+
href={`#${heading.slug}`}
|
|
34
|
+
class="toc-link transition hover:text-primary-accent"
|
|
35
|
+
>
|
|
36
|
+
{heading.text}
|
|
37
|
+
</a>
|
|
38
|
+
</li>
|
|
39
|
+
))
|
|
40
|
+
}
|
|
41
|
+
</ul>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
.toc-current {
|
|
46
|
+
color: var(--primary);
|
|
47
|
+
font-weight: 500;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
// Wrapper for Blog post content
|
|
53
|
+
let wrappingElement: Element | null;
|
|
54
|
+
let observeHeaderTags: IntersectionObserver;
|
|
55
|
+
let allHeaderTags: NodeListOf<Element>;
|
|
56
|
+
|
|
57
|
+
// Function that runs when the Intersection Observer fires
|
|
58
|
+
function setCurrent(e: IntersectionObserverEntry[]) {
|
|
59
|
+
var allSectionLinks = document.querySelectorAll(".toc-link");
|
|
60
|
+
e.map((i) => {
|
|
61
|
+
if (i.isIntersecting === true) {
|
|
62
|
+
allSectionLinks.forEach((link) => link.classList.remove("toc-current"));
|
|
63
|
+
const targetLink = document.querySelector(
|
|
64
|
+
`a[href="#${i.target.id}"].toc-link`,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (targetLink) targetLink.classList.add("toc-current");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function initTOC() {
|
|
73
|
+
// update this with whatever class wraps your blog post content
|
|
74
|
+
wrappingElement = document.querySelector(".markdown-content");
|
|
75
|
+
if (wrappingElement !== null) {
|
|
76
|
+
// Get all H1/H2/H3 tags from the post
|
|
77
|
+
allHeaderTags = wrappingElement.querySelectorAll(
|
|
78
|
+
":scope > h1, :scope > h2, :scope > h3",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Intersection Observer Options
|
|
83
|
+
let options: IntersectionObserverInit = {
|
|
84
|
+
root: null,
|
|
85
|
+
rootMargin: "0px 0px -50% 0px",
|
|
86
|
+
threshold: [1],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Each Intersection Observer runs setCurrent
|
|
90
|
+
observeHeaderTags = new IntersectionObserver(setCurrent, options);
|
|
91
|
+
if (wrappingElement === null) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
allHeaderTags.forEach((tag) => {
|
|
95
|
+
// add scroll margin top to account for fixed navbar
|
|
96
|
+
tag.classList.add("scroll-mt-3xl");
|
|
97
|
+
observeHeaderTags.observe(tag);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// runs on initial page load
|
|
102
|
+
initTOC();
|
|
103
|
+
|
|
104
|
+
// runs on view transitions navigation
|
|
105
|
+
document.addEventListener("astro:after-swap", initTOC);
|
|
106
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
import IconSun from "~icons/lucide/sun"
|
|
3
|
+
import IconMoon from "~icons/lucide/moon"
|
|
4
|
+
import Toggle from "./toggle/Toggle.astro";
|
|
5
|
+
|
|
6
|
+
export interface ThemeToggleProps {
|
|
7
|
+
class?: string;
|
|
8
|
+
size?: "sm" | "md" | "lg";
|
|
9
|
+
}
|
|
10
|
+
type Props = ThemeToggleProps;
|
|
11
|
+
|
|
12
|
+
const { class: className, size = "md" } = Astro.props as Props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<Toggle
|
|
16
|
+
id="theme-toggle"
|
|
17
|
+
size={size}
|
|
18
|
+
class:list={[
|
|
19
|
+
"group text-muted-foreground hover:text-foreground rounded-full hover:bg-transparent",
|
|
20
|
+
className,
|
|
21
|
+
]}
|
|
22
|
+
variant="ghost"
|
|
23
|
+
className={className}
|
|
24
|
+
aria-label="Toggle theme"
|
|
25
|
+
>
|
|
26
|
+
<IconSun class="sun-wrapper" />
|
|
27
|
+
<IconMoon class="moon-wrapper" />
|
|
28
|
+
</Toggle>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
#theme-toggle[data-state="on"] .sun-wrapper {
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
#theme-toggle[data-state="off"] .sun-wrapper {
|
|
35
|
+
display: flex;
|
|
36
|
+
}
|
|
37
|
+
#theme-toggle[data-state="on"] .moon-wrapper {
|
|
38
|
+
display: flex;
|
|
39
|
+
}
|
|
40
|
+
#theme-toggle[data-state="off"] .moon-wrapper {
|
|
41
|
+
display: none;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
function setupThemeToggle() {
|
|
47
|
+
const button = document.getElementById('theme-toggle');
|
|
48
|
+
if (!button) return;
|
|
49
|
+
|
|
50
|
+
// Use current document class to set initial button state
|
|
51
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
52
|
+
button.setAttribute('data-state', isDark ? 'on' : 'off');
|
|
53
|
+
button.setAttribute('aria-pressed', isDark.toString());
|
|
54
|
+
|
|
55
|
+
button.addEventListener('click', () => {
|
|
56
|
+
// Toggle the class on the <html> tag
|
|
57
|
+
const newDark = document.documentElement.classList.toggle('dark');
|
|
58
|
+
|
|
59
|
+
// Update button state visually
|
|
60
|
+
button.setAttribute('data-state', newDark ? 'on' : 'off');
|
|
61
|
+
button.setAttribute('aria-pressed', newDark.toString());
|
|
62
|
+
|
|
63
|
+
// Save the user's choice
|
|
64
|
+
localStorage.setItem('theme', newDark ? 'dark' : 'light');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Runs on initial load AND after each navigation (View Transitions)
|
|
69
|
+
document.addEventListener('astro:page-load', setupThemeToggle);
|
|
70
|
+
</script>
|
|
71
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { cv } from "css-variants";
|
|
4
|
+
|
|
5
|
+
export interface AccordionProps extends HTMLAttributes<"div"> {
|
|
6
|
+
/**
|
|
7
|
+
* The type of accordion. If "single", only one item can be open at a time.
|
|
8
|
+
*/
|
|
9
|
+
type?: "single" | "multiple";
|
|
10
|
+
/**
|
|
11
|
+
* The value of the item that should be open by default
|
|
12
|
+
*/
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type Props = AccordionProps;
|
|
17
|
+
|
|
18
|
+
export const accordion = cv({ base: "starwind-accordion" });
|
|
19
|
+
|
|
20
|
+
const { type = "single", defaultValue, class: className, ...rest } = Astro.props;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
class:list={[accordion(), className]}
|
|
25
|
+
data-type={type}
|
|
26
|
+
data-value={defaultValue}
|
|
27
|
+
{...rest}
|
|
28
|
+
data-slot="accordion"
|
|
29
|
+
>
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
type AccordionType = "single" | "multiple";
|
|
35
|
+
type AccordionState = "open" | "closed";
|
|
36
|
+
|
|
37
|
+
/** Represents a single accordion item with its associated elements */
|
|
38
|
+
interface AccordionItem {
|
|
39
|
+
element: HTMLElement;
|
|
40
|
+
trigger: HTMLElement;
|
|
41
|
+
content: HTMLElement;
|
|
42
|
+
value: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handles the functionality of an accordion component.
|
|
47
|
+
* Supports single and multiple open items, keyboard navigation,
|
|
48
|
+
* and maintains ARIA accessibility standards.
|
|
49
|
+
*/
|
|
50
|
+
class AccordionHandler {
|
|
51
|
+
private accordion: HTMLElement;
|
|
52
|
+
private type: AccordionType;
|
|
53
|
+
private items: AccordionItem[];
|
|
54
|
+
private itemsByValue: Map<string, AccordionItem>;
|
|
55
|
+
private accordionId: string;
|
|
56
|
+
private isInitialized: boolean = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new AccordionHandler instance
|
|
60
|
+
* @param accordion - The root accordion element
|
|
61
|
+
* @param idx - Unique index for this accordion instance
|
|
62
|
+
*/
|
|
63
|
+
constructor(accordion: HTMLElement, idx: number) {
|
|
64
|
+
this.accordion = accordion;
|
|
65
|
+
this.type = (accordion.dataset.type || "single") as AccordionType;
|
|
66
|
+
this.accordionId = `starwind-accordion${idx}`;
|
|
67
|
+
|
|
68
|
+
// Cache all items and create lookup maps
|
|
69
|
+
this.items = this.initializeItems();
|
|
70
|
+
this.itemsByValue = new Map(this.items.map((item) => [item.value, item]));
|
|
71
|
+
|
|
72
|
+
this.setupItems();
|
|
73
|
+
this.setInitialState();
|
|
74
|
+
this.isInitialized = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Initializes accordion items by querying the DOM and setting up data structures
|
|
79
|
+
* @returns Array of AccordionItem objects
|
|
80
|
+
*/
|
|
81
|
+
private initializeItems(): AccordionItem[] {
|
|
82
|
+
return Array.from(this.accordion.querySelectorAll<HTMLElement>(".starwind-accordion-item"))
|
|
83
|
+
.map((element, idx) => {
|
|
84
|
+
const trigger = element.querySelector<HTMLElement>(".starwind-accordion-trigger");
|
|
85
|
+
const content = element.querySelector<HTMLElement>(".starwind-accordion-content");
|
|
86
|
+
const value = element.getAttribute("data-value") || String(idx);
|
|
87
|
+
|
|
88
|
+
if (!trigger || !content) return null;
|
|
89
|
+
|
|
90
|
+
return { element, trigger, content, value };
|
|
91
|
+
})
|
|
92
|
+
.filter((item): item is AccordionItem => item !== null);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sets up initial state and event listeners for all accordion items
|
|
97
|
+
*/
|
|
98
|
+
private setupItems(): void {
|
|
99
|
+
this.items.forEach((item, idx) => {
|
|
100
|
+
this.setupAccessibility(item, idx);
|
|
101
|
+
this.setContentHeight(item.content);
|
|
102
|
+
this.setupEventListeners(item);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sets up ARIA attributes and IDs for accessibility
|
|
108
|
+
* @param item - The accordion item to setup
|
|
109
|
+
* @param idx - Index of the item
|
|
110
|
+
*/
|
|
111
|
+
private setupAccessibility(item: AccordionItem, idx: number): void {
|
|
112
|
+
const triggerId = `${this.accordionId}-t${idx}`;
|
|
113
|
+
const contentId = `${this.accordionId}-c${idx}`;
|
|
114
|
+
|
|
115
|
+
item.trigger.id = triggerId;
|
|
116
|
+
item.trigger.setAttribute("aria-controls", contentId);
|
|
117
|
+
item.trigger.setAttribute("aria-expanded", "false");
|
|
118
|
+
|
|
119
|
+
item.content.id = contentId;
|
|
120
|
+
item.content.setAttribute("aria-labelledby", triggerId);
|
|
121
|
+
item.content.setAttribute("role", "region");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Calculates and sets the content height CSS variable for animations
|
|
126
|
+
* @param content - The content element to measure
|
|
127
|
+
*/
|
|
128
|
+
private setContentHeight(content: HTMLElement): void {
|
|
129
|
+
const contentInner = content.firstElementChild as HTMLElement;
|
|
130
|
+
if (contentInner) {
|
|
131
|
+
const height = contentInner.getBoundingClientRect().height;
|
|
132
|
+
content.style.setProperty("--starwind-accordion-content-height", `${height}px`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets the initial state based on the default value attribute
|
|
138
|
+
*/
|
|
139
|
+
private setInitialState(): void {
|
|
140
|
+
const defaultValue = this.accordion.dataset.value;
|
|
141
|
+
if (defaultValue) {
|
|
142
|
+
const item = this.itemsByValue.get(defaultValue);
|
|
143
|
+
if (item) {
|
|
144
|
+
this.setItemState(item, true);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Sets up click and keyboard event listeners for an accordion item
|
|
151
|
+
* @param item - The accordion item to setup listeners for
|
|
152
|
+
*/
|
|
153
|
+
private setupEventListeners(item: AccordionItem): void {
|
|
154
|
+
item.trigger.addEventListener("click", () => this.handleClick(item));
|
|
155
|
+
item.trigger.addEventListener("keydown", (e) => this.handleKeyDown(e, item));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handles click events on accordion triggers
|
|
160
|
+
* @param item - The clicked accordion item
|
|
161
|
+
*/
|
|
162
|
+
private handleClick(item: AccordionItem): void {
|
|
163
|
+
const isOpen = item.element.getAttribute("data-state") === "open";
|
|
164
|
+
this.toggleItem(item, !isOpen);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Handles keyboard navigation events
|
|
169
|
+
* @param event - The keyboard event
|
|
170
|
+
* @param item - The current accordion item
|
|
171
|
+
*/
|
|
172
|
+
private handleKeyDown(event: KeyboardEvent, item: AccordionItem): void {
|
|
173
|
+
const index = this.items.indexOf(item);
|
|
174
|
+
|
|
175
|
+
const keyActions: Record<string, () => void> = {
|
|
176
|
+
ArrowDown: () => this.focusItem(index + 1),
|
|
177
|
+
ArrowUp: () => this.focusItem(index - 1),
|
|
178
|
+
Home: () => this.focusItem(0),
|
|
179
|
+
End: () => this.focusItem(this.items.length - 1),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const action = keyActions[event.key];
|
|
183
|
+
if (action) {
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
action();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Focuses an accordion item by index with wrapping
|
|
191
|
+
* @param index - The target index to focus
|
|
192
|
+
*/
|
|
193
|
+
private focusItem(index: number): void {
|
|
194
|
+
if (this.items.length === 0) return;
|
|
195
|
+
const targetIndex = (index + this.items.length) % this.items.length;
|
|
196
|
+
this.items[targetIndex]?.trigger.focus();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Toggles an accordion item's state
|
|
201
|
+
* @param item - The item to toggle
|
|
202
|
+
* @param shouldOpen - Whether the item should be opened
|
|
203
|
+
*/
|
|
204
|
+
private toggleItem(item: AccordionItem, shouldOpen: boolean): void {
|
|
205
|
+
if (this.type === "single" && shouldOpen) {
|
|
206
|
+
// Close other items if in single mode
|
|
207
|
+
this.items.forEach((otherItem) => {
|
|
208
|
+
if (otherItem !== item && otherItem.element.getAttribute("data-state") === "open") {
|
|
209
|
+
this.setItemState(otherItem, false);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.setItemState(item, shouldOpen);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Sets the state of an accordion item
|
|
219
|
+
* @param item - The item to update
|
|
220
|
+
* @param isOpen - Whether the item should be open
|
|
221
|
+
*/
|
|
222
|
+
private setItemState(item: AccordionItem, isOpen: boolean): void {
|
|
223
|
+
const state: AccordionState = isOpen ? "open" : "closed";
|
|
224
|
+
|
|
225
|
+
// Skip animation during initial setup, enable for subsequent toggles
|
|
226
|
+
if (!this.isInitialized) {
|
|
227
|
+
item.content.style.setProperty("animation", "none");
|
|
228
|
+
} else {
|
|
229
|
+
item.content.style.removeProperty("animation");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Set content height variable for animations
|
|
233
|
+
this.setContentHeight(item.content);
|
|
234
|
+
|
|
235
|
+
item.element.setAttribute("data-state", state);
|
|
236
|
+
item.content.setAttribute("data-state", state);
|
|
237
|
+
item.trigger.setAttribute("data-state", state);
|
|
238
|
+
item.trigger.setAttribute("aria-expanded", isOpen.toString());
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Store instances in a WeakMap to avoid memory leaks
|
|
243
|
+
const accordionInstances = new WeakMap<HTMLElement, AccordionHandler>();
|
|
244
|
+
let accordionCounter = 0;
|
|
245
|
+
|
|
246
|
+
const setupAccordions = () => {
|
|
247
|
+
document.querySelectorAll<HTMLElement>(".starwind-accordion").forEach((accordion) => {
|
|
248
|
+
if (!accordionInstances.has(accordion)) {
|
|
249
|
+
accordionInstances.set(accordion, new AccordionHandler(accordion, accordionCounter++));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
setupAccordions();
|
|
255
|
+
document.addEventListener("astro:after-swap", setupAccordions);
|
|
256
|
+
document.addEventListener("starwind:init", setupAccordions);
|
|
257
|
+
</script>
|