fulldev-ui 0.9.1 → 0.9.3

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,19 @@
1
1
  # Fulldev UI
2
2
 
3
+ ## 0.9.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e641911`](https://github.com/fulldotdev/ui/commit/e64191143efe448a46999d63ca7ed6d8bd320d1e) Thanks [@silveltman](https://github.com/silveltman)! - Add the gradient component to the registry, keep Lucide icons unfilled, and fix marquee spacing during looping animations.
8
+
9
+ ## 0.9.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`c052c13`](https://github.com/fulldotdev/ui/commit/c052c13c356bae933ffd838de1a95a0af7e97e21) Thanks [@silveltman](https://github.com/silveltman)! - Add active navigation item support to the Header 1 block.
14
+
15
+ - [`49e1bfc`](https://github.com/fulldotdev/ui/commit/49e1bfc3d1bbb7a6cb126094385704fdcc0a49d6) Thanks [@silveltman](https://github.com/silveltman)! - Update registry build and styling dependencies.
16
+
3
17
  ## 0.9.1
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fulldev-ui",
3
3
  "type": "module",
4
- "version": "0.9.1",
4
+ "version": "0.9.3",
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",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@astrojs/mdx": "^5.0.6",
52
- "@astrojs/sitemap": "^3.7.2",
52
+ "@astrojs/sitemap": "^3.7.3",
53
53
  "@data-slot/accordion": "^0.2.166",
54
54
  "@data-slot/alert-dialog": "^0.2.166",
55
55
  "@data-slot/collapsible": "^0.2.166",
@@ -68,10 +68,10 @@
68
68
  "@data-slot/tabs": "^0.2.166",
69
69
  "@data-slot/toggle": "^0.2.166",
70
70
  "@data-slot/tooltip": "^0.2.166",
71
- "@fontsource-variable/geist": "^5.2.8",
72
- "@fontsource-variable/geist-mono": "^5.2.7",
71
+ "@fontsource-variable/geist": "^5.2.9",
72
+ "@fontsource-variable/geist-mono": "^5.2.8",
73
73
  "@lexingtonthemes/seo": "^0.2.0",
74
- "@tailwindcss/vite": "^4.3.0",
74
+ "@tailwindcss/vite": "^4.3.2",
75
75
  "astro": "^6.3.3",
76
76
  "astro-favicons": "^3.1.6",
77
77
  "astro-live-code": "^0.0.6",
@@ -79,11 +79,11 @@
79
79
  "class-variance-authority": "^0.7.1",
80
80
  "clsx": "^2.1.1",
81
81
  "embla-carousel": "^8.6.0",
82
- "lucide-static": "^1.16.0",
83
- "shadcn": "^4.7.0",
84
- "simple-icons": "^16.19.0",
82
+ "lucide-static": "^1.22.0",
83
+ "shadcn": "^4.12.0",
84
+ "simple-icons": "^16.24.1",
85
85
  "tailwind-merge": "^3.6.0",
86
- "tailwindcss": "^4.3.0",
86
+ "tailwindcss": "^4.3.2",
87
87
  "tw-animate-css": "^1.4.0",
88
88
  "typescript": "^6.0.3"
89
89
  },
@@ -92,10 +92,10 @@
92
92
  "@changesets/changelog-github": "^0.7.0",
93
93
  "@changesets/cli": "^2.31.0",
94
94
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
95
- "prettier": "3.8.3",
95
+ "prettier": "3.9.3",
96
96
  "prettier-plugin-astro": "0.14.1",
97
97
  "prettier-plugin-tailwindcss": "^0.8.0",
98
- "sharp": "^0.34.5"
98
+ "sharp": "^0.35.2"
99
99
  },
100
100
  "scripts": {
101
101
  "astro": "astro",
@@ -12,12 +12,6 @@
12
12
  "type": "registry:file",
13
13
  "target": "src/content.config.ts"
14
14
  },
15
- {
16
- "path": "src/components/layout-renderer.astro",
17
- "content": "---\n// Keep this renderer generic and stable. New page types should be wired by\n// adding a matching schema and src/layouts/<type>.astro file, not by adding\n// layout-specific branches here.\nimport { getEntry, render, type CollectionEntry } from \"astro:content\"\n\ntype Props = CollectionEntry<\"pages\">\n\nconst page = Astro.props\nconst pageData = page.data\nconst { Content, headings } = await render(page)\nconst global = await getEntry(\"globals\", Astro.currentLocale || \"en\")\nif (!global) throw new Error(\"Add a global to content/globals\")\nconst globalData = global.data\n\nconst layoutImports = import.meta.glob(\"../layouts/*.astro\", { eager: true })\nconst layoutPath = `../layouts/${pageData.type}.astro`\nconst layoutImport = layoutImports[layoutPath] as { default?: any } | undefined\nconst Layout = layoutImport?.default\n\nif (!Layout) throw new Error(`Add a layout for page type \"${pageData.type}\"`)\n---\n\n<Layout global={globalData} page={pageData} headings={headings}>\n <Content />\n</Layout>\n",
18
- "type": "registry:file",
19
- "target": "src/components/layout-renderer.astro"
20
- },
21
15
  {
22
16
  "path": "src/lib/pages.ts",
23
17
  "content": "import { getCollection, type CollectionEntry } from \"astro:content\"\n\ntype Page = CollectionEntry<\"pages\">\n\nexport type PageSearchItem = {\n label: string\n href: string\n title: string\n path: string\n description: string\n group: string\n}\n\nexport type PageBreadcrumbItem = {\n label: string\n href: string\n}\n\nexport const normalizePath = (path: string) => {\n if (path === \"/\") return path\n\n return path.replace(/\\/$/, \"\")\n}\n\nexport const getPageHref = (page: Page) =>\n page.id === \"index\" ? \"/\" : `/${page.id}/`\n\nexport const formatSlug = (slug: string) =>\n slug\n .split(\"-\")\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \")\n\nconst getPageSearchGroup = (href: string) => {\n const [slug] = href.split(\"/\").filter(Boolean)\n\n return slug ? formatSlug(slug) : \"Overview\"\n}\n\nexport const getPageSearchItems = async (): Promise<PageSearchItem[]> => {\n const pages = await getCollection(\"pages\")\n\n return pages\n .map((page) => ({\n label: page.data.title,\n href: getPageHref(page),\n title: page.data.title,\n path: getPageHref(page),\n description: page.data.description,\n group: getPageSearchGroup(getPageHref(page)),\n }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\nexport const getPageBreadcrumbItems = async (\n pathname: string\n): Promise<PageBreadcrumbItem[]> => {\n const currentPath = normalizePath(pathname)\n const pages = await getCollection(\"pages\")\n const pageTitlesByHref = new Map(\n pages.map((page) => [getPageHref(page), page.data.title])\n )\n const homeLink = {\n label: pageTitlesByHref.get(\"/\") ?? \"Home\",\n href: \"/\",\n }\n\n if (currentPath === \"/\") return [homeLink]\n\n return [\n homeLink,\n ...currentPath\n .split(\"/\")\n .filter(Boolean)\n .map((slug, index, slugs) => {\n const href = `/${slugs.slice(0, index + 1).join(\"/\")}/`\n\n return {\n label: pageTitlesByHref.get(href) ?? formatSlug(slug),\n href,\n }\n }),\n ]\n}\n",
@@ -26,7 +20,7 @@
26
20
  },
27
21
  {
28
22
  "path": "src/pages/[...page].astro",
29
- "content": "---\nimport { getCollection } from \"astro:content\"\n\nimport LayoutRenderer from \"@/components/layout-renderer.astro\"\n\nexport async function getStaticPaths() {\n const pages = await getCollection(\"pages\")\n return pages.map((page) => {\n return {\n params: {\n page: page.id === \"index\" ? undefined : page.id,\n },\n props: page,\n }\n })\n}\n---\n\n<LayoutRenderer {...Astro.props} />\n",
23
+ "content": "---\nimport { getCollection, getEntry, render } from \"astro:content\"\n\nexport const prerender = true\n\nexport async function getStaticPaths() {\n const pages = await getCollection(\"pages\")\n return pages.map((page) => {\n return {\n params: {\n page: page.id === \"index\" ? undefined : page.id,\n },\n props: page,\n }\n })\n}\n\nconst page = Astro.props\nconst pageData = page.data\nconst { Content, headings } = await render(page)\nconst global = await getEntry(\"globals\", Astro.currentLocale || \"en\")\nif (!global) throw new Error(\"Add a global to content/globals\")\nconst globalData = global.data\n\nconst layoutImports = import.meta.glob(\"../layouts/*.astro\", { eager: true })\nconst layoutPath = `../layouts/${pageData.type}.astro`\nconst layoutImport = layoutImports[layoutPath] as { default?: any } | undefined\nconst Layout = layoutImport?.default\n\nif (!Layout) throw new Error(`Add a layout for page type \"${pageData.type}\"`)\n---\n\n<Layout global={globalData} page={pageData} headings={headings}>\n <Content />\n</Layout>\n",
30
24
  "type": "registry:file",
31
25
  "target": "src/pages/[...page].astro"
32
26
  },
@@ -19,6 +19,7 @@
19
19
  "@fulldev/dropdown-menu",
20
20
  "@fulldev/empty",
21
21
  "@fulldev/field",
22
+ "@fulldev/gradient",
22
23
  "@fulldev/header",
23
24
  "@fulldev/hover-card",
24
25
  "@fulldev/input",
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "gradient",
4
+ "files": [
5
+ {
6
+ "path": "src/components/ui/gradient/gradient.astro",
7
+ "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst gradientVariants = cva(\n \"bg-primary pointer-events-none absolute inset-0 opacity-50 [--gradient-stops:black,transparent]\",\n {\n variants: {\n variant: {\n radial:\n \"[mask-image:radial-gradient(ellipse_at_var(--gradient-position),var(--gradient-stops))] [-webkit-mask-image:radial-gradient(ellipse_at_var(--gradient-position),var(--gradient-stops))]\",\n linear:\n \"[mask-image:linear-gradient(var(--gradient-direction),var(--gradient-stops))] [-webkit-mask-image:linear-gradient(var(--gradient-direction),var(--gradient-stops))]\",\n },\n direction: {\n left: \"[--gradient-direction:to_left] [--gradient-position:0%_50%]\",\n right: \"[--gradient-direction:to_right] [--gradient-position:100%_50%]\",\n top: \"[--gradient-direction:to_top] [--gradient-position:50%_0%]\",\n bottom:\n \"[--gradient-direction:to_bottom] [--gradient-position:50%_100%]\",\n center:\n \"[--gradient-direction:to_bottom] [--gradient-position:50%_50%]\",\n },\n },\n defaultVariants: {\n variant: \"radial\",\n direction: \"bottom\",\n },\n }\n)\n\ntype Props = HTMLAttributes<\"div\"> & VariantProps<typeof gradientVariants>\n\nconst { class: className, variant, direction, ...props } = Astro.props\n---\n\n<div\n data-slot=\"gradient\"\n aria-hidden=\"true\"\n class={cn(gradientVariants({ variant, direction }), className)}\n {...props}\n>\n</div>\n",
8
+ "type": "registry:ui"
9
+ },
10
+ {
11
+ "path": "src/components/ui/gradient/index.ts",
12
+ "content": "export { default as Gradient } from \"./gradient.astro\"\n",
13
+ "type": "registry:ui"
14
+ }
15
+ ],
16
+ "type": "registry:ui"
17
+ }
@@ -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=\"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",
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 active?: boolean\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} active={item.active}>\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
  ],
@@ -8,7 +8,7 @@
8
8
  "files": [
9
9
  {
10
10
  "path": "src/components/ui/icon/icon.astro",
11
- "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\ntype Props = Omit<HTMLAttributes<\"svg\">, \"name\" | \"href\"> & {\n href?: HTMLAttributes<\"a\">[\"href\"]\n name?: string | number | null\n}\n\nconst { name, href, ...props } = Astro.props\n\nconst hrefMap = {\n \"x.com\": \"x\",\n twitter: \"twitter\",\n facebook: \"facebook\",\n instagram: \"instagram\",\n pinterest: \"pinterest\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n tumblr: \"tumblr\",\n \"wa.me\": \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n vimeo: \"vimeo\",\n flickr: \"flickr\",\n yelp: \"yelp\",\n spotify: \"spotify\",\n behance: \"behance\",\n dribbble: \"dribbble\",\n soundcloud: \"soundcloud\",\n github: \"github\",\n twitch: \"twitch\",\n \"tel:\": \"phone\",\n \"mailto:\": \"mail\",\n \"maps.app.goo.gl\": \"map-pin\",\n linkedin: \"linkedin\",\n} as const\n\nconst icons = import.meta.glob(\n [\n \"/node_modules/lucide-static/icons/*.svg\",\n \"/node_modules/simple-icons/icons/*.svg\",\n ],\n { eager: true }\n) as Record<string, { default: any }>\n\nconst foundPath = () => {\n let foundPath: string | undefined = undefined\n\n // For x.com, use the Simple Icons X instead of Lucide's X icon.\n if (name === \"x.com\") {\n foundPath = \"/node_modules/simple-icons/icons/x.svg\"\n } else if (name) {\n foundPath = Object.keys(icons).find((key) => key.includes(`/${name}.svg`))\n } else if (href) {\n const hrefKey = Object.keys(hrefMap).find((key) =>\n href.toString().includes(key)\n )\n const hrefValue = hrefMap[hrefKey as keyof typeof hrefMap]\n foundPath = Object.keys(icons)\n .reverse()\n .find((key) => key.includes(`/${hrefValue}.svg`))\n }\n\n return foundPath\n}\n\nconst iconPath = foundPath()\nconst module = iconPath ? icons[iconPath] : undefined\nconst Comp = module?.default\n---\n\n{\n Comp && foundPath()?.includes(\"simple-icons\") && (\n <Comp\n height={24}\n width={24}\n fill=\"currentColor\"\n class=\"size-[1em] text-base\"\n {...props}\n />\n )\n}\n\n{\n Comp && foundPath()?.includes(\"lucide-static\") && (\n <Comp height={24} width={24} class=\"size-[1em] text-base\" {...props} />\n )\n}\n",
11
+ "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\ntype Props = Omit<HTMLAttributes<\"svg\">, \"name\" | \"href\"> & {\n href?: HTMLAttributes<\"a\">[\"href\"]\n name?: string | number | null\n}\n\nconst { name, href, ...props } = Astro.props\n\nconst hrefMap = {\n \"x.com\": \"x\",\n twitter: \"twitter\",\n facebook: \"facebook\",\n instagram: \"instagram\",\n pinterest: \"pinterest\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n tumblr: \"tumblr\",\n \"wa.me\": \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n vimeo: \"vimeo\",\n flickr: \"flickr\",\n yelp: \"yelp\",\n spotify: \"spotify\",\n behance: \"behance\",\n dribbble: \"dribbble\",\n soundcloud: \"soundcloud\",\n github: \"github\",\n twitch: \"twitch\",\n \"tel:\": \"phone\",\n \"mailto:\": \"mail\",\n \"maps.app.goo.gl\": \"map-pin\",\n linkedin: \"linkedin\",\n} as const\n\nconst icons = import.meta.glob(\n [\n \"/node_modules/lucide-static/icons/*.svg\",\n \"/node_modules/simple-icons/icons/*.svg\",\n ],\n { eager: true }\n) as Record<string, { default: any }>\n\nconst foundPath = () => {\n let foundPath: string | undefined = undefined\n\n // For x.com, use the Simple Icons X instead of Lucide's X icon.\n if (name === \"x.com\") {\n foundPath = \"/node_modules/simple-icons/icons/x.svg\"\n } else if (name) {\n foundPath = Object.keys(icons).find((key) => key.includes(`/${name}.svg`))\n } else if (href) {\n const hrefKey = Object.keys(hrefMap).find((key) =>\n href.toString().includes(key)\n )\n const hrefValue = hrefMap[hrefKey as keyof typeof hrefMap]\n foundPath = Object.keys(icons)\n .reverse()\n .find((key) => key.includes(`/${hrefValue}.svg`))\n }\n\n return foundPath\n}\n\nconst iconPath = foundPath()\nconst module = iconPath ? icons[iconPath] : undefined\nconst Comp = module?.default\n---\n\n{\n Comp && foundPath()?.includes(\"simple-icons\") && (\n <Comp\n height={24}\n width={24}\n fill=\"currentColor\"\n class=\"size-[1em] text-base\"\n {...props}\n />\n )\n}\n\n{\n Comp && foundPath()?.includes(\"lucide-static\") && (\n <Comp\n height={24}\n width={24}\n fill=\"none\"\n class=\"size-[1em] text-base\"\n {...props}\n />\n )\n}\n",
12
12
  "type": "registry:ui"
13
13
  },
14
14
  {
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  {
6
6
  "path": "src/components/ui/marquee/marquee.astro",
7
- "content": "---\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Props = {\n direction?: \"left\" | \"right\" | \"top\" | \"bottom\"\n duration?: number | string\n gap?: string\n infinite?: boolean\n pauseOnHover?: boolean\n time?: number | string\n variant?: \"gradient\" | \"solid\"\n class?: string\n style?: string\n}\n\nconst {\n class: className = \"\",\n direction = \"left\",\n duration,\n gap = \"1rem\",\n infinite = true,\n pauseOnHover = false,\n style,\n time = 30,\n variant = \"gradient\",\n ...props\n} = Astro.props\n\nconst resolvedDuration = duration ?? time\nconst styleValue = `--duration:${\n typeof resolvedDuration === \"number\"\n ? `${resolvedDuration}s`\n : resolvedDuration\n}; --gap:${gap}; ${style ?? \"\"}`\nconst contentClass = cn(\n \"marquee-content flex min-w-full shrink-0 justify-around gap-[var(--gap)] will-change-transform group-data-[direction=bottom]:[animation-direction:reverse] group-data-[direction=right]:[animation-direction:reverse] data-[direction=bottom]:min-h-full data-[direction=bottom]:flex-col data-[direction=top]:min-h-full data-[direction=top]:flex-col motion-reduce:[animation-play-state:paused]\",\n infinite &&\n \"animate-[marquee-x_var(--duration,20s)_linear_infinite] group-data-[direction=bottom]:animate-[marquee-y_var(--duration,20s)_linear_infinite] group-data-[direction=top]:animate-[marquee-y_var(--duration,20s)_linear_infinite]\",\n direction === \"top\" && \"flex-col\",\n direction === \"bottom\" && \"flex-col\"\n)\n\nconst marqueeVariants = cva(\"relative w-full\", {\n variants: {\n variant: {\n gradient:\n \"[mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)] [-webkit-mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)] [&[data-direction=bottom]]:[mask-image:linear-gradient(to_top,transparent,black_10%,black_90%,transparent)] [&[data-direction=bottom]]:[-webkit-mask-image:linear-gradient(to_top,transparent,black_10%,black_90%,transparent)] [&[data-direction=top]]:[mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)] [&[data-direction=top]]:[-webkit-mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)]\",\n solid: \"\",\n },\n },\n defaultVariants: {\n variant: \"gradient\",\n },\n})\n---\n\n<div\n data-marquee\n data-direction={direction}\n data-pause-on-hover={pauseOnHover ? \"true\" : undefined}\n class={cn(\n \"\",\n // container utilities\n \"group flex w-full overflow-hidden data-[direction=bottom]:flex-col data-[direction=top]:flex-col\",\n marqueeVariants({ variant }),\n className\n )}\n style={styleValue}\n {...props}\n>\n <div class={contentClass}>\n <slot />\n </div>\n {\n infinite && (\n <div class={contentClass} aria-hidden=\"true\">\n <slot />\n </div>\n )\n }\n</div>\n\n<style>\n @keyframes marquee-x {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(-100%);\n }\n }\n @keyframes marquee-y {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(-100%);\n }\n }\n\n [data-marquee][data-pause-on-hover=\"true\"]:hover .marquee-content {\n animation-play-state: paused;\n }\n</style>\n",
7
+ "content": "---\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Props = {\n direction?: \"left\" | \"right\" | \"top\" | \"bottom\"\n duration?: number | string\n gap?: string\n infinite?: boolean\n pauseOnHover?: boolean\n time?: number | string\n variant?: \"gradient\" | \"solid\"\n class?: string\n style?: string\n}\n\nconst {\n class: className = \"\",\n direction = \"left\",\n duration,\n gap = \"1rem\",\n infinite = true,\n pauseOnHover = false,\n style,\n time = 30,\n variant = \"gradient\",\n ...props\n} = Astro.props\n\nconst resolvedDuration = duration ?? time\nconst styleValue = `--duration:${\n typeof resolvedDuration === \"number\"\n ? `${resolvedDuration}s`\n : resolvedDuration\n}; --gap:${gap}; ${style ?? \"\"}`\nconst contentClass = cn(\n \"marquee-content flex min-w-full shrink-0 justify-around gap-[var(--gap)] will-change-transform group-data-[direction=bottom]:[animation-direction:reverse] group-data-[direction=right]:[animation-direction:reverse] data-[direction=bottom]:min-h-full data-[direction=bottom]:flex-col data-[direction=top]:min-h-full data-[direction=top]:flex-col motion-reduce:[animation-play-state:paused]\",\n infinite &&\n \"animate-[marquee-x_var(--duration,20s)_linear_infinite] group-data-[direction=bottom]:animate-[marquee-y_var(--duration,20s)_linear_infinite] group-data-[direction=top]:animate-[marquee-y_var(--duration,20s)_linear_infinite]\",\n direction === \"top\" && \"flex-col\",\n direction === \"bottom\" && \"flex-col\"\n)\n\nconst marqueeVariants = cva(\"relative w-full\", {\n variants: {\n variant: {\n gradient:\n \"[mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)] [-webkit-mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)] [&[data-direction=bottom]]:[mask-image:linear-gradient(to_top,transparent,black_10%,black_90%,transparent)] [&[data-direction=bottom]]:[-webkit-mask-image:linear-gradient(to_top,transparent,black_10%,black_90%,transparent)] [&[data-direction=top]]:[mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)] [&[data-direction=top]]:[-webkit-mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)]\",\n solid: \"\",\n },\n },\n defaultVariants: {\n variant: \"gradient\",\n },\n})\n---\n\n<div\n data-marquee\n data-direction={direction}\n data-pause-on-hover={pauseOnHover ? \"true\" : undefined}\n class={cn(\n \"\",\n // container utilities\n \"group flex w-full gap-[var(--gap)] overflow-hidden data-[direction=bottom]:flex-col data-[direction=top]:flex-col\",\n marqueeVariants({ variant }),\n className\n )}\n style={styleValue}\n {...props}\n>\n <div class={contentClass}>\n <slot />\n </div>\n {\n infinite && (\n <div class={contentClass} aria-hidden=\"true\">\n <slot />\n </div>\n )\n }\n</div>\n\n<style>\n @keyframes marquee-x {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(calc(-100% - var(--gap)));\n }\n }\n @keyframes marquee-y {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(calc(-100% - var(--gap)));\n }\n }\n\n [data-marquee][data-pause-on-hover=\"true\"]:hover .marquee-content {\n animation-play-state: paused;\n }\n</style>\n",
8
8
  "type": "registry:ui"
9
9
  },
10
10
  {
@@ -651,6 +651,20 @@
651
651
  }
652
652
  ]
653
653
  },
654
+ {
655
+ "name": "gradient",
656
+ "type": "registry:ui",
657
+ "files": [
658
+ {
659
+ "path": "src/components/ui/gradient/gradient.astro",
660
+ "type": "registry:ui"
661
+ },
662
+ {
663
+ "path": "src/components/ui/gradient/index.ts",
664
+ "type": "registry:ui"
665
+ }
666
+ ]
667
+ },
654
668
  {
655
669
  "name": "header",
656
670
  "type": "registry:ui",
@@ -1686,6 +1700,7 @@
1686
1700
  "@fulldev/dropdown-menu",
1687
1701
  "@fulldev/empty",
1688
1702
  "@fulldev/field",
1703
+ "@fulldev/gradient",
1689
1704
  "@fulldev/header",
1690
1705
  "@fulldev/hover-card",
1691
1706
  "@fulldev/input",
@@ -1848,11 +1863,6 @@
1848
1863
  "type": "registry:file",
1849
1864
  "target": "src/content.config.ts"
1850
1865
  },
1851
- {
1852
- "path": "src/components/layout-renderer.astro",
1853
- "type": "registry:file",
1854
- "target": "src/components/layout-renderer.astro"
1855
- },
1856
1866
  {
1857
1867
  "path": "src/lib/pages.ts",
1858
1868
  "type": "registry:file",
package/registry.json CHANGED
@@ -651,6 +651,20 @@
651
651
  }
652
652
  ]
653
653
  },
654
+ {
655
+ "name": "gradient",
656
+ "type": "registry:ui",
657
+ "files": [
658
+ {
659
+ "path": "src/components/ui/gradient/gradient.astro",
660
+ "type": "registry:ui"
661
+ },
662
+ {
663
+ "path": "src/components/ui/gradient/index.ts",
664
+ "type": "registry:ui"
665
+ }
666
+ ]
667
+ },
654
668
  {
655
669
  "name": "header",
656
670
  "type": "registry:ui",
@@ -1686,6 +1700,7 @@
1686
1700
  "@fulldev/dropdown-menu",
1687
1701
  "@fulldev/empty",
1688
1702
  "@fulldev/field",
1703
+ "@fulldev/gradient",
1689
1704
  "@fulldev/header",
1690
1705
  "@fulldev/hover-card",
1691
1706
  "@fulldev/input",
@@ -1848,11 +1863,6 @@
1848
1863
  "type": "registry:file",
1849
1864
  "target": "src/content.config.ts"
1850
1865
  },
1851
- {
1852
- "path": "src/components/layout-renderer.astro",
1853
- "type": "registry:file",
1854
- "target": "src/components/layout-renderer.astro"
1855
- },
1856
1866
  {
1857
1867
  "path": "src/lib/pages.ts",
1858
1868
  "type": "registry:file",
@@ -33,6 +33,7 @@ type Props = {
33
33
  navigation: {
34
34
  label: string
35
35
  href: string
36
+ active?: boolean
36
37
  links?: {
37
38
  label: string
38
39
  href: string
@@ -67,7 +68,7 @@ const { class: className, logo, navigation, buttons } = Astro.props
67
68
  {
68
69
  navigation.map((item, index) => (
69
70
  <NavigationMenuItem value={`nav-${index}`}>
70
- <NavigationMenuLink href={item.href}>
71
+ <NavigationMenuLink href={item.href} active={item.active}>
71
72
  {item.label}
72
73
  </NavigationMenuLink>
73
74
  </NavigationMenuItem>
@@ -0,0 +1,45 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const gradientVariants = cva(
8
+ "bg-primary pointer-events-none absolute inset-0 opacity-50 [--gradient-stops:black,transparent]",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ radial:
13
+ "[mask-image:radial-gradient(ellipse_at_var(--gradient-position),var(--gradient-stops))] [-webkit-mask-image:radial-gradient(ellipse_at_var(--gradient-position),var(--gradient-stops))]",
14
+ linear:
15
+ "[mask-image:linear-gradient(var(--gradient-direction),var(--gradient-stops))] [-webkit-mask-image:linear-gradient(var(--gradient-direction),var(--gradient-stops))]",
16
+ },
17
+ direction: {
18
+ left: "[--gradient-direction:to_left] [--gradient-position:0%_50%]",
19
+ right: "[--gradient-direction:to_right] [--gradient-position:100%_50%]",
20
+ top: "[--gradient-direction:to_top] [--gradient-position:50%_0%]",
21
+ bottom:
22
+ "[--gradient-direction:to_bottom] [--gradient-position:50%_100%]",
23
+ center:
24
+ "[--gradient-direction:to_bottom] [--gradient-position:50%_50%]",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "radial",
29
+ direction: "bottom",
30
+ },
31
+ }
32
+ )
33
+
34
+ type Props = HTMLAttributes<"div"> & VariantProps<typeof gradientVariants>
35
+
36
+ const { class: className, variant, direction, ...props } = Astro.props
37
+ ---
38
+
39
+ <div
40
+ data-slot="gradient"
41
+ aria-hidden="true"
42
+ class={cn(gradientVariants({ variant, direction }), className)}
43
+ {...props}
44
+ >
45
+ </div>
@@ -0,0 +1 @@
1
+ export { default as Gradient } from "./gradient.astro"
@@ -85,6 +85,12 @@ const Comp = module?.default
85
85
 
86
86
  {
87
87
  Comp && foundPath()?.includes("lucide-static") && (
88
- <Comp height={24} width={24} class="size-[1em] text-base" {...props} />
88
+ <Comp
89
+ height={24}
90
+ width={24}
91
+ fill="none"
92
+ class="size-[1em] text-base"
93
+ {...props}
94
+ />
89
95
  )
90
96
  }
@@ -63,7 +63,7 @@ const marqueeVariants = cva("relative w-full", {
63
63
  class={cn(
64
64
  "",
65
65
  // container utilities
66
- "group flex w-full overflow-hidden data-[direction=bottom]:flex-col data-[direction=top]:flex-col",
66
+ "group flex w-full gap-[var(--gap)] overflow-hidden data-[direction=bottom]:flex-col data-[direction=top]:flex-col",
67
67
  marqueeVariants({ variant }),
68
68
  className
69
69
  )}
@@ -88,7 +88,7 @@ const marqueeVariants = cva("relative w-full", {
88
88
  transform: translateX(0);
89
89
  }
90
90
  to {
91
- transform: translateX(-100%);
91
+ transform: translateX(calc(-100% - var(--gap)));
92
92
  }
93
93
  }
94
94
  @keyframes marquee-y {
@@ -96,7 +96,7 @@ const marqueeVariants = cva("relative w-full", {
96
96
  transform: translateY(0);
97
97
  }
98
98
  to {
99
- transform: translateY(-100%);
99
+ transform: translateY(calc(-100% - var(--gap)));
100
100
  }
101
101
  }
102
102