+ )}
>
);
diff --git a/apps/docs/components/components-landing-page.tsx b/apps/docs/components/components-landing-page.tsx
index 38f3e37..504c7cc 100644
--- a/apps/docs/components/components-landing-page.tsx
+++ b/apps/docs/components/components-landing-page.tsx
@@ -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"
>
@@ -56,6 +57,25 @@ const ComponentCard = ({ component }: { component: Component }) => {
)}
+ {component.author?.username && (
+
+ )}
);
};
diff --git a/apps/docs/public/llms-full.txt b/apps/docs/public/llms-full.txt
index 6e1cea0..9f8a971 100644
--- a/apps/docs/public/llms-full.txt
+++ b/apps/docs/public/llms-full.txt
@@ -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
diff --git a/apps/docs/public/llms.txt b/apps/docs/public/llms.txt
index 0064c71..b047a10 100644
--- a/apps/docs/public/llms.txt
+++ b/apps/docs/public/llms.txt
@@ -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
diff --git a/apps/docs/public/r/native-flip-text-baseui.json b/apps/docs/public/r/native-flip-text-baseui.json
index 749ea01..0ec0326 100644
--- a/apps/docs/public/r/native-flip-text-baseui.json
+++ b/apps/docs/public/r/native-flip-text-baseui.json
@@ -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
\n
\n \n {words[index]}\n \n \n
\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"
}
diff --git a/apps/docs/public/r/native-user-card-baseui.json b/apps/docs/public/r/native-user-card-baseui.json
index 0be8afb..9f47037 100644
--- a/apps/docs/public/r/native-user-card-baseui.json
+++ b/apps/docs/public/r/native-user-card-baseui.json
@@ -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
\n \n
\n
\n \n \n {name.charAt(0)}\n \n \n
\n\n
\n
\n {name}\n
\n
\n {handle}\n
\n
\n
\n\n \n \n );\n\n return (\n
\n {onClick ? (\n \n ) : (\n \n {CardContent}\n \n )}\n \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
\n \n
\n
\n \n \n {name.charAt(0)}\n \n \n
\n\n
\n
\n {name}\n
\n
\n {handle}\n
\n
\n
\n\n \n \n );\n\n return (\n
\n {onClick ? (\n \n ) : (\n \n {CardContent}\n \n )}\n \n );\n}\n",
"type": "registry:component",
"target": "components/uitripled/native-user-card-baseui.tsx"
}
diff --git a/apps/docs/registry.json b/apps/docs/registry.json
index 63b4ffc..e48a1ee 100644
--- a/apps/docs/registry.json
+++ b/apps/docs/registry.json
@@ -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"
}
diff --git a/apps/docs/types/index.ts b/apps/docs/types/index.ts
index 5d1d4a8..5a16f3b 100644
--- a/apps/docs/types/index.ts
+++ b/apps/docs/types/index.ts
@@ -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
= {
diff --git a/packages/components/react-baseui/src/components/native/demo/native-marquee-demo.tsx b/packages/components/react-baseui/src/components/native/demo/native-marquee-demo.tsx
new file mode 100644
index 0000000..a098b57
--- /dev/null
+++ b/packages/components/react-baseui/src/components/native/demo/native-marquee-demo.tsx
@@ -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: , label: "Fast" },
+ {
+ id: "marquee-secure",
+ icon: ,
+ label: "Secure",
+ },
+ {
+ id: "marquee-modular",
+ icon: ,
+ label: "Modular",
+ },
+ { id: "marquee-global", icon: , label: "Global" },
+ {
+ id: "marquee-versioned",
+ icon: ,
+ label: "Versioned",
+ },
+ {
+ id: "marquee-performant",
+ icon: ,
+ label: "Performant",
+ },
+];
+
+export function NativeMarqueeDemo() {
+ return (
+
+
+
+
+
+ );
+}
+
+export function NativeMarqueeVertical() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/components/react-baseui/src/components/native/native-marquee-baseui.tsx b/packages/components/react-baseui/src/components/native/native-marquee-baseui.tsx
new file mode 100644
index 0000000..4d5c811
--- /dev/null
+++ b/packages/components/react-baseui/src/components/native/native-marquee-baseui.tsx
@@ -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(null);
+ const anim2Ref = useRef(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(
+ () => (
+
+ {items.map((item) => (
+ -
+ {item.icon}
+ {item.label}
+
+ ))}
+
+ ),
+ [items]
+ );
+
+ const copies = useCallback(
+ (multiplier: number) => {
+ const arraySize = multiplier >= 0 ? multiplier : 0;
+ return [...Array(arraySize)].map((_, i) => (
+
+ {content}
+
+ ));
+ },
+ [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 (
+
+
+
+
+ {content}
+
+ {copies(multiplier - 1)}
+
+
+
+
+ {copies(multiplier)}
+
+
+
+ );
+}
+
+function useMarquee(
+ { isVertical = false }: { isVertical?: boolean } = { isVertical: false }
+) {
+ const containerRef = useRef(null);
+ const marqueeRef = useRef(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 (
+ <>
+
+
+ >
+ );
+}
+
+export { NativeMarquee, useMarquee, SideFadeGradients };
diff --git a/packages/registry/public/r/dashboard-baseui.json b/packages/registry/public/r/dashboard-baseui.json
new file mode 100644
index 0000000..defa0bd
--- /dev/null
+++ b/packages/registry/public/r/dashboard-baseui.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "dashboard-baseui",
+ "type": "registry:block",
+ "title": "Dashboard",
+ "description": "Interactive stock portfolio dashboard with status cards, data table, and detailed stock information modal (Base UI)",
+ "registryDependencies": [
+ "button"
+ ],
+ "dependencies": [
+ "framer-motion",
+ "react"
+ ],
+ "files": [
+ {
+ "path": "@uitripled/react-baseui/src/components/components/stocks-dashboard/dashboard.tsx",
+ "content": "\"use client\";\n\nimport type React from \"react\";\n\nimport { NativeButton } from \"../../native/baseui/native-button-baseui\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, type Variants } from \"framer-motion\";\nimport {\n Activity,\n BarChart3,\n ChevronRight,\n Clock,\n DollarSign,\n Download,\n Menu,\n Percent,\n Settings,\n TrendingDown,\n TrendingUp,\n Users,\n Zap,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n CartesianGrid,\n Line,\n LineChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\n\n// ============================================================================\n// TYPES & INTERFACES\n// ============================================================================\n\ninterface MetricCardProps {\n label: string;\n value: string;\n change: string;\n trend: \"up\" | \"down\";\n icon: React.ReactNode;\n}\n\ninterface ChartCardProps {\n title: string;\n description: string;\n data: Array<{ name: string; value: number }>;\n dataKey: string;\n height?: number;\n}\n\ninterface DetailItem {\n label: string;\n value: string;\n subtitle: string;\n}\n\ninterface DetailedCardProps {\n title: string;\n items: DetailItem[];\n}\n\n// ============================================================================\n// LOCAL COMPONENTS\n// ============================================================================\n\ninterface BadgeProps extends React.HTMLAttributes {\n variant?: \"default\" | \"secondary\" | \"destructive\" | \"outline\";\n}\n\nfunction Badge({ className, variant = \"default\", ...props }: BadgeProps) {\n return (\n \n );\n}\n\n// ============================================================================\n// STATIC CHART DATA - EXTENSIVE DUMMY DATA FOR PROPER RENDERING\n// ============================================================================\n\nconst USER_GROWTH_DATA = [\n { name: \"Week 1\", value: 2400 },\n { name: \"Week 2\", value: 3210 },\n { name: \"Week 3\", value: 2290 },\n { name: \"Week 4\", value: 2000 },\n { name: \"Week 5\", value: 2181 },\n { name: \"Week 6\", value: 2500 },\n { name: \"Week 7\", value: 2100 },\n { name: \"Week 8\", value: 2200 },\n { name: \"Week 9\", value: 2290 },\n { name: \"Week 10\", value: 2000 },\n { name: \"Week 11\", value: 2181 },\n { name: \"Week 12\", value: 2500 },\n { name: \"Week 13\", value: 2100 },\n];\n\nconst REVENUE_TREND_DATA = [\n { name: \"Week 1\", value: 4000 },\n { name: \"Week 2\", value: 3000 },\n { name: \"Week 3\", value: 2000 },\n { name: \"Week 4\", value: 2780 },\n { name: \"Week 5\", value: 1890 },\n { name: \"Week 6\", value: 2390 },\n { name: \"Week 7\", value: 3490 },\n { name: \"Week 8\", value: 4000 },\n { name: \"Week 9\", value: 3500 },\n { name: \"Week 10\", value: 4200 },\n { name: \"Week 11\", value: 3800 },\n { name: \"Week 12\", value: 4500 },\n { name: \"Week 13\", value: 4100 },\n];\n\n// ============================================================================\n// ANIMATION VARIANTS\n// ============================================================================\n\nconst containerVariants: Variants = {\n hidden: { opacity: 0 },\n visible: {\n opacity: 1,\n transition: {\n staggerChildren: 0.1,\n delayChildren: 0.2,\n },\n },\n};\n\nconst itemVariants: Variants = {\n hidden: { opacity: 0, y: 20 },\n visible: {\n opacity: 1,\n y: 0,\n transition: { duration: 0.4 },\n },\n};\n\n// ============================================================================\n// COMPONENTS\n// ============================================================================\n\n// Metric Card Component\nfunction MetricCard({ label, value, change, trend, icon }: MetricCardProps) {\n const isPositive = trend === \"up\";\n const TrendIcon = isPositive ? TrendingUp : TrendingDown;\n\n return (\n \n \n\n \n
\n
\n {icon}\n
\n
\n \n {change}\n
\n
\n\n
\n
\n {label}\n
\n
\n {value}\n
\n
\n
\n \n );\n}\n\n// Chart Card Component\nfunction ChartCard({\n title,\n description,\n data,\n dataKey,\n height = 300,\n}: ChartCardProps) {\n return (\n \n \n\n \n
\n
\n
\n {title}\n
\n
{description}
\n
\n
\n
\n\n {/* Chart Container with proper sizing */}\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n \n );\n}\n\n// Detailed Card Component\nfunction DetailedCard({ title, items }: DetailedCardProps) {\n return (\n \n \n\n \n
\n {title}\n
\n\n
\n {items.map((item, index) => (\n
\n \n
\n
\n {item.label}\n
\n
{item.subtitle}
\n
\n
\n
\n {item.value}\n
\n
\n
\n
\n \n ))}\n
\n
\n \n );\n}\n\n// Dashboard Navigation Component\nfunction DashboardNav() {\n const [isOpen, setIsOpen] = useState(false);\n\n const navItems = [\n { label: \"Overview\", icon: BarChart3 },\n { label: \"Activity\", icon: Activity },\n { label: \"Users\", icon: Users },\n { label: \"Analytics\", icon: TrendingUp },\n { label: \"History\", icon: Clock },\n ];\n\n return (\n \n \n
\n
\n Dashboard\n
\n\n {/* Mobile menu button */}\n
\n\n {/* Desktop nav */}\n
\n {navItems.map((item) => {\n const Icon = item.icon;\n return (\n \n \n \n {item.label}\n \n \n );\n })}\n
\n
\n\n {/* Mobile nav */}\n {isOpen && (\n
\n {navItems.map((item) => {\n const Icon = item.icon;\n return (\n \n \n \n {item.label}\n \n \n );\n })}\n \n )}\n
\n \n );\n}\n\n// Dashboard Header Component\nfunction DashboardHeader() {\n return (\n \n \n
\n
\n \n Live Dashboard\n \n\n
\n Performance Overview\n
\n
\n Monitor your application metrics, user activity, and system health\n in real-time with detailed insights and historical data trends.\n
\n
\n\n
\n \n \n \n \n \n \n
\n
\n \n );\n}\n\n// Main Dashboard Grid\nfunction DashboardGrid() {\n return (\n \n {/* Top KPI Row - Replaced emoji icons with lucide icons */}\n \n }\n />\n }\n />\n }\n />\n \n }\n />\n \n\n {/* Charts Row */}\n \n \n \n \n\n {/* Detailed Cards Row */}\n \n \n \n \n \n \n );\n}\n\n// ============================================================================\n// MAIN PAGE COMPONENT\n// ============================================================================\n\nexport function DashboardPage() {\n return (\n \n {/* Glassmorphism background blobs */}\n \n\n {/* Navigation */}\n \n\n {/* Main Content */}\n \n \n );\n}\n",
+ "type": "registry:block",
+ "target": "components/uitripled/dashboard-baseui.tsx"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/registry/public/r/native-marquee-baseui.json b/packages/registry/public/r/native-marquee-baseui.json
new file mode 100644
index 0000000..b37ad33
--- /dev/null
+++ b/packages/registry/public/r/native-marquee-baseui.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "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",
+ "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useAnimate } from \"framer-motion\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\ninterface NativeMarqueeItem {\n id: string;\n icon?: React.ReactNode;\n label: string;\n}\n\ninterface NativeMarqueeProps {\n className?: string;\n speed?: number;\n items: NativeMarqueeItem[];\n gap?: number;\n isVertical?: boolean;\n reverse?: boolean;\n pauseOnHover?: boolean;\n pauseOnTouch?: boolean;\n}\n\nfunction NativeMarquee({\n speed = 10,\n gap = 2,\n isVertical = false,\n reverse = false,\n pauseOnHover = false,\n pauseOnTouch = false,\n items,\n}: NativeMarqueeProps) {\n const { containerRef, marqueeRef, multiplier, isMounted } = useMarquee({\n isVertical,\n });\n\n const [scope1, animate1] = useAnimate();\n const [scope2, animate2] = useAnimate();\n const anim1Ref = useRef(null);\n const anim2Ref = useRef(null);\n\n const duration = 100 / speed;\n\n useEffect(() => {\n if (!isMounted || !scope1.current || !scope2.current) return;\n\n const axis = isVertical ? \"y\" : \"x\";\n const from = reverse ? -100 : 0;\n const to = reverse ? 0 : -100;\n\n const start = () => {\n anim1Ref.current = animate1(\n scope1.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n anim2Ref.current = animate2(\n scope2.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n };\n\n const animationFrame = requestAnimationFrame(start);\n\n return () => cancelAnimationFrame(animationFrame);\n }, [isMounted, isVertical, speed, reverse]);\n\n const content = useMemo(\n () => (\n \n {items.map((item) => (\n - \n {item.icon}\n {item.label}\n
\n ))}\n
\n ),\n [items]\n );\n\n const copies = useCallback(\n (multiplier: number) => {\n const arraySize = multiplier >= 0 ? multiplier : 0;\n return [...Array(arraySize)].map((_, i) => (\n \n {content}\n \n ));\n },\n [items]\n );\n\n const pause = () => {\n anim1Ref.current?.pause();\n anim2Ref.current?.pause();\n };\n\n const resume = () => {\n anim1Ref.current?.play();\n anim2Ref.current?.play();\n };\n\n const hoverProps = pauseOnHover\n ? { onMouseEnter: pause, onMouseLeave: resume }\n : {};\n const touchProps = pauseOnTouch\n ? { onTouchStart: pause, onTouchEnd: resume }\n : {};\n\n if (!isMounted) {\n return null;\n }\n\n return (\n \n
\n \n \n {content}\n \n {copies(multiplier - 1)}\n
\n \n
\n \n {copies(multiplier)}\n
\n \n
\n );\n}\n\nfunction useMarquee(\n { isVertical = false }: { isVertical?: boolean } = { isVertical: false }\n) {\n const containerRef = useRef(null);\n const marqueeRef = useRef(null);\n const [multiplier, setMultiplier] = useState(1);\n const [isMounted, setIsMounted] = useState(false);\n\n const calculateMultiplier = useCallback(() => {\n if (!containerRef.current || !marqueeRef.current) return;\n const containerRect = containerRef.current.getBoundingClientRect();\n const marqueeRect = marqueeRef.current.getBoundingClientRect();\n\n let marqueeSize: number;\n let containerSize: number;\n let scale: number;\n if (isVertical) {\n containerSize = containerRect.height;\n marqueeSize = marqueeRect.height;\n scale = Math.ceil(containerSize / marqueeSize);\n } else {\n containerSize = containerRect.width;\n marqueeSize = marqueeRect.width;\n scale = Math.min(Math.ceil(containerSize / marqueeSize), 20);\n }\n setMultiplier(marqueeSize < containerSize ? scale : 1);\n }, [isVertical]);\n\n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n useEffect(() => {\n if (!isMounted) return;\n calculateMultiplier();\n if (marqueeRef.current && containerRef.current) {\n const resizeObserver = new ResizeObserver(() => {\n calculateMultiplier();\n });\n resizeObserver.observe(marqueeRef.current);\n return () => resizeObserver.disconnect();\n }\n }, [calculateMultiplier, isMounted]);\n\n return { containerRef, marqueeRef, multiplier, isMounted };\n}\n\nfunction SideFadeGradients({ isVertical = false }: { isVertical?: boolean }) {\n return (\n <>\n \n \n >\n );\n}\n\nexport { NativeMarquee, useMarquee, SideFadeGradients };\n",
+ "type": "registry:component",
+ "target": "components/uitripled/native-marquee-baseui.tsx"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/registry/public/r/native-marquee-shadcnui.json b/packages/registry/public/r/native-marquee-shadcnui.json
new file mode 100644
index 0000000..ba60025
--- /dev/null
+++ b/packages/registry/public/r/native-marquee-shadcnui.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "native-marquee-shadcnui",
+ "type": "registry:component",
+ "title": "Native Marquee",
+ "description": "A smooth, infinite scrolling marquee for logos or text.",
+ "dependencies": [
+ "framer-motion",
+ "react"
+ ],
+ "files": [
+ {
+ "path": "@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx",
+ "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useAnimate } from \"framer-motion\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\n\n\ninterface NativeMarqueeItem {\n id: string;\n icon?: React.ReactNode;\n label: string;\n}\n\ninterface NativeMarqueeProps {\n className?: string;\n speed?: number;\n items: NativeMarqueeItem[];\n gap?: number;\n isVertical?: boolean;\n reverse?: boolean;\n pauseOnHover?: boolean;\n pauseOnTouch?: boolean;\n}\n\nfunction NativeMarquee({\n speed = 10,\n gap = 2,\n isVertical = false,\n reverse = false,\n pauseOnHover = false,\n pauseOnTouch = false,\n items,\n}: NativeMarqueeProps) {\n const { containerRef, marqueeRef, multiplier, isMounted } = useMarquee({\n isVertical,\n });\n\n const [scope1, animate1] = useAnimate();\n const [scope2, animate2] = useAnimate();\n const anim1Ref = useRef(null);\n const anim2Ref = useRef(null);\n\n const duration = 100 / speed;\n\n useEffect(() => {\n if (!isMounted || !scope1.current || !scope2.current) return;\n\n const axis = isVertical ? \"y\" : \"x\";\n const from = reverse ? -100 : 0;\n const to = reverse ? 0 : -100;\n\n const start = () => {\n anim1Ref.current = animate1(\n scope1.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n anim2Ref.current = animate2(\n scope2.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n };\n\n const animationFrame = requestAnimationFrame(start);\n\n return () => cancelAnimationFrame(animationFrame);\n }, [isMounted, isVertical, speed, reverse]);\n\n const content = useMemo(\n () => (\n \n {items.map((item) => (\n - \n {item.icon}\n {item.label}\n
\n ))}\n
\n ),\n [items]\n );\n\n const copies = useCallback(\n (multiplier: number) => {\n const arraySize = multiplier >= 0 ? multiplier : 0;\n return [...Array(arraySize)].map((_, i) => (\n \n {content}\n \n ));\n },\n [items]\n );\n\n const pause = () => {\n anim1Ref.current?.pause();\n anim2Ref.current?.pause();\n };\n\n const resume = () => {\n anim1Ref.current?.play();\n anim2Ref.current?.play();\n };\n\n const hoverProps = pauseOnHover\n ? { onMouseEnter: pause, onMouseLeave: resume }\n : {};\n const touchProps = pauseOnTouch\n ? { onTouchStart: pause, onTouchEnd: resume }\n : {};\n\n if (!isMounted) {\n return null;\n }\n\n return (\n \n
\n \n {/* to match the other span elements paddings */}\n \n {content}\n \n {copies(multiplier - 1)}\n
\n \n
\n \n {copies(multiplier)}\n
\n \n
\n );\n}\n\nfunction useMarquee(\n { isVertical = false }: { isVertical?: boolean } = { isVertical: false }\n) {\n const containerRef = useRef(null);\n const marqueeRef = useRef(null);\n const [multiplier, setMultiplier] = useState(1);\n const [isMounted, setIsMounted] = useState(false);\n\n const calculateMultiplier = useCallback(() => {\n if (!containerRef.current || !marqueeRef.current) return;\n const containerRect = containerRef.current.getBoundingClientRect();\n const marqueeRect = marqueeRef.current.getBoundingClientRect();\n\n let marqueeSize: number;\n let containerSize: number;\n let scale: number;\n if (isVertical) {\n containerSize = containerRect.height;\n marqueeSize = marqueeRect.height;\n scale = Math.ceil(containerSize / marqueeSize);\n } else {\n containerSize = containerRect.width;\n marqueeSize = marqueeRect.width;\n scale = Math.min(Math.ceil(containerSize / marqueeSize), 20);\n }\n setMultiplier(marqueeSize < containerSize ? scale : 1);\n }, [isVertical]);\n\n // ensure the marquee is mounted on the client\n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n useEffect(() => {\n if (!isMounted) return;\n calculateMultiplier();\n if (marqueeRef.current && containerRef.current) {\n const resizeObserver = new ResizeObserver(() => {\n calculateMultiplier();\n });\n resizeObserver.observe(marqueeRef.current);\n return () => resizeObserver.disconnect();\n }\n }, [calculateMultiplier, isMounted]);\n\n return { containerRef, marqueeRef, multiplier, isMounted };\n}\n\nfunction SideFadeGradients({ isVertical = false }: { isVertical?: boolean }) {\n return (\n <>\n \n \n >\n );\n}\nexport { NativeMarquee, useMarquee, SideFadeGradients };\n",
+ "type": "registry:component",
+ "target": "components/uitripled/native-marquee-shadcnui.tsx"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/registry/public/r/native-user-card-baseui.json b/packages/registry/public/r/native-user-card-baseui.json
index 0be8afb..9f47037 100644
--- a/packages/registry/public/r/native-user-card-baseui.json
+++ b/packages/registry/public/r/native-user-card-baseui.json
@@ -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 \n \n
\n
\n \n \n {name.charAt(0)}\n \n \n
\n\n
\n
\n {name}\n
\n
\n {handle}\n
\n
\n
\n\n \n \n );\n\n return (\n \n {onClick ? (\n \n ) : (\n \n {CardContent}\n \n )}\n \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 \n \n
\n
\n \n \n {name.charAt(0)}\n \n \n
\n\n
\n
\n {name}\n
\n
\n {handle}\n
\n
\n
\n\n \n \n );\n\n return (\n \n {onClick ? (\n \n ) : (\n \n {CardContent}\n \n )}\n \n );\n}\n",
"type": "registry:component",
"target": "components/uitripled/native-user-card-baseui.tsx"
}
diff --git a/packages/registry/public/r/registry.json b/packages/registry/public/r/registry.json
index e48a1ee..b33a0c6 100644
--- a/packages/registry/public/r/registry.json
+++ b/packages/registry/public/r/registry.json
@@ -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",
diff --git a/packages/registry/registry.json b/packages/registry/registry.json
index 63b4ffc..b33a0c6 100644
--- a/packages/registry/registry.json
+++ b/packages/registry/registry.json
@@ -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",
diff --git a/packages/registry/src/registry/native.tsx b/packages/registry/src/registry/native.tsx
index c248e96..12ab9ff 100644
--- a/packages/registry/src/registry/native.tsx
+++ b/packages/registry/src/registry/native.tsx
@@ -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[] = [
- `
- }
+ `,
+ },
],
- 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",
diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts
index e88387e..b935556 100644
--- a/packages/registry/src/types.ts
+++ b/packages/registry/src/types.ts
@@ -18,4 +18,7 @@ export type Component = {
easing?: string;
baseuiComponent?: React.ComponentType;
baseuiCodePath?: string;
+ author?: {
+ username: string;
+ };
};
diff --git a/packages/scripts/src/sync-registry.ts b/packages/scripts/src/sync-registry.ts
index 42a25d2..2bd55b6 100644
--- a/packages/scripts/src/sync-registry.ts
+++ b/packages/scripts/src/sync-registry.ts
@@ -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 = {