fulldev-ui 0.9.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Fulldev UI
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#178](https://github.com/fulldotdev/ui/pull/178) [`e2c8f47`](https://github.com/fulldotdev/ui/commit/e2c8f476f3333d09eb21ff4fa251d84326664be3) Thanks [@silveltman](https://github.com/silveltman)! - Fix vertical alignment in the header block.
8
+
3
9
  ## 0.9.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fulldev-ui",
3
3
  "type": "module",
4
- "version": "0.9.0",
4
+ "version": "0.9.1",
5
5
  "description": "Open-source Astro UI components and blocks",
6
6
  "homepage": "https://ui.full.dev",
7
7
  "bugs": "https://github.com/fulldotdev/ui/issues",
@@ -18,6 +18,8 @@
18
18
  "@fulldev/content-2",
19
19
  "@fulldev/content-3",
20
20
  "@fulldev/content-4",
21
+ "@fulldev/content-5",
22
+ "@fulldev/content-6",
21
23
  "@fulldev/cta-1",
22
24
  "@fulldev/cta-2",
23
25
  "@fulldev/cta-3",
@@ -20,6 +20,7 @@
20
20
  "@fulldev/empty",
21
21
  "@fulldev/field",
22
22
  "@fulldev/header",
23
+ "@fulldev/hover-card",
23
24
  "@fulldev/input",
24
25
  "@fulldev/input-group",
25
26
  "@fulldev/item",
@@ -28,6 +29,7 @@
28
29
  "@fulldev/marquee",
29
30
  "@fulldev/native-select",
30
31
  "@fulldev/navigation-menu",
32
+ "@fulldev/popover",
31
33
  "@fulldev/price",
32
34
  "@fulldev/radio-group",
33
35
  "@fulldev/rating",
@@ -44,7 +46,9 @@
44
46
  "@fulldev/tabs",
45
47
  "@fulldev/textarea",
46
48
  "@fulldev/theme-toggle",
49
+ "@fulldev/tile",
47
50
  "@fulldev/toc",
51
+ "@fulldev/toggle",
48
52
  "@fulldev/tooltip",
49
53
  "@fulldev/typography",
50
54
  "@fulldev/video",
@@ -12,7 +12,7 @@
12
12
  "files": [
13
13
  {
14
14
  "path": "src/components/blocks/header-1.astro",
15
- "content": "---\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Header, HeaderContainer, HeaderGroup } from \"@/components/ui/header\"\nimport { Icon } from \"@/components/ui/icon\"\nimport { Logo, LogoImage, LogoText } from \"@/components/ui/logo\"\nimport {\n NavigationMenu,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n} from \"@/components/ui/navigation-menu\"\nimport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\"\n\ntype Props = {\n class?: string\n logo: {\n label: string\n href: string\n src?: any\n srcLight?: any\n srcDark?: any\n alt?: string\n }\n navigation: {\n label: string\n href: string\n links?: {\n label: string\n href: string\n }[]\n }[]\n buttons: {\n label: string\n href: string\n icon?: string\n variant?: \"default\" | \"outline\" | \"secondary\" | \"ghost\"\n }[]\n}\n\nconst { class: className, logo, navigation, buttons } = Astro.props\n---\n\n<Header class={cn(\"\", className)}>\n <HeaderContainer class=\"justify-between gap-4\">\n <HeaderGroup>\n <Logo href={logo.href}>\n <LogoImage\n src={logo.src}\n srcLight={logo.srcLight}\n srcDark={logo.srcDark}\n alt={logo.alt}\n />\n {logo.label && <LogoText>{logo.label}</LogoText>}\n </Logo>\n </HeaderGroup>\n <NavigationMenu class=\"hidden flex-none justify-start lg:flex\">\n <NavigationMenuList class=\"justify-start\">\n {\n navigation.map((item, index) => (\n <NavigationMenuItem value={`nav-${index}`}>\n <NavigationMenuLink href={item.href}>\n {item.label}\n </NavigationMenuLink>\n </NavigationMenuItem>\n ))\n }\n </NavigationMenuList>\n </NavigationMenu>\n <HeaderGroup class=\"ml-auto hidden flex-1 justify-end lg:flex\">\n <slot />\n {\n buttons.map(({ label, icon, ...button }) => (\n <Button\n as=\"a\"\n size={icon && !label ? \"icon-sm\" : \"sm\"}\n aria-label={label}\n target={button.href.startsWith(\"http\") ? \"_blank\" : undefined}\n rel={button.href.startsWith(\"http\") ? \"noreferrer\" : undefined}\n {...button}\n >\n {icon && <Icon name={icon} />}\n {label}\n </Button>\n ))\n }\n </HeaderGroup>\n <Sheet class=\"lg:hidden\">\n <SheetTrigger\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"ml-auto\"\n aria-label=\"Open menu\"\n >\n <Icon name=\"menu\" />\n </SheetTrigger>\n <SheetContent side=\"right\" class=\"p-0\">\n <SheetHeader class=\"sr-only\">\n <SheetTitle>Menu</SheetTitle>\n <SheetDescription>Site navigation and actions.</SheetDescription>\n </SheetHeader>\n <div class=\"flex h-full flex-col gap-6 px-4 py-12\">\n <nav aria-label=\"Main navigation\" class=\"flex flex-col gap-1\">\n {\n navigation.map((item) => (\n <SheetClose variant=\"ghost\" class=\"justify-start\">\n {item.label}\n </SheetClose>\n ))\n }\n </nav>\n <div class=\"mt-auto flex flex-col gap-2\">\n <slot />\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </HeaderContainer>\n</Header>\n",
15
+ "content": "---\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Header, HeaderContainer, HeaderGroup } from \"@/components/ui/header\"\nimport { Icon } from \"@/components/ui/icon\"\nimport { Logo, LogoImage, LogoText } from \"@/components/ui/logo\"\nimport {\n NavigationMenu,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n} from \"@/components/ui/navigation-menu\"\nimport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\"\n\ntype Props = {\n class?: string\n logo: {\n label: string\n href: string\n src?: any\n srcLight?: any\n srcDark?: any\n alt?: string\n }\n navigation: {\n label: string\n href: string\n links?: {\n label: string\n href: string\n }[]\n }[]\n buttons: {\n label: string\n href: string\n icon?: string\n variant?: \"default\" | \"outline\" | \"secondary\" | \"ghost\"\n }[]\n}\n\nconst { class: className, logo, navigation, buttons } = Astro.props\n---\n\n<Header class={cn(\"\", className)}>\n <HeaderContainer class=\"items-center justify-between gap-4\">\n <HeaderGroup>\n <Logo href={logo.href}>\n <LogoImage\n src={logo.src}\n srcLight={logo.srcLight}\n srcDark={logo.srcDark}\n alt={logo.alt}\n />\n {logo.label && <LogoText>{logo.label}</LogoText>}\n </Logo>\n </HeaderGroup>\n <NavigationMenu class=\"hidden flex-none justify-start lg:flex\">\n <NavigationMenuList class=\"justify-start\">\n {\n navigation.map((item, index) => (\n <NavigationMenuItem value={`nav-${index}`}>\n <NavigationMenuLink href={item.href}>\n {item.label}\n </NavigationMenuLink>\n </NavigationMenuItem>\n ))\n }\n </NavigationMenuList>\n </NavigationMenu>\n <HeaderGroup class=\"ml-auto hidden flex-1 justify-end lg:flex\">\n <slot />\n {\n buttons.map(({ label, icon, ...button }) => (\n <Button\n as=\"a\"\n size={icon && !label ? \"icon-sm\" : \"sm\"}\n aria-label={label}\n target={button.href.startsWith(\"http\") ? \"_blank\" : undefined}\n rel={button.href.startsWith(\"http\") ? \"noreferrer\" : undefined}\n {...button}\n >\n {icon && <Icon name={icon} />}\n {label}\n </Button>\n ))\n }\n </HeaderGroup>\n <Sheet class=\"lg:hidden\">\n <SheetTrigger\n variant=\"ghost\"\n size=\"icon-sm\"\n class=\"ml-auto\"\n aria-label=\"Open menu\"\n >\n <Icon name=\"menu\" />\n </SheetTrigger>\n <SheetContent side=\"right\" class=\"p-0\">\n <SheetHeader class=\"sr-only\">\n <SheetTitle>Menu</SheetTitle>\n <SheetDescription>Site navigation and actions.</SheetDescription>\n </SheetHeader>\n <div class=\"flex h-full flex-col gap-6 px-4 py-12\">\n <nav aria-label=\"Main navigation\" class=\"flex flex-col gap-1\">\n {\n navigation.map((item) => (\n <SheetClose variant=\"ghost\" class=\"justify-start\">\n {item.label}\n </SheetClose>\n ))\n }\n </nav>\n <div class=\"mt-auto flex flex-col gap-2\">\n <slot />\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </HeaderContainer>\n</Header>\n",
16
16
  "type": "registry:block"
17
17
  }
18
18
  ],
@@ -1687,6 +1687,7 @@
1687
1687
  "@fulldev/empty",
1688
1688
  "@fulldev/field",
1689
1689
  "@fulldev/header",
1690
+ "@fulldev/hover-card",
1690
1691
  "@fulldev/input",
1691
1692
  "@fulldev/input-group",
1692
1693
  "@fulldev/item",
@@ -1695,6 +1696,7 @@
1695
1696
  "@fulldev/marquee",
1696
1697
  "@fulldev/native-select",
1697
1698
  "@fulldev/navigation-menu",
1699
+ "@fulldev/popover",
1698
1700
  "@fulldev/price",
1699
1701
  "@fulldev/radio-group",
1700
1702
  "@fulldev/rating",
@@ -1711,7 +1713,9 @@
1711
1713
  "@fulldev/tabs",
1712
1714
  "@fulldev/textarea",
1713
1715
  "@fulldev/theme-toggle",
1716
+ "@fulldev/tile",
1714
1717
  "@fulldev/toc",
1718
+ "@fulldev/toggle",
1715
1719
  "@fulldev/tooltip",
1716
1720
  "@fulldev/typography",
1717
1721
  "@fulldev/video",
@@ -1738,6 +1742,8 @@
1738
1742
  "@fulldev/content-2",
1739
1743
  "@fulldev/content-3",
1740
1744
  "@fulldev/content-4",
1745
+ "@fulldev/content-5",
1746
+ "@fulldev/content-6",
1741
1747
  "@fulldev/cta-1",
1742
1748
  "@fulldev/cta-2",
1743
1749
  "@fulldev/cta-3",
@@ -20,7 +20,7 @@
20
20
  "files": [
21
21
  {
22
22
  "path": "src/components/blocks/sidebar-1.astro",
23
- "content": "---\nimport { cn } from \"@/lib/utils\"\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandShortcut,\n} from \"@/components/ui/command\"\nimport { Dialog, DialogTrigger } from \"@/components/ui/dialog\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Header, HeaderContainer, HeaderGroup } from \"@/components/ui/header\"\nimport { Icon } from \"@/components/ui/icon\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"@/components/ui/input-group\"\nimport { Kbd } from \"@/components/ui/kbd\"\nimport { LogoImage, LogoText } from \"@/components/ui/logo\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/components/ui/sidebar\"\nimport { ThemeToggle } from \"@/components/ui/theme-toggle\"\n\ntype Props = {\n class?: string\n logo: {\n label: string\n href: string\n src?: any\n srcLight?: any\n srcDark?: any\n alt?: string\n }\n search: {\n label: string\n empty: string\n items: {\n label: string\n href: string\n title: string\n path: string\n description: string\n group: string\n }[]\n }\n breadcrumb: {\n items: {\n label: string\n href: string\n }[]\n menu: {\n label: string\n href: string\n }[]\n }\n navigation: {\n label: string\n href: string\n links?: {\n label: string\n href: string\n }[]\n }[]\n githubRepo: string\n}\n\nconst {\n class: className,\n logo,\n search,\n breadcrumb,\n navigation,\n githubRepo,\n}: Props = Astro.props\n\nconst normalizePath = (path: string) => {\n if (path === \"/\") return path\n\n return path.replace(/\\/$/, \"\")\n}\nconst currentPath = normalizePath(Astro.url.pathname)\nconst searchGroups = search.items.reduce<\n { label: string; items: typeof search.items }[]\n>((groups, item) => {\n const group = groups.find((entry) => entry.label === item.group)\n\n if (group) {\n group.items.push(item)\n } else {\n groups.push({ label: item.group, items: [item] })\n }\n\n return groups\n}, [])\n\nlet githubStars = \"Stars\"\n\ntry {\n const response = await fetch(`https://api.github.com/repos/${githubRepo}`)\n const data = await response.json()\n\n if (response.ok && typeof data.stargazers_count === \"number\") {\n githubStars = new Intl.NumberFormat().format(data.stargazers_count)\n }\n} catch {\n // Keep the fallback label when GitHub is unavailable.\n}\n---\n\n<SidebarProvider>\n <Sidebar class={cn(\"\", className)} collapsible=\"offcanvas\" variant=\"inset\">\n <SidebarHeader class=\"flex-col\">\n <SidebarMenuButton size=\"lg\" variant=\"ghost\" href={logo.href}>\n <LogoImage\n src={logo.src}\n srcLight={logo.srcLight}\n srcDark={logo.srcDark}\n alt={logo.alt}\n />\n <LogoText class=\"font-semibold\">{logo.label}</LogoText>\n </SidebarMenuButton>\n <Dialog class=\"w-full\" data-search-dialog>\n <DialogTrigger\n as=\"div\"\n role=\"button\"\n tabindex=\"0\"\n class=\"h-auto w-full cursor-pointer border-0 bg-transparent p-0 shadow-none hover:bg-transparent\"\n aria-keyshortcuts=\"Meta+K Control+K\"\n >\n <InputGroup class=\"h-8 shadow-none\">\n <InputGroupInput\n placeholder={search.label}\n readonly\n tabindex=\"-1\"\n aria-label={search.label}\n class=\"cursor-pointer\"\n />\n <InputGroupAddon>\n <Icon name=\"search\" />\n </InputGroupAddon>\n <InputGroupAddon align=\"inline-end\">\n <Kbd data-search-modifier>⌘</Kbd>\n <Kbd>K</Kbd>\n </InputGroupAddon>\n </InputGroup>\n </DialogTrigger>\n\n <CommandDialog title={search.label} description={search.label}>\n <Command\n class=\"[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground\"\n >\n <CommandInput placeholder={search.label} />\n <CommandList>\n <CommandEmpty>{search.empty}</CommandEmpty>\n\n {\n searchGroups.map((group) => (\n <CommandGroup heading={group.label}>\n {group.items.map((item) => (\n <CommandItem\n href={item.href}\n value={item.path}\n label={item.label}\n keywords={[item.title, item.path]}\n >\n {item.label}\n <CommandShortcut>{item.href}</CommandShortcut>\n </CommandItem>\n ))}\n </CommandGroup>\n ))\n }\n </CommandList>\n </Command>\n </CommandDialog>\n </Dialog>\n </SidebarHeader>\n <SidebarContent\n class=\"mask-[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-none group-data-[collapsible=icon]:[-webkit-mask-image:none]\"\n >\n {\n navigation.map((group) => (\n <SidebarGroup>\n <SidebarGroupLabel>{group.label}</SidebarGroupLabel>\n <SidebarMenu>\n {(group.links ?? [group]).map((item) => (\n <SidebarMenuItem>\n <SidebarMenuButton\n href={item.href}\n isActive={normalizePath(item.href) === currentPath}\n >\n <span>{item.label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n ))\n }\n </SidebarContent>\n </Sidebar>\n <SidebarInset>\n <Header class=\"h-16\">\n <HeaderContainer class=\"max-w-none justify-between\">\n <HeaderGroup>\n <SidebarTrigger />\n <Separator orientation=\"vertical\" class=\"my-auto mr-2 h-4\" />\n <Breadcrumb>\n <BreadcrumbList>\n {\n breadcrumb.items.map((item, index) => {\n const isLast = index === breadcrumb.items.length - 1\n const isHome = item.href === \"/\"\n const hasMenu =\n !isHome &&\n breadcrumb.menu.some(\n (menuItem) =>\n normalizePath(menuItem.href) ===\n normalizePath(item.href)\n )\n\n return (\n <>\n {index > 0 && (\n <BreadcrumbSeparator>/</BreadcrumbSeparator>\n )}\n <BreadcrumbItem>\n {hasMenu ? (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${item.label} pages`}\n aria-current={isLast ? \"page\" : undefined}\n class={cn(\n \"hover:text-foreground inline-flex items-center gap-1 transition-colors\",\n isLast && \"text-foreground\"\n )}\n >\n {item.label}\n <Icon\n name=\"chevron-down\"\n class=\"size-3.5 opacity-70\"\n aria-hidden=\"true\"\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n {breadcrumb.menu.map((menuItem) => (\n <a\n href={menuItem.href}\n aria-current={\n normalizePath(menuItem.href) ===\n currentPath\n ? \"page\"\n : undefined\n }\n 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\"\n >\n {menuItem.label}\n </a>\n ))}\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : isLast ? (\n <BreadcrumbPage>{item.label}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink href={item.href}>\n {item.label}\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n </>\n )\n })\n }\n {\n breadcrumb.menu.length > 0 && currentPath === \"/\" && (\n <>\n <BreadcrumbSeparator>/</BreadcrumbSeparator>\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label=\"Open page menu\"\n class=\"hover:text-foreground inline-flex items-center leading-none transition-colors\"\n >\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n {breadcrumb.menu.map((item) => (\n <a\n href={item.href}\n 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\"\n >\n {item.label}\n </a>\n ))}\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n </>\n )\n }\n </BreadcrumbList>\n </Breadcrumb>\n </HeaderGroup>\n <HeaderGroup>\n <Button\n as=\"a\"\n href={`https://github.com/${githubRepo}`}\n size=\"sm\"\n variant=\"ghost\"\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label={`View ${githubRepo} on GitHub, ${githubStars}`}\n >\n <Icon name=\"github\" />\n <span>{githubStars}</span>\n </Button>\n <ThemeToggle />\n </HeaderGroup>\n </HeaderContainer>\n </Header>\n <slot />\n </SidebarInset>\n</SidebarProvider>\n\n<script>\n const isMac = /Mac|iPhone|iPad|iPod/i.test(\n navigator.platform || navigator.userAgent\n )\n\n document.querySelectorAll(\"[data-search-modifier]\").forEach((shortcut) => {\n shortcut.textContent = isMac ? \"⌘\" : \"Ctrl\"\n })\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key.toLowerCase() !== \"k\") return\n if (event.shiftKey || event.altKey) return\n\n const dialog = document.querySelector(\"[data-search-dialog]\")\n const isSearchShortcut = isMac\n ? event.metaKey && !event.ctrlKey\n : event.ctrlKey && !event.metaKey\n\n if (!dialog || dialog.hasAttribute(\"data-open\") || !isSearchShortcut) return\n\n event.preventDefault()\n dialog.dispatchEvent(\n new CustomEvent(\"dialog:set\", { detail: { open: true } })\n )\n })\n</script>\n",
23
+ "content": "---\nimport { cn } from \"@/lib/utils\"\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandShortcut,\n} from \"@/components/ui/command\"\nimport { Dialog, DialogTrigger } from \"@/components/ui/dialog\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Header, HeaderContainer, HeaderGroup } from \"@/components/ui/header\"\nimport { Icon } from \"@/components/ui/icon\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"@/components/ui/input-group\"\nimport { Kbd } from \"@/components/ui/kbd\"\nimport { LogoImage, LogoText } from \"@/components/ui/logo\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/components/ui/sidebar\"\nimport { ThemeToggle } from \"@/components/ui/theme-toggle\"\n\ntype Props = {\n class?: string\n logo: {\n label: string\n href: string\n src?: any\n srcLight?: any\n srcDark?: any\n alt?: string\n }\n search: {\n label: string\n empty: string\n items: {\n label: string\n href: string\n title: string\n path: string\n description: string\n group: string\n }[]\n }\n breadcrumb: {\n items: {\n label: string\n href: string\n }[]\n menu: {\n label: string\n href: string\n }[]\n }\n navigation: {\n label: string\n href: string\n links?: {\n label: string\n href: string\n }[]\n }[]\n githubRepo: string\n}\n\nconst {\n class: className,\n logo,\n search,\n breadcrumb,\n navigation,\n githubRepo,\n}: Props = Astro.props\n\nconst normalizePath = (path: string) => {\n if (path === \"/\") return path\n\n return path.replace(/\\/$/, \"\")\n}\nconst currentPath = normalizePath(Astro.url.pathname)\nconst searchGroups = search.items.reduce<\n { label: string; items: typeof search.items }[]\n>((groups, item) => {\n const group = groups.find((entry) => entry.label === item.group)\n\n if (group) {\n group.items.push(item)\n } else {\n groups.push({ label: item.group, items: [item] })\n }\n\n return groups\n}, [])\n\nconst githubStars = \"560+\"\n---\n\n<SidebarProvider>\n <Sidebar class={cn(\"\", className)} collapsible=\"offcanvas\" variant=\"inset\">\n <SidebarHeader class=\"flex-col\">\n <SidebarMenuButton size=\"lg\" variant=\"ghost\" href={logo.href}>\n <LogoImage\n src={logo.src}\n srcLight={logo.srcLight}\n srcDark={logo.srcDark}\n alt={logo.alt}\n />\n <LogoText class=\"font-semibold\">{logo.label}</LogoText>\n </SidebarMenuButton>\n <Dialog class=\"w-full\" data-search-dialog>\n <DialogTrigger\n as=\"div\"\n role=\"button\"\n tabindex=\"0\"\n class=\"h-auto w-full cursor-pointer border-0 bg-transparent p-0 shadow-none hover:bg-transparent\"\n aria-keyshortcuts=\"Meta+K Control+K\"\n >\n <InputGroup class=\"h-8 shadow-none\">\n <InputGroupInput\n placeholder={search.label}\n readonly\n tabindex=\"-1\"\n aria-label={search.label}\n class=\"cursor-pointer\"\n />\n <InputGroupAddon>\n <Icon name=\"search\" />\n </InputGroupAddon>\n <InputGroupAddon align=\"inline-end\">\n <Kbd data-search-modifier>⌘</Kbd>\n <Kbd>K</Kbd>\n </InputGroupAddon>\n </InputGroup>\n </DialogTrigger>\n\n <CommandDialog title={search.label} description={search.label}>\n <Command\n class=\"[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground\"\n >\n <CommandInput placeholder={search.label} />\n <CommandList>\n <CommandEmpty>{search.empty}</CommandEmpty>\n\n {\n searchGroups.map((group) => (\n <CommandGroup heading={group.label}>\n {group.items.map((item) => (\n <CommandItem\n href={item.href}\n value={item.path}\n label={item.label}\n keywords={[item.title, item.path]}\n >\n {item.label}\n <CommandShortcut>{item.href}</CommandShortcut>\n </CommandItem>\n ))}\n </CommandGroup>\n ))\n }\n </CommandList>\n </Command>\n </CommandDialog>\n </Dialog>\n </SidebarHeader>\n <SidebarContent\n class=\"mask-[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-none group-data-[collapsible=icon]:[-webkit-mask-image:none]\"\n >\n {\n navigation.map((group) => (\n <SidebarGroup>\n <SidebarGroupLabel>{group.label}</SidebarGroupLabel>\n <SidebarMenu>\n {(group.links ?? [group]).map((item) => (\n <SidebarMenuItem>\n <SidebarMenuButton\n href={item.href}\n isActive={normalizePath(item.href) === currentPath}\n >\n <span>{item.label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n ))\n }\n </SidebarContent>\n </Sidebar>\n <SidebarInset>\n <Header class=\"h-16\">\n <HeaderContainer class=\"max-w-none justify-between\">\n <HeaderGroup class=\"flex-1 md:flex-none\">\n <SidebarTrigger />\n <Separator orientation=\"vertical\" class=\"my-auto mr-2 h-4\" />\n <Dialog class=\"block min-w-0 flex-1 md:hidden\" data-search-dialog>\n <DialogTrigger\n as=\"div\"\n role=\"button\"\n tabindex=\"0\"\n class=\"h-auto w-full cursor-pointer border-0 bg-transparent p-0 shadow-none hover:bg-transparent\"\n aria-keyshortcuts=\"Meta+K Control+K\"\n >\n <InputGroup class=\"h-8 shadow-none\">\n <InputGroupInput\n placeholder={search.label}\n readonly\n tabindex=\"-1\"\n aria-label={search.label}\n class=\"cursor-pointer\"\n />\n <InputGroupAddon>\n <Icon name=\"search\" />\n </InputGroupAddon>\n </InputGroup>\n </DialogTrigger>\n\n <CommandDialog title={search.label} description={search.label}>\n <Command\n class=\"[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground\"\n >\n <CommandInput placeholder={search.label} />\n <CommandList>\n <CommandEmpty>{search.empty}</CommandEmpty>\n\n {\n searchGroups.map((group) => (\n <CommandGroup heading={group.label}>\n {group.items.map((item) => (\n <CommandItem\n href={item.href}\n value={item.path}\n label={item.label}\n keywords={[item.title, item.path]}\n >\n {item.label}\n <CommandShortcut>{item.href}</CommandShortcut>\n </CommandItem>\n ))}\n </CommandGroup>\n ))\n }\n </CommandList>\n </Command>\n </CommandDialog>\n </Dialog>\n <Breadcrumb class=\"hidden md:block\">\n <BreadcrumbList>\n {\n breadcrumb.items.map((item, index) => {\n const isLast = index === breadcrumb.items.length - 1\n const isHome = item.href === \"/\"\n const hasMenu =\n !isHome &&\n breadcrumb.menu.some(\n (menuItem) =>\n normalizePath(menuItem.href) ===\n normalizePath(item.href)\n )\n\n return (\n <>\n {index > 0 && (\n <BreadcrumbSeparator>/</BreadcrumbSeparator>\n )}\n <BreadcrumbItem>\n {hasMenu ? (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${item.label} pages`}\n aria-current={isLast ? \"page\" : undefined}\n class={cn(\n \"hover:text-foreground inline-flex items-center gap-1 transition-colors\",\n isLast && \"text-foreground\"\n )}\n >\n {item.label}\n <Icon\n name=\"chevron-down\"\n class=\"size-3.5 opacity-70\"\n aria-hidden=\"true\"\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n {breadcrumb.menu.map((menuItem) => (\n <a\n href={menuItem.href}\n aria-current={\n normalizePath(menuItem.href) ===\n currentPath\n ? \"page\"\n : undefined\n }\n 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\"\n >\n {menuItem.label}\n </a>\n ))}\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : isLast ? (\n <BreadcrumbPage>{item.label}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink href={item.href}>\n {item.label}\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n </>\n )\n })\n }\n {\n breadcrumb.menu.length > 0 && currentPath === \"/\" && (\n <>\n <BreadcrumbSeparator>/</BreadcrumbSeparator>\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label=\"Open page menu\"\n class=\"hover:text-foreground inline-flex items-center leading-none transition-colors\"\n >\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n {breadcrumb.menu.map((item) => (\n <a\n href={item.href}\n 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\"\n >\n {item.label}\n </a>\n ))}\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n </>\n )\n }\n </BreadcrumbList>\n </Breadcrumb>\n </HeaderGroup>\n <HeaderGroup>\n <Button\n as=\"a\"\n href={`https://github.com/${githubRepo}`}\n size=\"sm\"\n variant=\"ghost\"\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label={`View ${githubRepo} on GitHub, ${githubStars}`}\n >\n <Icon name=\"github\" />\n <span>{githubStars}</span>\n </Button>\n <ThemeToggle />\n </HeaderGroup>\n </HeaderContainer>\n </Header>\n <slot />\n </SidebarInset>\n</SidebarProvider>\n\n<script>\n const isMac = /Mac|iPhone|iPad|iPod/i.test(\n navigator.platform || navigator.userAgent\n )\n\n document.querySelectorAll(\"[data-search-modifier]\").forEach((shortcut) => {\n shortcut.textContent = isMac ? \"⌘\" : \"Ctrl\"\n })\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key.toLowerCase() !== \"k\") return\n if (event.shiftKey || event.altKey) return\n\n const dialogs = Array.from(\n document.querySelectorAll(\"[data-search-dialog]\")\n )\n const dialog =\n dialogs.find((dialog) => dialog.getClientRects().length > 0) ?? dialogs[0]\n const isSearchShortcut = isMac\n ? event.metaKey && !event.ctrlKey\n : event.ctrlKey && !event.metaKey\n\n if (!dialog || dialog.hasAttribute(\"data-open\") || !isSearchShortcut) return\n\n event.preventDefault()\n dialog.dispatchEvent(\n new CustomEvent(\"dialog:set\", { detail: { open: true } })\n )\n })\n</script>\n",
24
24
  "type": "registry:block"
25
25
  }
26
26
  ],
@@ -14,7 +14,7 @@
14
14
  "files": [
15
15
  {
16
16
  "path": "src/components/ui/sidebar/sidebar-provider.astro",
17
- "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\n\ntype Props = HTMLAttributes<\"div\"> & {\n defaultOpen?: boolean\n open?: boolean\n}\n\nconst {\n class: className,\n style,\n defaultOpen = true,\n open,\n ...props\n} = Astro.props\n\nconst resolvedStyle = [\n `--sidebar-width: ${SIDEBAR_WIDTH}`,\n `--sidebar-width-mobile: ${SIDEBAR_WIDTH_MOBILE}`,\n `--sidebar-width-icon: ${SIDEBAR_WIDTH_ICON}`,\n typeof style === \"string\" ? style : undefined,\n]\n .filter(Boolean)\n .join(\"; \")\n---\n\n<div\n data-slot=\"sidebar-wrapper\"\n data-default-open={defaultOpen ? \"true\" : \"false\"}\n data-open={open === undefined ? undefined : open ? \"true\" : \"false\"}\n style={resolvedStyle}\n class={cn(\n \"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full\",\n className\n )}\n {...props}\n>\n <slot />\n <script is:inline>\n ;(() => {\n window.__fulldevSyncSidebarProvider ??= (provider, open) => {\n const state = open ? \"expanded\" : \"collapsed\"\n const sidebar = provider.querySelector(\n \"[data-slot='sidebar']:not([data-mobile='true'])\"\n )\n const collapsible = sidebar?.dataset.sidebarCollapsible ?? \"offcanvas\"\n\n provider.dataset.open = open ? \"true\" : \"false\"\n provider.dataset.state = state\n\n if (sidebar) {\n sidebar.dataset.state = collapsible === \"none\" ? \"expanded\" : state\n sidebar.dataset.collapsible =\n collapsible === \"none\" ? \"none\" : open ? \"\" : collapsible\n }\n\n provider\n .querySelectorAll(\"[data-sidebar='trigger'], [data-sidebar='rail']\")\n .forEach((control) => {\n control.setAttribute(\"aria-expanded\", String(open))\n })\n\n provider\n .querySelectorAll(\"[data-sidebar='menu-tooltip']\")\n .forEach((tooltip) => {\n tooltip.hidden = state !== \"collapsed\"\n })\n }\n\n const provider = document.currentScript?.closest(\n \"[data-slot='sidebar-wrapper']\"\n )\n if (!(provider instanceof HTMLElement)) return\n\n const controlledOpen = provider.dataset.open\n const cookieValue = document.cookie\n .split(\"; \")\n .find((entry) => entry.startsWith(\"sidebar_state=\"))\n ?.slice(\"sidebar_state=\".length)\n\n const isOpen =\n controlledOpen === \"true\" || controlledOpen === \"false\"\n ? controlledOpen === \"true\"\n : cookieValue === \"true\" || cookieValue === \"false\"\n ? cookieValue === \"true\"\n : provider.dataset.defaultOpen !== \"false\"\n\n provider.dataset.openMobile = \"false\"\n window.__fulldevSyncSidebarProvider(provider, isOpen)\n })()\n </script>\n</div>\n\n<script is:inline>\n ;(() => {\n const scrollTop = Number(sessionStorage.getItem(\"sidebar_scroll\") || 0)\n\n document.querySelectorAll(\"[data-sidebar='content']\").forEach((content) => {\n content.scrollTop = scrollTop\n })\n })()\n</script>\n\n<script is:inline>\n ;(() => {\n if (window.__fulldevSidebarInitialized) return\n window.__fulldevSidebarInitialized = true\n\n const SIDEBAR_COOKIE_NAME = \"sidebar_state\"\n const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\n\n const toggleSidebar = (provider) => {\n if (!window.matchMedia(\"(min-width: 768px)\").matches) {\n const openMobile = provider.dataset.openMobile !== \"true\"\n provider.dataset.openMobile = openMobile ? \"true\" : \"false\"\n provider\n .querySelector(\"[data-sidebar='sheet']\")\n ?.dispatchEvent(\n new CustomEvent(\"dialog:set\", { detail: { open: openMobile } })\n )\n return\n }\n\n const open = provider.dataset.open === \"false\"\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n window.__fulldevSyncSidebarProvider?.(provider, open)\n }\n\n document.addEventListener(\"click\", (event) => {\n const control = event.target?.closest?.(\n \"[data-sidebar='trigger'], [data-sidebar='rail']\"\n )\n if (!control || control.matches(\":disabled, [aria-disabled='true']\")) {\n return\n }\n\n const provider = control.closest(\"[data-slot='sidebar-wrapper']\")\n if (!provider) return\n\n toggleSidebar(provider)\n })\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key.toLowerCase() !== \"b\") return\n if (!event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return\n }\n if (event.defaultPrevented) return\n\n const target = event.target\n const isTyping =\n target instanceof HTMLElement &&\n (target.isContentEditable ||\n target.matches(\"input, textarea, select, [role='textbox']\"))\n if (isTyping) return\n\n const provider = document.querySelector(\"[data-slot='sidebar-wrapper']\")\n if (!(provider instanceof HTMLElement)) return\n\n event.preventDefault()\n toggleSidebar(provider)\n })\n\n document.addEventListener(\n \"scroll\",\n (event) => {\n if (event.target?.dataset?.sidebar === \"content\") {\n sessionStorage.setItem(\"sidebar_scroll\", event.target.scrollTop)\n }\n },\n true\n )\n })()\n</script>\n",
17
+ "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\n\ntype Props = HTMLAttributes<\"div\"> & {\n defaultOpen?: boolean\n open?: boolean\n}\n\nconst {\n class: className,\n style,\n defaultOpen = true,\n open,\n ...props\n} = Astro.props\n\nconst resolvedStyle = [\n `--sidebar-width: ${SIDEBAR_WIDTH}`,\n `--sidebar-width-mobile: ${SIDEBAR_WIDTH_MOBILE}`,\n `--sidebar-width-icon: ${SIDEBAR_WIDTH_ICON}`,\n typeof style === \"string\" ? style : undefined,\n]\n .filter(Boolean)\n .join(\"; \")\n---\n\n<div\n data-slot=\"sidebar-wrapper\"\n data-default-open={defaultOpen ? \"true\" : \"false\"}\n data-open={open === undefined ? undefined : open ? \"true\" : \"false\"}\n data-sidebar-initializing=\"true\"\n style={resolvedStyle}\n class={cn(\n \"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full\",\n className\n )}\n {...props}\n>\n <slot />\n <style is:inline>\n [data-slot=\"sidebar-wrapper\"][data-sidebar-initializing=\"true\"]\n [data-slot=\"sidebar-gap\"],\n [data-slot=\"sidebar-wrapper\"][data-sidebar-initializing=\"true\"]\n [data-slot=\"sidebar-container\"] {\n transition: none !important;\n }\n </style>\n <script is:inline>\n ;(() => {\n window.__fulldevSyncSidebarProvider ??= (provider, open) => {\n const state = open ? \"expanded\" : \"collapsed\"\n const sidebar = provider.querySelector(\n \"[data-slot='sidebar']:not([data-mobile='true'])\"\n )\n const collapsible = sidebar?.dataset.sidebarCollapsible ?? \"offcanvas\"\n\n provider.dataset.open = open ? \"true\" : \"false\"\n provider.dataset.state = state\n\n if (sidebar) {\n sidebar.dataset.state = collapsible === \"none\" ? \"expanded\" : state\n sidebar.dataset.collapsible =\n collapsible === \"none\" ? \"none\" : open ? \"\" : collapsible\n }\n\n provider\n .querySelectorAll(\"[data-sidebar='trigger'], [data-sidebar='rail']\")\n .forEach((control) => {\n control.setAttribute(\"aria-expanded\", String(open))\n })\n\n provider\n .querySelectorAll(\"[data-sidebar='menu-tooltip']\")\n .forEach((tooltip) => {\n tooltip.hidden = state !== \"collapsed\"\n })\n }\n\n const provider = document.currentScript?.closest(\n \"[data-slot='sidebar-wrapper']\"\n )\n if (!(provider instanceof HTMLElement)) return\n\n const controlledOpen = provider.dataset.open\n const cookieValue = document.cookie\n .split(\"; \")\n .find((entry) => entry.startsWith(\"sidebar_state=\"))\n ?.slice(\"sidebar_state=\".length)\n\n const isOpen =\n controlledOpen === \"true\" || controlledOpen === \"false\"\n ? controlledOpen === \"true\"\n : cookieValue === \"true\" || cookieValue === \"false\"\n ? cookieValue === \"true\"\n : provider.dataset.defaultOpen !== \"false\"\n\n provider.dataset.openMobile = \"false\"\n window.__fulldevSyncSidebarProvider(provider, isOpen)\n requestAnimationFrame(() => {\n provider.removeAttribute(\"data-sidebar-initializing\")\n })\n })()\n </script>\n</div>\n\n<script is:inline>\n ;(() => {\n const scrollTop = Number(sessionStorage.getItem(\"sidebar_scroll\") || 0)\n\n document.querySelectorAll(\"[data-sidebar='content']\").forEach((content) => {\n content.scrollTop = scrollTop\n })\n })()\n</script>\n\n<script is:inline>\n ;(() => {\n if (window.__fulldevSidebarInitialized) return\n window.__fulldevSidebarInitialized = true\n\n const SIDEBAR_COOKIE_NAME = \"sidebar_state\"\n const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\n\n const toggleSidebar = (provider) => {\n if (!window.matchMedia(\"(min-width: 768px)\").matches) {\n const openMobile = provider.dataset.openMobile !== \"true\"\n provider.dataset.openMobile = openMobile ? \"true\" : \"false\"\n provider\n .querySelector(\"[data-sidebar='sheet']\")\n ?.dispatchEvent(\n new CustomEvent(\"dialog:set\", { detail: { open: openMobile } })\n )\n return\n }\n\n const open = provider.dataset.open === \"false\"\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n window.__fulldevSyncSidebarProvider?.(provider, open)\n }\n\n document.addEventListener(\"click\", (event) => {\n const control = event.target?.closest?.(\n \"[data-sidebar='trigger'], [data-sidebar='rail']\"\n )\n if (!control || control.matches(\":disabled, [aria-disabled='true']\")) {\n return\n }\n\n const provider = control.closest(\"[data-slot='sidebar-wrapper']\")\n if (!provider) return\n\n toggleSidebar(provider)\n })\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key.toLowerCase() !== \"b\") return\n if (!event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return\n }\n if (event.defaultPrevented) return\n\n const target = event.target\n const isTyping =\n target instanceof HTMLElement &&\n (target.isContentEditable ||\n target.matches(\"input, textarea, select, [role='textbox']\"))\n if (isTyping) return\n\n const provider = document.querySelector(\"[data-slot='sidebar-wrapper']\")\n if (!(provider instanceof HTMLElement)) return\n\n event.preventDefault()\n toggleSidebar(provider)\n })\n\n document.addEventListener(\n \"scroll\",\n (event) => {\n if (event.target?.dataset?.sidebar === \"content\") {\n sessionStorage.setItem(\"sidebar_scroll\", event.target.scrollTop)\n }\n },\n true\n )\n })()\n</script>\n",
18
18
  "type": "registry:ui"
19
19
  },
20
20
  {
package/registry.json CHANGED
@@ -1687,6 +1687,7 @@
1687
1687
  "@fulldev/empty",
1688
1688
  "@fulldev/field",
1689
1689
  "@fulldev/header",
1690
+ "@fulldev/hover-card",
1690
1691
  "@fulldev/input",
1691
1692
  "@fulldev/input-group",
1692
1693
  "@fulldev/item",
@@ -1695,6 +1696,7 @@
1695
1696
  "@fulldev/marquee",
1696
1697
  "@fulldev/native-select",
1697
1698
  "@fulldev/navigation-menu",
1699
+ "@fulldev/popover",
1698
1700
  "@fulldev/price",
1699
1701
  "@fulldev/radio-group",
1700
1702
  "@fulldev/rating",
@@ -1711,7 +1713,9 @@
1711
1713
  "@fulldev/tabs",
1712
1714
  "@fulldev/textarea",
1713
1715
  "@fulldev/theme-toggle",
1716
+ "@fulldev/tile",
1714
1717
  "@fulldev/toc",
1718
+ "@fulldev/toggle",
1715
1719
  "@fulldev/tooltip",
1716
1720
  "@fulldev/typography",
1717
1721
  "@fulldev/video",
@@ -1738,6 +1742,8 @@
1738
1742
  "@fulldev/content-2",
1739
1743
  "@fulldev/content-3",
1740
1744
  "@fulldev/content-4",
1745
+ "@fulldev/content-5",
1746
+ "@fulldev/content-6",
1741
1747
  "@fulldev/cta-1",
1742
1748
  "@fulldev/cta-2",
1743
1749
  "@fulldev/cta-3",
@@ -50,7 +50,7 @@ const { class: className, logo, navigation, buttons } = Astro.props
50
50
  ---
51
51
 
52
52
  <Header class={cn("", className)}>
53
- <HeaderContainer class="justify-between gap-4">
53
+ <HeaderContainer class="items-center justify-between gap-4">
54
54
  <HeaderGroup>
55
55
  <Logo href={logo.href}>
56
56
  <LogoImage
@@ -124,18 +124,7 @@ const searchGroups = search.items.reduce<
124
124
  return groups
125
125
  }, [])
126
126
 
127
- let githubStars = "Stars"
128
-
129
- try {
130
- const response = await fetch(`https://api.github.com/repos/${githubRepo}`)
131
- const data = await response.json()
132
-
133
- if (response.ok && typeof data.stargazers_count === "number") {
134
- githubStars = new Intl.NumberFormat().format(data.stargazers_count)
135
- }
136
- } catch {
137
- // Keep the fallback label when GitHub is unavailable.
138
- }
127
+ const githubStars = "560+"
139
128
  ---
140
129
 
141
130
  <SidebarProvider>
@@ -233,10 +222,61 @@ try {
233
222
  <SidebarInset>
234
223
  <Header class="h-16">
235
224
  <HeaderContainer class="max-w-none justify-between">
236
- <HeaderGroup>
225
+ <HeaderGroup class="flex-1 md:flex-none">
237
226
  <SidebarTrigger />
238
227
  <Separator orientation="vertical" class="my-auto mr-2 h-4" />
239
- <Breadcrumb>
228
+ <Dialog class="block min-w-0 flex-1 md:hidden" data-search-dialog>
229
+ <DialogTrigger
230
+ as="div"
231
+ role="button"
232
+ tabindex="0"
233
+ class="h-auto w-full cursor-pointer border-0 bg-transparent p-0 shadow-none hover:bg-transparent"
234
+ aria-keyshortcuts="Meta+K Control+K"
235
+ >
236
+ <InputGroup class="h-8 shadow-none">
237
+ <InputGroupInput
238
+ placeholder={search.label}
239
+ readonly
240
+ tabindex="-1"
241
+ aria-label={search.label}
242
+ class="cursor-pointer"
243
+ />
244
+ <InputGroupAddon>
245
+ <Icon name="search" />
246
+ </InputGroupAddon>
247
+ </InputGroup>
248
+ </DialogTrigger>
249
+
250
+ <CommandDialog title={search.label} description={search.label}>
251
+ <Command
252
+ class="[&_[data-slot=command-item][data-selected]]:bg-accent [&_[data-slot=command-item][data-selected]]:text-accent-foreground"
253
+ >
254
+ <CommandInput placeholder={search.label} />
255
+ <CommandList>
256
+ <CommandEmpty>{search.empty}</CommandEmpty>
257
+
258
+ {
259
+ searchGroups.map((group) => (
260
+ <CommandGroup heading={group.label}>
261
+ {group.items.map((item) => (
262
+ <CommandItem
263
+ href={item.href}
264
+ value={item.path}
265
+ label={item.label}
266
+ keywords={[item.title, item.path]}
267
+ >
268
+ {item.label}
269
+ <CommandShortcut>{item.href}</CommandShortcut>
270
+ </CommandItem>
271
+ ))}
272
+ </CommandGroup>
273
+ ))
274
+ }
275
+ </CommandList>
276
+ </Command>
277
+ </CommandDialog>
278
+ </Dialog>
279
+ <Breadcrumb class="hidden md:block">
240
280
  <BreadcrumbList>
241
281
  {
242
282
  breadcrumb.items.map((item, index) => {
@@ -370,7 +410,11 @@ try {
370
410
  if (event.key.toLowerCase() !== "k") return
371
411
  if (event.shiftKey || event.altKey) return
372
412
 
373
- const dialog = document.querySelector("[data-search-dialog]")
413
+ const dialogs = Array.from(
414
+ document.querySelectorAll("[data-search-dialog]")
415
+ )
416
+ const dialog =
417
+ dialogs.find((dialog) => dialog.getClientRects().length > 0) ?? dialogs[0]
374
418
  const isSearchShortcut = isMac
375
419
  ? event.metaKey && !event.ctrlKey
376
420
  : event.ctrlKey && !event.metaKey
@@ -34,6 +34,7 @@ const resolvedStyle = [
34
34
  data-slot="sidebar-wrapper"
35
35
  data-default-open={defaultOpen ? "true" : "false"}
36
36
  data-open={open === undefined ? undefined : open ? "true" : "false"}
37
+ data-sidebar-initializing="true"
37
38
  style={resolvedStyle}
38
39
  class={cn(
39
40
  "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
@@ -42,6 +43,14 @@ const resolvedStyle = [
42
43
  {...props}
43
44
  >
44
45
  <slot />
46
+ <style is:inline>
47
+ [data-slot="sidebar-wrapper"][data-sidebar-initializing="true"]
48
+ [data-slot="sidebar-gap"],
49
+ [data-slot="sidebar-wrapper"][data-sidebar-initializing="true"]
50
+ [data-slot="sidebar-container"] {
51
+ transition: none !important;
52
+ }
53
+ </style>
45
54
  <script is:inline>
46
55
  ;(() => {
47
56
  window.__fulldevSyncSidebarProvider ??= (provider, open) => {
@@ -93,6 +102,9 @@ const resolvedStyle = [
93
102
 
94
103
  provider.dataset.openMobile = "false"
95
104
  window.__fulldevSyncSidebarProvider(provider, isOpen)
105
+ requestAnimationFrame(() => {
106
+ provider.removeAttribute("data-sidebar-initializing")
107
+ })
96
108
  })()
97
109
  </script>
98
110
  </div>