Add author credits; register marquee & dashboard

Expose an author.username on Component and render a GitHub credit link in AnimationDetailPage, AnimationCard and the components landing cards. Add a new NativeMarquee component and demo (react-baseui) and register a Dashboard block and Native Marquee component in the registry (packages/registry public files). Fix registry paths for the flip-text resource and update native-user-card implementation content (motion/hover tweaks). Also update public llms timestamps. These changes wire up author attribution and add/register new UI pieces in the registry.
This commit is contained in:
moumensoliman 2026-04-14 17:58:28 +02:00
parent 4831a011e2
commit 86378f1a26
20 changed files with 567 additions and 33 deletions

View file

@ -30,6 +30,7 @@ import {
ChevronDown,
Copy,
FileText,
Github,
Info,
RefreshCw,
} from "lucide-react";
@ -621,6 +622,23 @@ export default function AnimationDetailPageClient({
<p className="mb-4 text-sm text-muted-foreground sm:text-base">
{component.description}
</p>
{component.author?.username && (
<a
href={`https://github.com/${component.author.username}`}
target="_blank"
rel="noopener noreferrer"
className="mb-4 inline-flex items-center gap-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
aria-label={`View ${component.author.username} on GitHub`}
>
<span>
By{" "}
<span className="font-medium text-foreground">
@{component.author.username}
</span>
</span>
<Github className="h-3.5 w-3.5" aria-hidden="true" />
</a>
)}
</div>
<Button
variant="outline"

View file

@ -2,6 +2,7 @@
import { Component, categoryNames } from "@/types";
import { motion } from "framer-motion";
import { Github } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
@ -39,7 +40,7 @@ export function AnimationCard({ animation }: AnimationCardProps) {
</div>
{/* Content */}
<div className="p-4">
<div className="p-4 pb-3">
<div className="mb-2 flex items-center gap-2">
<span className="rounded border border-border bg-card px-2 py-0.5 text-xs font-medium text-muted-foreground">
{categoryNames[animation.category]}
@ -73,6 +74,25 @@ export function AnimationCard({ animation }: AnimationCardProps) {
</div>
</div>
</Link>
{animation.author?.username && (
<div className="flex items-center justify-end border-t border-border/60 px-4 py-2">
<a
href={`https://github.com/${animation.author.username}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
aria-label={`View ${animation.author.username} on GitHub`}
>
<span>
By{" "}
<span className="font-medium text-foreground">
@{animation.author.username}
</span>
</span>
<Github className="h-3 w-3" aria-hidden="true" />
</a>
</div>
)}
</motion.div>
</>
);

View file

@ -6,7 +6,7 @@ import { Component, ComponentCategory, categoryNames } from "@/types";
import { Input } from "@uitripled/react-shadcn/ui/input";
import { ScrollArea } from "@uitripled/react-shadcn/ui/scroll-area";
import { AnimatePresence, motion } from "framer-motion";
import { ArrowRight, Search } from "lucide-react";
import { ArrowRight, Github, Search } from "lucide-react";
import Link from "next/link";
import { useMemo, useState } from "react";
@ -18,10 +18,11 @@ const ComponentCard = ({ component }: { component: Component }) => {
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.2 }}
className="group relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card/50 transition-all hover:-translate-y-1 hover:border-primary/50 hover:bg-card hover:shadow-md"
>
<Link
href={`/components/${component.id}`}
className="group relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card/50 p-4 transition-all hover:-translate-y-1 hover:border-primary/50 hover:bg-card hover:shadow-md"
className="flex flex-1 flex-col p-4"
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-2">
@ -56,6 +57,25 @@ const ComponentCard = ({ component }: { component: Component }) => {
</div>
)}
</Link>
{component.author?.username && (
<div className="flex items-center justify-end border-t border-border/60 px-4 py-2">
<a
href={`https://github.com/${component.author.username}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-[11px] text-muted-foreground transition-colors hover:text-foreground"
aria-label={`View ${component.author.username} on GitHub`}
>
<span>
By{" "}
<span className="font-medium text-foreground">
@{component.author.username}
</span>
</span>
<Github className="h-3 w-3" aria-hidden="true" />
</a>
</div>
)}
</motion.div>
);
};

View file

@ -2,7 +2,7 @@
> Production-ready UI components, blocks, and full pages available in shadcn/ui and Base UI, powered by Framer Motion. Copy-paste components — not an installable npm package.
**Last Updated:** 2026-03-13T00:55:24.849Z
**Last Updated:** 2026-04-14T15:39:21.158Z
- Components come in **shadcn/ui**, **Base UI**, and **Carbon** variants (the suffix in the name indicates the style system, e.g. `-shadcnui`, `-baseui`, `-carbon`)
- **Copy-paste model** — browse the docs, preview a component, then copy its code directly into your project

View file

@ -2,7 +2,7 @@
> Production-ready UI components, blocks, and full pages available in shadcn/ui and Base UI, powered by Framer Motion. Copy-paste components — not an installable npm package.
**Last Updated:** 2026-03-13T00:55:24.833Z
**Last Updated:** 2026-04-14T15:39:21.142Z
- Components come in **shadcn/ui**, **Base UI**, and **Carbon** variants (the suffix in the name indicates the style system, e.g. `-shadcnui`, `-baseui`, `-carbon`)
- **Copy-paste model** — browse the docs, preview a component, then copy its code directly into your project

View file

@ -13,8 +13,8 @@
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-flip-text-baseui.tsx",
"content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { useEffect, useState } from \"react\";\n\ninterface NativeFlipTextProps {\n /**\n * Array of words to flip through.\n */\n words: string[];\n /**\n * Duration of each word display in ms.\n * Default: 2000\n */\n duration?: number;\n className?: string;\n}\n\nexport function NativeFlipText({\n words,\n duration = 2000,\n className,\n}: NativeFlipTextProps) {\n const [index, setIndex] = useState(0);\n\n useEffect(() => {\n const interval = setInterval(() => {\n setIndex((prevIndex) => (prevIndex + 1) % words.length);\n }, duration);\n\n return () => clearInterval(interval);\n }, [words.length, duration]);\n\n return (\n <div\n className={cn(\n \"inline-flex items-center justify-center overflow-hidden\",\n className\n )}\n >\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={words[index]}\n initial={{ rotateX: -90, opacity: 0, filter: \"blur(6px)\" }}\n animate={{ rotateX: 0, opacity: 1, filter: \"blur(0px)\" }}\n exit={{ rotateX: 90, opacity: 0, filter: \"blur(6px)\" }}\n transition={{\n type: \"spring\",\n stiffness: 100,\n damping: 20,\n opacity: { duration: 0.3 },\n filter: { duration: 0.3 },\n rotateX: { duration: 0.4 },\n }}\n className=\"text-center\"\n >\n {words[index]}\n </motion.div>\n </AnimatePresence>\n </div>\n );\n}\n",
"path": "@uitripled/react-carbon/src/components/native/native-flip-text-carbon-baseui.tsx",
"content": "// Error: Could not read file @uitripled/react-carbon/src/components/native/native-flip-text-carbon-baseui.tsx\n// File not found: @uitripled/react-carbon/src/components/native/native-flip-text-carbon-baseui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-flip-text-baseui.tsx"
}

View file

@ -14,7 +14,7 @@
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-user-card-baseui.tsx",
"content": "\"use client\";\n\nimport { Avatar } from \"@base-ui/react/avatar\";\nimport { Button } from \"@base-ui/react/button\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, MotionConfig } from \"framer-motion\";\nimport { ArrowRight } from \"lucide-react\";\n\ninterface NativeUserCardProps {\n imageSrc: string;\n name: string;\n handle: string;\n href?: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst transition = {\n type: \"spring\" as const,\n stiffness: 400,\n damping: 30,\n};\n\nexport function NativeUserCard({\n imageSrc,\n name,\n handle,\n href = \"#\",\n onClick,\n className,\n}: NativeUserCardProps) {\n const CardContent = (\n <motion.div\n initial={{ opacity: 0, scale: 0.98, y: 5 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n transition={transition}\n className={cn(\n \"group relative flex w-full max-w-full items-center justify-between gap-4 rounded-lg border border-border bg-card p-1 transition-all duration-300\",\n className\n )}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"relative h-10 w-10 shrink-0 overflow-hidden rounded-lg sm:h-12 sm:w-12\">\n <Avatar.Root className=\"h-full w-full rounded-lg\">\n <Avatar.Image src={imageSrc} alt={name} className=\"h-full w-full object-cover\" />\n <Avatar.Fallback className=\"flex h-full w-full items-center justify-center bg-muted text-muted-foreground font-semibold\">\n {name.charAt(0)}\n </Avatar.Fallback>\n </Avatar.Root>\n </div>\n\n <div className=\"flex min-w-0 flex-col\">\n <h3 className=\"truncate text-sm font-semibold leading-none tracking-tight text-foreground\">\n {name}\n </h3>\n <p className=\"truncate text-xs font-medium text-muted-foreground\">\n {handle}\n </p>\n </div>\n </div>\n\n <div className=\"relative shrink-0 pl-2\">\n <Button\n className={cn(\n \"flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg bg-primary text-primary-foreground opacity-100 transition-transform duration-300 group-hover:scale-105 sm:h-12 sm:w-12\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n )}\n >\n <motion.div whileTap={{ scale: 0.95 }}>\n <ArrowRight className=\"h-3 w-3\" />\n </motion.div>\n </Button>\n </div>\n </motion.div>\n );\n\n return (\n <MotionConfig transition={transition}>\n {onClick ? (\n <Button\n onClick={onClick}\n className=\"block w-full max-w-full text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg h-auto p-0 hover:bg-transparent\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </Button>\n ) : (\n <a\n href={href}\n className=\"block w-full max-w-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </a>\n )}\n </MotionConfig>\n );\n}\n",
"content": "\"use client\";\n\nimport { Avatar } from \"@base-ui/react/avatar\";\nimport { Button } from \"@base-ui/react/button\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, MotionConfig } from \"framer-motion\";\nimport { ArrowRight } from \"lucide-react\";\n\ninterface NativeUserCardProps {\n imageSrc: string;\n name: string;\n handle: string;\n href?: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst transition = {\n type: \"spring\" as const,\n stiffness: 400,\n damping: 30,\n};\n\nexport function NativeUserCard({\n imageSrc,\n name,\n handle,\n href = \"#\",\n onClick,\n className,\n}: NativeUserCardProps) {\n const CardContent = (\n <motion.div\n initial=\"initial\"\n animate=\"animate\"\n whileHover=\"hover\"\n variants={{\n initial: { opacity: 0, scale: 0.98, y: 5 },\n animate: { opacity: 1, scale: 1, y: 0 },\n }}\n transition={transition}\n className={cn(\n \"group relative flex w-full max-w-full items-center justify-between gap-4 rounded-lg border border-border bg-card p-1 transition-all duration-300\",\n className\n )}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"relative h-10 w-10 shrink-0 overflow-hidden rounded-lg sm:h-12 sm:w-12\">\n <Avatar.Root className=\"h-full w-full rounded-lg\">\n <Avatar.Image src={imageSrc} alt={name} className=\"h-full w-full object-cover\" />\n <Avatar.Fallback className=\"flex h-full w-full items-center justify-center bg-muted text-muted-foreground font-semibold\">\n {name.charAt(0)}\n </Avatar.Fallback>\n </Avatar.Root>\n </div>\n\n <div className=\"flex min-w-0 flex-col\">\n <h3 className=\"truncate text-sm font-semibold leading-none tracking-tight text-foreground\">\n {name}\n </h3>\n <p className=\"truncate text-xs font-medium text-muted-foreground\">\n {handle}\n </p>\n </div>\n </div>\n\n <div className=\"relative shrink-0 pl-2\">\n <Button\n className={cn(\n \"flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg bg-primary text-primary-foreground opacity-100 transition-transform duration-300 sm:h-12 sm:w-12\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n )}\n >\n <motion.div whileTap={{ scale: 0.95 }}>\n <motion.span\n className=\"inline-block\"\n variants={{\n hover: { x: 3 },\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <ArrowRight className=\"h-3 w-3\" />\n </motion.span>\n </motion.div>\n </Button>\n </div>\n </motion.div>\n );\n\n return (\n <MotionConfig transition={transition}>\n {onClick ? (\n <Button\n onClick={onClick}\n className=\"block w-full max-w-full text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg h-auto p-0 hover:bg-transparent\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </Button>\n ) : (\n <a\n href={href}\n className=\"block w-full max-w-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </a>\n )}\n </MotionConfig>\n );\n}\n",
"type": "registry:component",
"target": "components/uitripled/native-user-card-baseui.tsx"
}

View file

@ -3692,7 +3692,7 @@
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-flip-text-baseui.tsx",
"path": "@uitripled/react-carbon/src/components/native/native-flip-text-carbon-baseui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-flip-text-baseui.tsx"
}

View file

@ -45,6 +45,9 @@ export type Component = {
isFree?: boolean;
display?: boolean;
availableIn?: UILibrary[]; // Which UI libraries have this component implemented
author?: {
username: string; // GitHub username — rendered as a credit link on component cards
};
};
export const categoryNames: Record<ComponentCategory, string> = {

View file

@ -0,0 +1,66 @@
"use client";
import { NativeMarquee, SideFadeGradients } from "../native-marquee-baseui";
import { Cpu, GitBranch, Globe, Layers, Shield, Zap } from "lucide-react";
const ITEMS = [
{ id: "marquee-fast", icon: <Zap aria-hidden="true" />, label: "Fast" },
{
id: "marquee-secure",
icon: <Shield aria-hidden="true" />,
label: "Secure",
},
{
id: "marquee-modular",
icon: <Layers aria-hidden="true" />,
label: "Modular",
},
{ id: "marquee-global", icon: <Globe aria-hidden="true" />, label: "Global" },
{
id: "marquee-versioned",
icon: <GitBranch aria-hidden="true" />,
label: "Versioned",
},
{
id: "marquee-performant",
icon: <Cpu aria-hidden="true" />,
label: "Performant",
},
];
export function NativeMarqueeDemo() {
return (
<div className="flex overflow-hidden gap-2 relative flex-col">
<SideFadeGradients />
<NativeMarquee
gap={8}
pauseOnTouch={true}
pauseOnHover={true}
items={ITEMS}
/>
<NativeMarquee gap={8} reverse={true} speed={5} items={ITEMS} />
</div>
);
}
export function NativeMarqueeVertical() {
return (
<div className="flex overflow-hidden gap-2 relative h-96">
<SideFadeGradients isVertical={true} />
<NativeMarquee
gap={8}
pauseOnTouch={true}
pauseOnHover={true}
items={ITEMS}
isVertical={true}
/>
<NativeMarquee
gap={8}
reverse={true}
speed={5}
items={ITEMS}
isVertical={true}
/>
</div>
);
}

View file

@ -0,0 +1,249 @@
"use client";
import { cn } from "@uitripled/utils";
import { motion, useAnimate } from "framer-motion";
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
interface NativeMarqueeItem {
id: string;
icon?: React.ReactNode;
label: string;
}
interface NativeMarqueeProps {
className?: string;
speed?: number;
items: NativeMarqueeItem[];
gap?: number;
isVertical?: boolean;
reverse?: boolean;
pauseOnHover?: boolean;
pauseOnTouch?: boolean;
}
function NativeMarquee({
speed = 10,
gap = 2,
isVertical = false,
reverse = false,
pauseOnHover = false,
pauseOnTouch = false,
items,
}: NativeMarqueeProps) {
const { containerRef, marqueeRef, multiplier, isMounted } = useMarquee({
isVertical,
});
const [scope1, animate1] = useAnimate();
const [scope2, animate2] = useAnimate();
const anim1Ref = useRef<any>(null);
const anim2Ref = useRef<any>(null);
const duration = 100 / speed;
useEffect(() => {
if (!isMounted || !scope1.current || !scope2.current) return;
const axis = isVertical ? "y" : "x";
const from = reverse ? -100 : 0;
const to = reverse ? 0 : -100;
const start = () => {
anim1Ref.current = animate1(
scope1.current,
{ [axis]: [from + "%", to + "%"] },
{ duration, ease: "linear", repeat: Infinity }
);
anim2Ref.current = animate2(
scope2.current,
{ [axis]: [from + "%", to + "%"] },
{ duration, ease: "linear", repeat: Infinity }
);
};
const animationFrame = requestAnimationFrame(start);
return () => cancelAnimationFrame(animationFrame);
}, [isMounted, isVertical, speed, reverse]);
const content = useMemo(
() => (
<ul className={`flex items-center gap-2 ${isVertical ? "flex-col" : ""}`}>
{items.map((item) => (
<li
key={item.id}
className={cn(
"flex items-center gap-2 ",
"border border-border bg-background px-4 py-2 rounded-full text-sm text-muted-foreground whitespace-nowrap",
"hover:bg-muted hover:border-foreground/20",
isVertical ? "w-full justify-center" : ""
)}
>
{item.icon}
{item.label}
</li>
))}
</ul>
),
[items]
);
const copies = useCallback(
(multiplier: number) => {
const arraySize = multiplier >= 0 ? multiplier : 0;
return [...Array(arraySize)].map((_, i) => (
<span key={i} aria-hidden="true" className="shrink-0">
{content}
</span>
));
},
[items]
);
const pause = () => {
anim1Ref.current?.pause();
anim2Ref.current?.pause();
};
const resume = () => {
anim1Ref.current?.play();
anim2Ref.current?.play();
};
const hoverProps = pauseOnHover
? { onMouseEnter: pause, onMouseLeave: resume }
: {};
const touchProps = pauseOnTouch
? { onTouchStart: pause, onTouchEnd: resume }
: {};
if (!isMounted) {
return null;
}
return (
<div
ref={containerRef}
className={cn(
"flex shrink-0 grow-0 basis-auto overflow-hidden",
isVertical ? "flex-col h-full" : "w-full "
)}
{...hoverProps}
{...touchProps}
>
<motion.div ref={scope1}>
<div
className={cn(
"flex grow-0 shrink-0 basis-auto min-w-min",
isVertical ? "flex-col" : ""
)}
style={{
marginRight: isVertical ? 0 : `${gap}px`,
marginBottom: isVertical ? `${gap}px` : 0,
gap: `${gap}px`,
}}
>
<span ref={marqueeRef} className="shrink-0">
{content}
</span>
{copies(multiplier - 1)}
</div>
</motion.div>
<motion.div ref={scope2}>
<div
className={cn(
"flex grow-0 shrink-0 basis-auto min-w-min",
isVertical ? "flex-col" : ""
)}
aria-hidden="true"
style={{
marginRight: isVertical ? 0 : `${gap}px`,
marginBottom: isVertical ? `${gap}px` : 0,
gap: `${gap}px`,
}}
>
{copies(multiplier)}
</div>
</motion.div>
</div>
);
}
function useMarquee(
{ isVertical = false }: { isVertical?: boolean } = { isVertical: false }
) {
const containerRef = useRef<HTMLDivElement>(null);
const marqueeRef = useRef<HTMLDivElement>(null);
const [multiplier, setMultiplier] = useState(1);
const [isMounted, setIsMounted] = useState(false);
const calculateMultiplier = useCallback(() => {
if (!containerRef.current || !marqueeRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const marqueeRect = marqueeRef.current.getBoundingClientRect();
let marqueeSize: number;
let containerSize: number;
let scale: number;
if (isVertical) {
containerSize = containerRect.height;
marqueeSize = marqueeRect.height;
scale = Math.ceil(containerSize / marqueeSize);
} else {
containerSize = containerRect.width;
marqueeSize = marqueeRect.width;
scale = Math.min(Math.ceil(containerSize / marqueeSize), 20);
}
setMultiplier(marqueeSize < containerSize ? scale : 1);
}, [isVertical]);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
if (!isMounted) return;
calculateMultiplier();
if (marqueeRef.current && containerRef.current) {
const resizeObserver = new ResizeObserver(() => {
calculateMultiplier();
});
resizeObserver.observe(marqueeRef.current);
return () => resizeObserver.disconnect();
}
}, [calculateMultiplier, isMounted]);
return { containerRef, marqueeRef, multiplier, isMounted };
}
function SideFadeGradients({ isVertical = false }: { isVertical?: boolean }) {
return (
<>
<div
className={cn(
"absolute z-10 pointer-events-none",
isVertical
? "inset-x-0 top-0 h-24 bg-gradient-to-b from-background to-transparent"
: "inset-y-0 left-0 w-24 bg-gradient-to-r from-background to-transparent"
)}
/>
<div
className={cn(
"absolute z-10 pointer-events-none",
isVertical
? "inset-x-0 bottom-0 h-24 bg-gradient-to-t from-background to-transparent"
: "inset-y-0 right-0 w-24 bg-gradient-to-l from-background to-transparent"
)}
/>
</>
);
}
export { NativeMarquee, useMarquee, SideFadeGradients };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,7 +14,7 @@
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-user-card-baseui.tsx",
"content": "\"use client\";\n\nimport { Avatar } from \"@base-ui/react/avatar\";\nimport { Button } from \"@base-ui/react/button\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, MotionConfig } from \"framer-motion\";\nimport { ArrowRight } from \"lucide-react\";\n\ninterface NativeUserCardProps {\n imageSrc: string;\n name: string;\n handle: string;\n href?: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst transition = {\n type: \"spring\" as const,\n stiffness: 400,\n damping: 30,\n};\n\nexport function NativeUserCard({\n imageSrc,\n name,\n handle,\n href = \"#\",\n onClick,\n className,\n}: NativeUserCardProps) {\n const CardContent = (\n <motion.div\n initial={{ opacity: 0, scale: 0.98, y: 5 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n transition={transition}\n className={cn(\n \"group relative flex w-full max-w-full items-center justify-between gap-4 rounded-lg border border-border bg-card p-1 transition-all duration-300\",\n className\n )}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"relative h-10 w-10 shrink-0 overflow-hidden rounded-lg sm:h-12 sm:w-12\">\n <Avatar.Root className=\"h-full w-full rounded-lg\">\n <Avatar.Image src={imageSrc} alt={name} className=\"h-full w-full object-cover\" />\n <Avatar.Fallback className=\"flex h-full w-full items-center justify-center bg-muted text-muted-foreground font-semibold\">\n {name.charAt(0)}\n </Avatar.Fallback>\n </Avatar.Root>\n </div>\n\n <div className=\"flex min-w-0 flex-col\">\n <h3 className=\"truncate text-sm font-semibold leading-none tracking-tight text-foreground\">\n {name}\n </h3>\n <p className=\"truncate text-xs font-medium text-muted-foreground\">\n {handle}\n </p>\n </div>\n </div>\n\n <div className=\"relative shrink-0 pl-2\">\n <Button\n className={cn(\n \"flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg bg-primary text-primary-foreground opacity-100 transition-transform duration-300 group-hover:scale-105 sm:h-12 sm:w-12\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n )}\n >\n <motion.div whileTap={{ scale: 0.95 }}>\n <ArrowRight className=\"h-3 w-3\" />\n </motion.div>\n </Button>\n </div>\n </motion.div>\n );\n\n return (\n <MotionConfig transition={transition}>\n {onClick ? (\n <Button\n onClick={onClick}\n className=\"block w-full max-w-full text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg h-auto p-0 hover:bg-transparent\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </Button>\n ) : (\n <a\n href={href}\n className=\"block w-full max-w-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </a>\n )}\n </MotionConfig>\n );\n}\n",
"content": "\"use client\";\n\nimport { Avatar } from \"@base-ui/react/avatar\";\nimport { Button } from \"@base-ui/react/button\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, MotionConfig } from \"framer-motion\";\nimport { ArrowRight } from \"lucide-react\";\n\ninterface NativeUserCardProps {\n imageSrc: string;\n name: string;\n handle: string;\n href?: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst transition = {\n type: \"spring\" as const,\n stiffness: 400,\n damping: 30,\n};\n\nexport function NativeUserCard({\n imageSrc,\n name,\n handle,\n href = \"#\",\n onClick,\n className,\n}: NativeUserCardProps) {\n const CardContent = (\n <motion.div\n initial=\"initial\"\n animate=\"animate\"\n whileHover=\"hover\"\n variants={{\n initial: { opacity: 0, scale: 0.98, y: 5 },\n animate: { opacity: 1, scale: 1, y: 0 },\n }}\n transition={transition}\n className={cn(\n \"group relative flex w-full max-w-full items-center justify-between gap-4 rounded-lg border border-border bg-card p-1 transition-all duration-300\",\n className\n )}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"relative h-10 w-10 shrink-0 overflow-hidden rounded-lg sm:h-12 sm:w-12\">\n <Avatar.Root className=\"h-full w-full rounded-lg\">\n <Avatar.Image src={imageSrc} alt={name} className=\"h-full w-full object-cover\" />\n <Avatar.Fallback className=\"flex h-full w-full items-center justify-center bg-muted text-muted-foreground font-semibold\">\n {name.charAt(0)}\n </Avatar.Fallback>\n </Avatar.Root>\n </div>\n\n <div className=\"flex min-w-0 flex-col\">\n <h3 className=\"truncate text-sm font-semibold leading-none tracking-tight text-foreground\">\n {name}\n </h3>\n <p className=\"truncate text-xs font-medium text-muted-foreground\">\n {handle}\n </p>\n </div>\n </div>\n\n <div className=\"relative shrink-0 pl-2\">\n <Button\n className={cn(\n \"flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg bg-primary text-primary-foreground opacity-100 transition-transform duration-300 sm:h-12 sm:w-12\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n )}\n >\n <motion.div whileTap={{ scale: 0.95 }}>\n <motion.span\n className=\"inline-block\"\n variants={{\n hover: { x: 3 },\n }}\n transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n >\n <ArrowRight className=\"h-3 w-3\" />\n </motion.span>\n </motion.div>\n </Button>\n </div>\n </motion.div>\n );\n\n return (\n <MotionConfig transition={transition}>\n {onClick ? (\n <Button\n onClick={onClick}\n className=\"block w-full max-w-full text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg h-auto p-0 hover:bg-transparent\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </Button>\n ) : (\n <a\n href={href}\n className=\"block w-full max-w-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg\"\n aria-label={`View profile of ${name}`}\n >\n {CardContent}\n </a>\n )}\n </MotionConfig>\n );\n}\n",
"type": "registry:component",
"target": "components/uitripled/native-user-card-baseui.tsx"
}

View file

@ -1315,7 +1315,7 @@
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/stocks-dashboard/dashboard-baseui.tsx",
"path": "@uitripled/react-baseui/src/components/components/stocks-dashboard/dashboard.tsx",
"type": "registry:block",
"target": "components/uitripled/dashboard-baseui.tsx"
}
@ -4050,6 +4050,48 @@
"category": "native",
"subcategory": null
},
{
"name": "native-marquee-baseui",
"type": "registry:component",
"title": "Native Marquee",
"description": "A smooth, infinite scrolling marquee for logos or text. (Base UI)",
"registryDependencies": [
"button"
],
"dependencies": [
"framer-motion",
"react"
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-marquee-baseui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-marquee-baseui.tsx"
}
],
"category": "native",
"subcategory": null
},
{
"name": "native-marquee-shadcnui",
"type": "registry:component",
"title": "Native Marquee",
"description": "A smooth, infinite scrolling marquee for logos or text.",
"registryDependencies": [],
"dependencies": [
"framer-motion",
"react"
],
"files": [
{
"path": "@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-marquee-shadcnui.tsx"
}
],
"category": "native",
"subcategory": null
},
{
"name": "native-morphing-button-baseui",
"type": "registry:component",

View file

@ -1315,7 +1315,7 @@
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/stocks-dashboard/dashboard-baseui.tsx",
"path": "@uitripled/react-baseui/src/components/components/stocks-dashboard/dashboard.tsx",
"type": "registry:block",
"target": "components/uitripled/dashboard-baseui.tsx"
}
@ -3692,7 +3692,7 @@
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-flip-text-baseui.tsx",
"path": "@uitripled/react-carbon/src/components/native/native-flip-text-carbon-baseui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-flip-text-baseui.tsx"
}
@ -4050,6 +4050,48 @@
"category": "native",
"subcategory": null
},
{
"name": "native-marquee-baseui",
"type": "registry:component",
"title": "Native Marquee",
"description": "A smooth, infinite scrolling marquee for logos or text. (Base UI)",
"registryDependencies": [
"button"
],
"dependencies": [
"framer-motion",
"react"
],
"files": [
{
"path": "@uitripled/react-baseui/src/components/native/native-marquee-baseui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-marquee-baseui.tsx"
}
],
"category": "native",
"subcategory": null
},
{
"name": "native-marquee-shadcnui",
"type": "registry:component",
"title": "Native Marquee",
"description": "A smooth, infinite scrolling marquee for logos or text.",
"registryDependencies": [],
"dependencies": [
"framer-motion",
"react"
],
"files": [
{
"path": "@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx",
"type": "registry:component",
"target": "components/uitripled/native-marquee-shadcnui.tsx"
}
],
"category": "native",
"subcategory": null
},
{
"name": "native-morphing-button-baseui",
"type": "registry:component",

View file

@ -823,13 +823,13 @@ export const nativeComponents: Component[] = [
availableIn: ["shadcnui", "carbon", "baseui"],
},
{
id: "native-marquee",
name: "Native Marquee",
description: "A smooth, infinite scrolling marquee for logos or text.",
category: "native",
tags: ["marquee", "scroll", "animation", "infinite", "interactive", "native"],
component: NativeMarquee,
variants: [
id: "native-marquee",
name: "Native Marquee",
description: "A smooth, infinite scrolling marquee for logos or text.",
category: "native",
tags: ["marquee", "scroll", "animation", "infinite", "interactive", "native"],
component: NativeMarquee,
variants: [
{
id: "default",
name: "Default",
@ -845,18 +845,18 @@ export const nativeComponents: Component[] = [
<SideFadeGradients isVertical={true} />
<NativeMarquee gap={8} pauseOnTouch={true} pauseOnHover={true} items={ITEMS} isVertical={true}/>
<NativeMarquee gap={8} reverse={true} speed={5} items={ITEMS} isVertical={true}/>
`
}
`,
},
],
codePath:"@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx",
duration: "1000ms",
easing: "linear",
display: true,
availableIn: ["shadcnui"],
author: {
username: "M4sayev"
}
codePath: "@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx",
duration: "1000ms",
easing: "linear",
display: true,
availableIn: ["shadcnui", "baseui"],
author: {
username: "M4sayev",
},
},
{
id: "native-notification-bell",
name: "Native Notification Bell",

View file

@ -18,4 +18,7 @@ export type Component = {
easing?: string;
baseuiComponent?: React.ComponentType<any>;
baseuiCodePath?: string;
author?: {
username: string;
};
};

View file

@ -207,6 +207,12 @@ function parseRegistryFile(content: string, fileName: string) {
}
}
// Extract optional baseuiCodePath override used when generating
// the baseui variant. Without this, generateVariantEntries falls back
// to convertToBaseuiPath which can't handle non-shadcn codePaths.
const baseuiCodePathMatch = entryText.match(/baseuiCodePath:\s*["'](@uitripled\/[^"']+)["']/);
const baseuiCodePath = baseuiCodePathMatch ? baseuiCodePathMatch[1] : null;
if (componentPath) {
entries.push({
id,
@ -215,6 +221,7 @@ function parseRegistryFile(content: string, fileName: string) {
category,
componentName,
componentPath,
baseuiCodePath,
availableIn,
});
} else {
@ -422,7 +429,7 @@ function getSubcategory(componentPath: string, category: string) {
* @param {string|null} uiLibrary - The UI library variant (shadcnui, baseui, or null for pure)
*/
function createRegistryEntry(componentData: any, uiLibrary: string | null = null) {
const { id, name, description, category, componentPath } = componentData;
const { id, name, description, category, componentPath, baseuiCodePath } = componentData;
const registryCategory = CATEGORY_MAPPING[category] || category;
const registryType = CATEGORY_TO_TYPE[category] || "registry:component";
@ -443,8 +450,9 @@ function createRegistryEntry(componentData: any, uiLibrary: string | null = null
entryName = id.endsWith("-baseui") ? id : `${id}-baseui`;
entryTitle = name;
entryDescription = description ? `${description} (Base UI)` : description;
// Convert to baseui path
entryPath = convertToBaseuiPath(componentPath, id);
// Prefer an explicit baseuiCodePath override when the entry declares one,
// since convertToBaseuiPath only knows how to rewrite react-shadcn paths.
entryPath = baseuiCodePath || convertToBaseuiPath(componentPath, id);
}
const entry: any = {