fulldev-ui 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +24 -20
  3. package/public/r/accordion.json +2 -2
  4. package/public/r/alert-dialog.json +2 -2
  5. package/public/r/banner-2.json +1 -1
  6. package/public/r/banner.json +1 -5
  7. package/public/r/blocks-1.json +1 -1
  8. package/public/r/button.json +1 -1
  9. package/public/r/collapsible.json +2 -2
  10. package/public/r/combobox.json +4 -3
  11. package/public/r/command.json +5 -4
  12. package/public/r/contact-1.json +1 -1
  13. package/public/r/contact-2.json +1 -1
  14. package/public/r/contact-3.json +1 -1
  15. package/public/r/dialog.json +2 -2
  16. package/public/r/doc-1.json +1 -1
  17. package/public/r/dropdown-menu.json +4 -4
  18. package/public/r/hover-card.json +4 -4
  19. package/public/r/input-group.json +0 -1
  20. package/public/r/layout.json +3 -0
  21. package/public/r/navigation-menu.json +3 -3
  22. package/public/r/popover.json +3 -3
  23. package/public/r/radio-group.json +2 -2
  24. package/public/r/registry.json +26 -26
  25. package/public/r/select.json +3 -3
  26. package/public/r/sheet.json +2 -2
  27. package/public/r/sidebar-1.json +1 -1
  28. package/public/r/sidebar.json +1 -1
  29. package/public/r/slider.json +2 -2
  30. package/public/r/switch.json +2 -2
  31. package/public/r/tabs.json +4 -4
  32. package/public/r/toggle.json +2 -2
  33. package/public/r/tooltip.json +3 -3
  34. package/public/r/typography.json +1 -1
  35. package/registry.json +31 -21
  36. package/src/components/blocks/banner-2.astro +7 -4
  37. package/src/components/blocks/blocks-1.astro +23 -23
  38. package/src/components/blocks/contact-1.astro +2 -1
  39. package/src/components/blocks/contact-2.astro +2 -1
  40. package/src/components/blocks/contact-3.astro +2 -1
  41. package/src/components/blocks/doc-1.astro +40 -26
  42. package/src/components/blocks/sidebar-1.astro +48 -46
  43. package/src/components/ui/accordion/accordion.astro +8 -2
  44. package/src/components/ui/alert-dialog/alert-dialog.astro +8 -2
  45. package/src/components/ui/banner/banner.astro +23 -41
  46. package/src/components/ui/button/button.astro +4 -1
  47. package/src/components/ui/collapsible/collapsible.astro +8 -2
  48. package/src/components/ui/combobox/combobox-content.astro +1 -1
  49. package/src/components/ui/combobox/combobox.astro +9 -17
  50. package/src/components/ui/command/command-dialog.astro +1 -0
  51. package/src/components/ui/command/command.astro +40 -25
  52. package/src/components/ui/dialog/dialog.astro +8 -2
  53. package/src/components/ui/dropdown-menu/dropdown-menu-content.astro +1 -1
  54. package/src/components/ui/dropdown-menu/dropdown-menu-item.astro +40 -9
  55. package/src/components/ui/dropdown-menu/dropdown-menu.astro +8 -2
  56. package/src/components/ui/hover-card/hover-card-content.astro +1 -1
  57. package/src/components/ui/hover-card/hover-card.astro +15 -4
  58. package/src/components/ui/navigation-menu/navigation-menu-positioner.astro +1 -1
  59. package/src/components/ui/navigation-menu/navigation-menu.astro +11 -2
  60. package/src/components/ui/popover/popover-content.astro +2 -1
  61. package/src/components/ui/popover/popover.astro +9 -2
  62. package/src/components/ui/radio-group/radio-group.astro +8 -2
  63. package/src/components/ui/select/select-content.astro +2 -2
  64. package/src/components/ui/select/select.astro +11 -7
  65. package/src/components/ui/sheet/sheet.astro +8 -2
  66. package/src/components/ui/slider/slider.astro +10 -4
  67. package/src/components/ui/switch/switch.astro +8 -2
  68. package/src/components/ui/tabs/tabs-content.astro +1 -1
  69. package/src/components/ui/tabs/tabs-trigger.astro +3 -2
  70. package/src/components/ui/tabs/tabs.astro +8 -2
  71. package/src/components/ui/toggle/toggle.astro +8 -2
  72. package/src/components/ui/tooltip/tooltip-content.astro +1 -1
  73. package/src/components/ui/tooltip/tooltip.astro +11 -2
  74. package/src/components/ui/typography/typography.astro +1 -1
  75. package/src/styles/global.css +2 -2
@@ -21,6 +21,7 @@ import {
21
21
  import {
22
22
  DropdownMenu,
23
23
  DropdownMenuContent,
24
+ DropdownMenuItem,
24
25
  DropdownMenuTrigger,
25
26
  } from "@/components/ui/dropdown-menu"
26
27
  import { SectionContainer } from "@/components/ui/section"
@@ -180,11 +181,10 @@ const openLinks = markdownUrl
180
181
  </DropdownMenuTrigger>
181
182
  <DropdownMenuContent align="end" sideOffset={6} class="w-56">
182
183
  {openLinks.map((link) => (
183
- <a
184
+ <DropdownMenuItem
184
185
  href={link.href}
185
186
  target="_blank"
186
187
  rel="noreferrer noopener"
187
- class="hover:bg-accent hover:text-accent-foreground flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none"
188
188
  >
189
189
  {link.icon === "markdown" && <Markdown class="size-4" />}
190
190
  {link.icon === "chatgpt" && (
@@ -202,7 +202,7 @@ const openLinks = markdownUrl
202
202
  {link.icon === "cursor" && <Cursor class="size-4" />}
203
203
  <span>{link.label}</span>
204
204
  <ExternalLink class="text-muted-foreground ml-auto size-3.5" />
205
- </a>
205
+ </DropdownMenuItem>
206
206
  ))}
207
207
  </DropdownMenuContent>
208
208
  </DropdownMenu>
@@ -299,38 +299,52 @@ const openLinks = markdownUrl
299
299
  }
300
300
  </SectionContainer>
301
301
 
302
- <script is:inline>
302
+ <script>
303
303
  const copyIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>`
304
304
  const checkIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>`
305
- document.querySelectorAll("[data-slot='typography'] > pre").forEach((pre) => {
306
- if (pre.closest(".docs-code-block")) return
307
- const wrapper = document.createElement("div")
308
- const button = document.createElement("button")
309
- wrapper.className = "docs-code-block"
310
- button.type = "button"
311
- button.className = "docs-code-copy"
312
- button.ariaLabel = "Copy code"
313
- button.innerHTML = `<span data-copy-icon>${copyIcon}</span><span data-check-icon class="hidden">${checkIcon}</span>`
314
- pre.before(wrapper)
315
- wrapper.append(pre, button)
316
- })
317
- document.addEventListener("click", (event) => {
318
- const button = event.target?.closest?.(
305
+ const initializeCodeBlocks = () => {
306
+ document
307
+ .querySelectorAll("[data-slot='typography'] > pre")
308
+ .forEach((pre) => {
309
+ if (pre.closest(".docs-code-block")) return
310
+ const wrapper = document.createElement("div")
311
+ const button = document.createElement("button")
312
+ wrapper.className = "docs-code-block"
313
+ button.type = "button"
314
+ button.className = "docs-code-copy"
315
+ button.ariaLabel = "Copy code"
316
+ button.innerHTML = `<span data-copy-icon>${copyIcon}</span><span data-check-icon class="hidden">${checkIcon}</span>`
317
+ pre.before(wrapper)
318
+ wrapper.append(pre, button)
319
+ })
320
+ }
321
+
322
+ initializeCodeBlocks()
323
+ document.addEventListener("astro:page-load", initializeCodeBlocks)
324
+
325
+ document.addEventListener("click", async (event) => {
326
+ if (!(event.target instanceof Element)) return
327
+ const button = event.target.closest(
319
328
  "[id^='docs-copy-page-'], .docs-code-copy"
320
329
  )
321
- if (!button) return
330
+ if (!(button instanceof HTMLButtonElement)) return
322
331
  const source =
323
332
  button.dataset.source ??
324
333
  button
325
334
  .closest(".docs-code-block")
326
335
  ?.querySelector("pre")
327
336
  ?.textContent?.replace(/\n$/, "")
328
- button.querySelector("[data-copy-icon]")?.classList.add("hidden")
329
- button.querySelector("[data-check-icon]")?.classList.remove("hidden")
330
- navigator.clipboard?.writeText(source ?? "")
331
- setTimeout(() => {
332
- button.querySelector("[data-copy-icon]")?.classList.remove("hidden")
333
- button.querySelector("[data-check-icon]")?.classList.add("hidden")
334
- }, 1200)
337
+
338
+ try {
339
+ await navigator.clipboard.writeText(source ?? "")
340
+ button.querySelector("[data-copy-icon]")?.classList.add("hidden")
341
+ button.querySelector("[data-check-icon]")?.classList.remove("hidden")
342
+ setTimeout(() => {
343
+ button.querySelector("[data-copy-icon]")?.classList.remove("hidden")
344
+ button.querySelector("[data-check-icon]")?.classList.add("hidden")
345
+ }, 1200)
346
+ } catch {
347
+ // Keep the copy state unchanged so the action can be retried.
348
+ }
335
349
  })
336
350
  </script>
@@ -23,11 +23,12 @@ import {
23
23
  CommandList,
24
24
  CommandShortcut,
25
25
  } from "@/components/ui/command"
26
- import { Dialog, DialogTrigger } from "@/components/ui/dialog"
26
+ import { DialogTrigger } from "@/components/ui/dialog"
27
27
  import {
28
28
  DropdownMenu,
29
29
  DropdownMenuContent,
30
30
  DropdownMenuGroup,
31
+ DropdownMenuItem,
31
32
  DropdownMenuTrigger,
32
33
  } from "@/components/ui/dropdown-menu"
33
34
  import { Header, HeaderContainer, HeaderGroup } from "@/components/ui/header"
@@ -139,7 +140,7 @@ try {
139
140
  <SidebarProvider>
140
141
  <Sidebar class={cn("", className)} collapsible="offcanvas" variant="inset">
141
142
  <SidebarHeader class="flex-col">
142
- <SidebarMenuButton size="lg" variant="ghost" href={logo.href}>
143
+ <SidebarMenuButton size="lg" href={logo.href}>
143
144
  <LogoImage
144
145
  src={logo.src}
145
146
  srcLight={logo.srcLight}
@@ -148,8 +149,14 @@ try {
148
149
  />
149
150
  <LogoText class="font-semibold">{logo.label}</LogoText>
150
151
  </SidebarMenuButton>
151
- <Dialog class="w-full" data-search-dialog>
152
+ <CommandDialog
153
+ class="w-full"
154
+ data-search-dialog
155
+ title={search.label}
156
+ description={search.label}
157
+ >
152
158
  <DialogTrigger
159
+ slot="trigger"
153
160
  variant="outline"
154
161
  size="sm"
155
162
  class="w-full justify-start shadow-none"
@@ -165,40 +172,38 @@ try {
165
172
  </Kbd>
166
173
  </DialogTrigger>
167
174
 
168
- <CommandDialog title={search.label} description={search.label}>
169
- <Command
170
- class="[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground"
171
- >
172
- <CommandInput placeholder={search.label} />
173
- <CommandList>
174
- <CommandEmpty>{search.empty}</CommandEmpty>
175
+ <Command
176
+ class="[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground"
177
+ >
178
+ <CommandInput autofocus placeholder={search.label} />
179
+ <CommandList>
180
+ <CommandEmpty>{search.empty}</CommandEmpty>
175
181
 
176
- {
177
- searchGroups.map((group) => (
178
- <CommandGroup heading={group.label}>
179
- {group.items.map((item) => (
180
- <CommandItem
181
- href={item.href}
182
- value={`${item.group} ${item.label}`}
183
- label={item.label}
184
- keywords={[
185
- item.group,
186
- item.title,
187
- item.path,
188
- item.description,
189
- ]}
190
- >
191
- {item.label}
192
- <CommandShortcut>{item.href}</CommandShortcut>
193
- </CommandItem>
194
- ))}
195
- </CommandGroup>
196
- ))
197
- }
198
- </CommandList>
199
- </Command>
200
- </CommandDialog>
201
- </Dialog>
182
+ {
183
+ searchGroups.map((group) => (
184
+ <CommandGroup heading={group.label}>
185
+ {group.items.map((item) => (
186
+ <CommandItem
187
+ href={item.href}
188
+ value={`${item.group} ${item.label}`}
189
+ label={item.label}
190
+ keywords={[
191
+ item.group,
192
+ item.title,
193
+ item.path,
194
+ item.description,
195
+ ]}
196
+ >
197
+ {item.label}
198
+ <CommandShortcut>{item.href}</CommandShortcut>
199
+ </CommandItem>
200
+ ))}
201
+ </CommandGroup>
202
+ ))
203
+ }
204
+ </CommandList>
205
+ </Command>
206
+ </CommandDialog>
202
207
  </SidebarHeader>
203
208
  <SidebarContent
204
209
  class="[mask-image:linear-gradient(to_bottom,transparent,black_1rem,black_calc(100%_-_1rem),transparent)] [-webkit-mask-image:linear-gradient(to_bottom,transparent,black_1rem,black_calc(100%_-_1rem),transparent)] group-data-[collapsible=icon]:[mask-image:none] group-data-[collapsible=icon]:[-webkit-mask-image:none]"
@@ -268,7 +273,7 @@ try {
268
273
  <DropdownMenuContent align="start">
269
274
  <DropdownMenuGroup>
270
275
  {breadcrumb.menu.map((menuItem) => (
271
- <a
276
+ <DropdownMenuItem
272
277
  href={menuItem.href}
273
278
  aria-current={
274
279
  normalizePath(menuItem.href) ===
@@ -276,10 +281,10 @@ try {
276
281
  ? "page"
277
282
  : undefined
278
283
  }
279
- class="hover:bg-accent hover:text-accent-foreground aria-[current=page]:text-foreground flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
284
+ class="aria-[current=page]:text-foreground"
280
285
  >
281
286
  {menuItem.label}
282
- </a>
287
+ </DropdownMenuItem>
283
288
  ))}
284
289
  </DropdownMenuGroup>
285
290
  </DropdownMenuContent>
@@ -308,12 +313,9 @@ try {
308
313
  <DropdownMenuContent align="start">
309
314
  <DropdownMenuGroup>
310
315
  {breadcrumb.menu.map((item) => (
311
- <a
312
- href={item.href}
313
- class="hover:bg-accent hover:text-accent-foreground flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
314
- >
316
+ <DropdownMenuItem href={item.href}>
315
317
  {item.label}
316
- </a>
318
+ </DropdownMenuItem>
317
319
  ))}
318
320
  </DropdownMenuGroup>
319
321
  </DropdownMenuContent>
@@ -325,7 +327,7 @@ try {
325
327
  </BreadcrumbList>
326
328
  </Breadcrumb>
327
329
  </HeaderGroup>
328
- <HeaderGroup>
330
+ <HeaderGroup class="shrink-0">
329
331
  <Button
330
332
  as="a"
331
333
  href={`https://github.com/${githubRepo}`}
@@ -336,7 +338,7 @@ try {
336
338
  aria-label={`View ${githubRepo} on GitHub`}
337
339
  >
338
340
  <Icon name="github" />
339
- <span>{githubStars}</span>
341
+ <span class="hidden sm:inline">{githubStars}</span>
340
342
  </Button>
341
343
  <ThemeToggle />
342
344
  </HeaderGroup>
@@ -48,6 +48,12 @@ const serializedDefaultValue = Array.isArray(defaultValue)
48
48
  <script>
49
49
  import { create } from "@data-slot/accordion"
50
50
 
51
- create()
52
- document.addEventListener("astro:page-load", () => create())
51
+ const controllers: ReturnType<typeof create> = []
52
+ const initialize = () => controllers.push(...create())
53
+ initialize()
54
+ document.addEventListener("astro:page-load", initialize)
55
+ document.addEventListener("astro:before-swap", () => {
56
+ for (const controller of controllers) controller.destroy()
57
+ controllers.length = 0
58
+ })
53
59
  </script>
@@ -33,6 +33,12 @@ const {
33
33
  <script>
34
34
  import { create } from "@data-slot/alert-dialog"
35
35
 
36
- create()
37
- document.addEventListener("astro:page-load", () => create())
36
+ const controllers: ReturnType<typeof create> = []
37
+ const initialize = () => controllers.push(...create())
38
+ initialize()
39
+ document.addEventListener("astro:page-load", initialize)
40
+ document.addEventListener("astro:before-swap", () => {
41
+ for (const controller of controllers) controller.destroy()
42
+ controllers.length = 0
43
+ })
38
44
  </script>
@@ -35,51 +35,33 @@ const { class: className, variant, storageKey, ...props } = Astro.props
35
35
  <slot />
36
36
  </aside>
37
37
 
38
- <script is:inline>
39
- const initializedKey = "fulldevBannerInitialized"
40
-
41
- if (!document.documentElement.dataset[initializedKey]) {
42
- document.documentElement.dataset[initializedKey] = "true"
43
-
44
- const getStorageToken = (key) => `banner:${key}`
45
-
46
- const syncBanners = () => {
47
- document.querySelectorAll("[data-slot='banner']").forEach((banner) => {
48
- if (!(banner instanceof HTMLElement)) return
49
-
50
- const storageKey = banner.dataset.bannerStorageKey
51
-
52
- if (
53
- storageKey &&
54
- sessionStorage.getItem(getStorageToken(storageKey)) === "hidden"
55
- ) {
56
- banner.hidden = true
57
- }
58
- })
59
- }
60
-
61
- syncBanners()
62
-
63
- document.addEventListener("click", (event) => {
64
- const target = event.target
65
-
66
- if (!(target instanceof Element)) return
67
-
68
- const closeButton = target.closest("[data-slot='banner-close']")
69
-
70
- if (!(closeButton instanceof HTMLElement)) return
71
-
72
- const banner = closeButton.closest("[data-slot='banner']")
38
+ <script>
39
+ const getStorageToken = (key: string) => `banner:${key}`
73
40
 
41
+ const syncBanners = () => {
42
+ document.querySelectorAll("[data-slot='banner']").forEach((banner) => {
74
43
  if (!(banner instanceof HTMLElement)) return
75
-
76
- banner.hidden = true
77
-
78
44
  const storageKey = banner.dataset.bannerStorageKey
79
-
80
- if (storageKey) {
81
- sessionStorage.setItem(getStorageToken(storageKey), "hidden")
45
+ if (
46
+ storageKey &&
47
+ sessionStorage.getItem(getStorageToken(storageKey)) === "hidden"
48
+ ) {
49
+ banner.hidden = true
82
50
  }
83
51
  })
84
52
  }
53
+
54
+ syncBanners()
55
+ document.addEventListener("astro:page-load", syncBanners)
56
+
57
+ document.addEventListener("click", (event) => {
58
+ if (!(event.target instanceof Element)) return
59
+ const closeButton = event.target.closest("[data-slot='banner-close']")
60
+ const banner = closeButton?.closest("[data-slot='banner']")
61
+ if (!(banner instanceof HTMLElement)) return
62
+ banner.hidden = true
63
+ const storageKey = banner.dataset.bannerStorageKey
64
+ if (storageKey)
65
+ sessionStorage.setItem(getStorageToken(storageKey), "hidden")
66
+ })
85
67
  </script>
@@ -15,12 +15,15 @@ const {
15
15
  size,
16
16
  ...props
17
17
  } = Astro.props
18
+
19
+ const attributes: Record<string, unknown> = { ...props }
20
+ if (Tag === "button") attributes.type ??= "button"
18
21
  ---
19
22
 
20
23
  <Tag
21
24
  data-slot="button"
22
25
  class={cn(buttonVariants({ variant, size }), className)}
23
- {...props}
26
+ {...attributes}
24
27
  >
25
28
  <slot />
26
29
  </Tag>
@@ -29,6 +29,12 @@ const {
29
29
  <script>
30
30
  import { create } from "@data-slot/collapsible"
31
31
 
32
- create()
33
- document.addEventListener("astro:page-load", () => create())
32
+ const controllers: ReturnType<typeof create> = []
33
+ const initialize = () => controllers.push(...create())
34
+ initialize()
35
+ document.addEventListener("astro:page-load", initialize)
36
+ document.addEventListener("astro:before-swap", () => {
37
+ for (const controller of controllers) controller.destroy()
38
+ controllers.length = 0
39
+ })
34
40
  </script>
@@ -38,7 +38,7 @@ const {
38
38
  data-align-offset={alignOffset}
39
39
  data-avoid-collisions={avoidCollisions}
40
40
  data-collision-padding={collisionPadding}
41
- class="isolate z-50"
41
+ class="isolate z-[1000]"
42
42
  >
43
43
  <div
44
44
  data-slot="combobox-content"
@@ -67,22 +67,14 @@ const {
67
67
  </div>
68
68
 
69
69
  <script>
70
- import { createCombobox } from "@data-slot/combobox"
70
+ import { create } from "@data-slot/combobox"
71
71
 
72
- const initCombobox = () => {
73
- document.querySelectorAll('[data-slot="combobox"]').forEach((root) => {
74
- if (
75
- !root.querySelector('[data-slot="combobox-input"]') ||
76
- !root.querySelector('[data-slot="combobox-content"]')
77
- ) {
78
- return
79
- }
80
-
81
- createCombobox(root as HTMLElement)
82
- })
83
- }
84
-
85
- initCombobox()
86
-
87
- document.addEventListener("astro:page-load", initCombobox)
72
+ const controllers: ReturnType<typeof create> = []
73
+ const initialize = () => controllers.push(...create())
74
+ initialize()
75
+ document.addEventListener("astro:page-load", initialize)
76
+ document.addEventListener("astro:before-swap", () => {
77
+ for (const controller of controllers) controller.destroy()
78
+ controllers.length = 0
79
+ })
88
80
  </script>
@@ -25,6 +25,7 @@ const {
25
25
  ---
26
26
 
27
27
  <Dialog {...props}>
28
+ <slot name="trigger" />
28
29
  <DialogContent
29
30
  class:list={[
30
31
  "top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0",
@@ -46,31 +46,46 @@ const {
46
46
 
47
47
  <script>
48
48
  import { create } from "@data-slot/command"
49
+ import { getParts } from "@data-slot/core"
49
50
 
50
- const initCommand = () => {
51
- create()
51
+ const controllers: ReturnType<typeof create> = []
52
+ const initialize = () => controllers.push(...create())
53
+ initialize()
54
+ document.addEventListener("astro:page-load", initialize)
55
+ document.addEventListener("astro:before-swap", () => {
56
+ for (const controller of controllers) controller.destroy()
57
+ controllers.length = 0
58
+ })
52
59
 
53
- document.querySelectorAll("[data-slot=command]").forEach((command) => {
54
- if (!(command instanceof HTMLElement)) return
55
- if (command.dataset.linkNavigation === "true") return
56
-
57
- command.dataset.linkNavigation = "true"
58
- command.addEventListener("command:select", (event) => {
59
- const { detail } = event as CustomEvent<{ value?: unknown }>
60
- const value = detail?.value
61
- if (typeof value !== "string") return
62
-
63
- const item = Array.from(
64
- command.querySelectorAll("[data-slot=command-item][href]")
65
- ).find((item) => item.getAttribute("data-value") === value)
66
- const href = item?.getAttribute("href")
67
-
68
- if (href) window.location.href = href
69
- })
70
- })
71
- }
72
-
73
- initCommand()
74
-
75
- document.addEventListener("astro:page-load", initCommand)
60
+ // Data Slot selection does not activate links. Preserve native navigation.
61
+ document.addEventListener(
62
+ "keydown",
63
+ (event) => {
64
+ if (
65
+ event.key !== "Enter" ||
66
+ event.defaultPrevented ||
67
+ event.isComposing ||
68
+ event.keyCode === 229
69
+ )
70
+ return
71
+ const input = event.target
72
+ if (
73
+ !(input instanceof HTMLInputElement) ||
74
+ input.dataset.slot !== "command-input"
75
+ )
76
+ return
77
+ const root = input.closest('[data-slot="command"]')
78
+ if (!root) return
79
+ const item = getParts<HTMLAnchorElement>(root, "command-item").find(
80
+ (item) =>
81
+ item.hasAttribute("data-selected") &&
82
+ item.hasAttribute("href") &&
83
+ item.getAttribute("aria-disabled") !== "true"
84
+ )
85
+ if (!item) return
86
+ event.preventDefault()
87
+ item.click()
88
+ },
89
+ true
90
+ )
76
91
  </script>
@@ -36,6 +36,12 @@ const {
36
36
  <script>
37
37
  import { create } from "@data-slot/dialog"
38
38
 
39
- create()
40
- document.addEventListener("astro:page-load", () => create())
39
+ const controllers: ReturnType<typeof create> = []
40
+ const initialize = () => controllers.push(...create())
41
+ initialize()
42
+ document.addEventListener("astro:page-load", initialize)
43
+ document.addEventListener("astro:before-swap", () => {
44
+ for (const controller of controllers) controller.destroy()
45
+ controllers.length = 0
46
+ })
41
47
  </script>
@@ -35,7 +35,7 @@ const {
35
35
  data-align-offset={alignOffset}
36
36
  data-avoid-collisions={avoidCollisions}
37
37
  data-collision-padding={collisionPadding}
38
- class="isolate z-50 outline-none"
38
+ class="isolate z-[1000] outline-none"
39
39
  >
40
40
  <div
41
41
  data-slot="dropdown-menu-content"
@@ -3,13 +3,16 @@ import type { HTMLAttributes } from "astro/types"
3
3
 
4
4
  import { cn } from "@/lib/utils"
5
5
 
6
- type Props = HTMLAttributes<"button"> & {
7
- disabled?: boolean
8
- variant?: "default" | "destructive"
9
- inset?: boolean
10
- }
6
+ type Props = HTMLAttributes<"button"> &
7
+ HTMLAttributes<"a"> & {
8
+ as?: "button" | "a"
9
+ disabled?: boolean
10
+ variant?: "default" | "destructive"
11
+ inset?: boolean
12
+ }
11
13
 
12
14
  const {
15
+ as: Tag = "href" in Astro.props ? "a" : "button",
13
16
  class: className,
14
17
  type = "button",
15
18
  disabled,
@@ -19,13 +22,13 @@ const {
19
22
  } = Astro.props
20
23
  ---
21
24
 
22
- <button
25
+ <Tag
23
26
  data-slot="dropdown-menu-item"
24
27
  data-disabled={disabled}
25
28
  data-variant={variant}
26
29
  data-inset={inset}
27
- disabled={disabled ? true : undefined}
28
- type={type}
30
+ disabled={Tag === "button" && disabled ? true : undefined}
31
+ type={Tag === "button" ? type : undefined}
29
32
  class={cn(
30
33
  "group/dropdown-menu-item data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground not-data-[variant=destructive]:data-[highlighted]:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
31
34
  className
@@ -33,4 +36,32 @@ const {
33
36
  {...props}
34
37
  >
35
38
  <slot />
36
- </button>
39
+ </Tag>
40
+
41
+ <script>
42
+ // Data Slot owns selection; links also need native keyboard activation.
43
+ document.addEventListener(
44
+ "keydown",
45
+ (event) => {
46
+ if (
47
+ event.defaultPrevented ||
48
+ event.isComposing ||
49
+ !["Enter", " "].includes(event.key)
50
+ )
51
+ return
52
+ const item =
53
+ event.target instanceof Element
54
+ ? event.target.closest('[data-slot="dropdown-menu-item"][href]')
55
+ : null
56
+ if (
57
+ !(item instanceof HTMLAnchorElement) ||
58
+ item.getAttribute("aria-disabled") === "true"
59
+ )
60
+ return
61
+ event.preventDefault()
62
+ event.stopPropagation()
63
+ item.click()
64
+ },
65
+ true
66
+ )
67
+ </script>
@@ -67,6 +67,12 @@ const defaultValuesAttr =
67
67
  <script>
68
68
  import { create } from "@data-slot/dropdown-menu"
69
69
 
70
- create()
71
- document.addEventListener("astro:page-load", () => create())
70
+ const controllers: ReturnType<typeof create> = []
71
+ const initialize = () => controllers.push(...create())
72
+ initialize()
73
+ document.addEventListener("astro:page-load", initialize)
74
+ document.addEventListener("astro:before-swap", () => {
75
+ for (const controller of controllers) controller.destroy()
76
+ controllers.length = 0
77
+ })
72
78
  </script>