Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@gsap/react": "^2.1.2",
"@left4code/svg-renderer": "^1.0.4",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.3.3",
Expand Down
8 changes: 4 additions & 4 deletions public/r/future-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
"files": [
{
"path": "./src/components/nurui/future-button-demo.tsx",
"content": "\"use client\";\nimport { Button } from \"@/components/nurui/future-button\";\nimport { ShoppingCart } from \"lucide-react\";\n\nexport default function FutureButtonDemo() {\n return (\n <div className=\"flex justify-center py-20\">\n <Button variant=\"destructive\">\n <ShoppingCart className=\"w-4 h-4 mr-2\" />\n Purchase Item\n </Button>\n </div>\n );\n}\n",
"content": "\"use client\";\nimport { ShoppingCart } from \"lucide-react\";\nimport FutureButton from \"./future-button\";\n\nexport default function FutureButtonDemo() {\n return (\n <div className=\"flex justify-center py-20\">\n <FutureButton>\n <ShoppingCart className=\"w-4 h-4 mr-2\" />\n Purchase Item\n </FutureButton>\n </div>\n );\n}\n",
"type": "registry:component"
},
{
"path": "./src/components/nurui/future-button.tsx",
"content": "import type React from \"react\";\nimport { Frame } from \"@/components/nurui/future-frame\";\nimport { twMerge } from \"tailwind-merge\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nconst buttonVariants = cva(\n [\n \"group font-black mb-2 relative px-8 py-1 cursor-pointer transition-all [&:hover_svg]:drop-shadow-xl outline-none\",\n \"[&>span]:relative [&>span]:flex [&>span]:items-center [&>span]:justify-center [&>span]:group-hover:text-shadow-lg\",\n ],\n {\n variants: {\n variant: {\n default:\n \"[--color-frame-1-stroke:theme(colors.blue.500)] [--color-frame-1-fill:theme(colors.blue.500/0.22)] [--color-frame-2-stroke:theme(colors.blue.500)] [--color-frame-2-fill:theme(colors.blue.500/0.1)] text-blue-100 [&:hover_svg]:drop-shadow-blue-500/50\",\n accent:\n \"[--color-frame-1-stroke:theme(colors.purple.500)] [--color-frame-1-fill:theme(colors.purple.500/0.4)] [--color-frame-2-stroke:theme(colors.purple.500)] [--color-frame-2-fill:theme(colors.purple.500/0.2)] text-purple-100 [&:hover_svg]:drop-shadow-purple-500/50\",\n destructive:\n \"[--color-frame-1-stroke:theme(colors.red.500)] [--color-frame-1-fill:theme(colors.red.500/0.22)] [--color-frame-2-stroke:theme(colors.red.500)] [--color-frame-2-fill:theme(colors.red.500/0.1)] text-red-100 [&:hover_svg]:drop-shadow-red-500/50\",\n secondary:\n \"[--color-frame-1-stroke:theme(colors.gray.500)] [--color-frame-1-fill:theme(colors.gray.500/0.15)] [--color-frame-2-stroke:theme(colors.gray.500)] [--color-frame-2-fill:theme(colors.gray.500/0.1)] text-gray-100 [&:hover_svg]:drop-shadow-gray-500/50\",\n success:\n \"[--color-frame-1-stroke:theme(colors.green.500)] [--color-frame-1-fill:theme(colors.green.500/0.22)] [--color-frame-2-stroke:theme(colors.green.500)] [--color-frame-2-fill:theme(colors.green.500/0.1)] text-green-100 [&:hover_svg]:drop-shadow-green-500/50\",\n },\n shape: {\n default: \"\",\n flat: \"[--color-frame-2-stroke:transparent] [--color-frame-2-fill:transparent]\",\n simple: \"ps-8 pe-6\",\n \"tab-left\": \"\",\n \"tab-center\": \"\",\n \"tab-right\": \"\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n shape: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n children,\n variant = \"default\",\n shape = \"default\",\n customPaths,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n customPaths?: string[];\n }) {\n return (\n <button\n {...props}\n className={twMerge(buttonVariants({ variant, shape, className }))}\n >\n <div className=\"absolute inset-0 -mb-2\">\n {!customPaths && (shape == \"default\" || shape == \"flat\") && (\n <Frame\n paths={JSON.parse(\n '[{\"show\":true,\"style\":{\"strokeWidth\":\"1\",\"stroke\":\"var(--color-frame-1-stroke)\",\"fill\":\"var(--color-frame-1-fill)\"},\"path\":[[\"M\",\"17\",\"0\"],[\"L\",\"180\",\"0\"],[\"L\",\"200\",\"9.5\"],[\"L\",\"182\",\"34\"],[\"L\",\"4\",\"34\"],[\"L\",\"0\",\"25\"],[\"L\",\"17\",\"0\"]]},{\"show\":true,\"style\":{\"strokeWidth\":\"1\",\"stroke\":\"var(--color-frame-2-stroke)\",\"fill\":\"var(--color-frame-2-fill)\"},\"path\":[[\"M\",\"9\",\"34\"],[\"L\",\"178\",\"34\"],[\"L\",\"175\",\"40\"],[\"L\",\"12\",\"40\"],[\"L\",\"9\",\"34\"]]}]',\n )}\n />\n )}\n {!customPaths && shape == \"simple\" && (\n <Frame\n paths={JSON.parse(\n '[{\"show\":true,\"style\":{\"strokeWidth\":\"1\",\"stroke\":\"var(--color-frame-1-stroke)\",\"fill\":\"var(--color-frame-1-fill)\"},\"path\":[[\"M\",\"17\",\"0\"],[\"L\",\"200\",\"0\"],[\"L\",\"200\",\"34\"],[\"L\",\"3\",\"34\"],[\"L\",\"0\",\"24\"],[\"L\",\"17\",\"0\"]]},{\"show\":true,\"style\":{\"strokeWidth\":\"1\",\"stroke\":\"var(--color-frame-2-stroke)\",\"fill\":\"var(--color-frame-2-fill)\"},\"path\":[[\"M\",\"8\",\"34\"],[\"L\",\"195\",\"34\"],[\"L\",\"193\",\"40\"],[\"L\",\"10\",\"40\"],[\"L\",\"8\",\"34\"]]}]',\n )}\n />\n )}\n {customPaths?.map((customPath, customPathKey) => {\n return <Frame key={customPathKey} paths={JSON.parse(customPath)} />;\n })}\n </div>\n <span>{children}</span>\n </button>\n );\n}\n\nexport { Button };\n",
"content": "import { Frame } from \"@/components/nurui/frame\";\nimport { twMerge } from \"tailwind-merge\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\n// 🎨 Theme colors — no CSS variables, pure hex/rgba\nconst COLORS = {\n default: {\n stroke1: \"#4f46e5\",\n fill1: \"rgba(79,70,229,0.22)\",\n stroke2: \"#4f46e5\",\n fill2: \"rgba(79,70,229,0.1)\",\n text: \"#ffffff\",\n },\n accent: {\n stroke1: \"#f97316\",\n fill1: \"rgba(249,115,22,0.4)\",\n stroke2: \"#f97316\",\n fill2: \"rgba(249,115,22,0.2)\",\n text: \"#ffffff\",\n },\n destructive: {\n stroke1: \"#dc2626\",\n fill1: \"rgba(220,38,38,0.22)\",\n stroke2: \"#dc2626\",\n fill2: \"rgba(220,38,38,0.1)\",\n text: \"#ffffff\",\n },\n secondary: {\n stroke1: \"#64748b\",\n fill1: \"rgba(100,116,139,0.15)\",\n stroke2: \"#64748b\",\n fill2: \"rgba(100,116,139,0.1)\",\n text: \"#ffffff\",\n },\n success: {\n stroke1: \"#16a34a\",\n fill1: \"rgba(22,163,74,0.22)\",\n stroke2: \"#16a34a\",\n fill2: \"rgba(22,163,74,0.1)\",\n text: \"#ffffff\",\n },\n};\n\nconst buttonVariants = cva(\n \"group font-bold mb-2 relative px-8 py-2 cursor-pointer transition-all outline-none [&>span]:relative [&>span]:flex [&>span]:items-center [&>span]:justify-center\",\n {\n variants: {\n shape: {\n default: \"\",\n flat: \"\",\n simple: \"ps-8 pe-6\",\n },\n },\n defaultVariants: {\n shape: \"default\",\n },\n },\n);\n\nfunction FutureButton({\n className,\n children,\n shape = \"default\",\n enableBackdropBlur = false,\n enableViewBox = false,\n customPaths,\n textColor,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n customPaths?: string[];\n enableBackdropBlur?: boolean;\n enableViewBox?: boolean;\n bgColor?: string;\n textColor?: string;\n }) {\n const colors = COLORS.default;\n\n return (\n <button\n {...props}\n style={{\n color: textColor || colors.text,\n }}\n className={twMerge(buttonVariants({ shape, className }))}\n >\n <div className=\"absolute inset-0 -mb-2\">\n {!customPaths && (shape === \"default\" || shape === \"flat\") && (\n <Frame\n enableBackdropBlur={enableBackdropBlur}\n enableViewBox={enableViewBox}\n paths={[\n {\n show: true,\n style: {\n strokeWidth: \"1\",\n stroke: colors.stroke1,\n fill: colors.fill1,\n },\n path: [\n [\"M\", \"17\", \"0\"],\n [\"L\", \"100% - 7\", \"0\"],\n [\"L\", \"100% + 0\", \"0% + 9.5\"],\n [\"L\", \"100% - 18\", \"100% - 6\"],\n [\"L\", \"4\", \"100% - 6\"],\n [\"L\", \"0\", \"100% - 15\"],\n [\"L\", \"17\", \"0\"],\n ],\n },\n {\n show: true,\n style: {\n strokeWidth: \"1\",\n stroke: colors.stroke2,\n fill: colors.fill2,\n },\n path: [\n [\"M\", \"9\", \"100% - 6\"],\n [\"L\", \"100% - 22\", \"100% - 6\"],\n [\"L\", \"100% - 25\", \"100% + 0\"],\n [\"L\", \"12\", \"100% + 0\"],\n [\"L\", \"9\", \"100% - 6\"],\n ],\n },\n ]}\n />\n )}\n\n {!customPaths && shape === \"simple\" && (\n <Frame\n enableBackdropBlur={enableBackdropBlur}\n enableViewBox={enableViewBox}\n paths={[\n {\n show: true,\n style: {\n strokeWidth: \"1\",\n stroke: colors.stroke1,\n fill: colors.fill1,\n },\n path: [\n [\"M\", \"17\", \"0\"],\n [\"L\", \"100% - 0\", \"0\"],\n [\"L\", \"100% - 0\", \"100% - 6\"],\n [\"L\", \"0% + 3\", \"100% - 6\"],\n [\"L\", \"0% - 0\", \"100% - 16\"],\n [\"L\", \"17\", \"0\"],\n ],\n },\n {\n show: true,\n style: {\n strokeWidth: \"1\",\n stroke: colors.stroke2,\n fill: colors.fill2,\n },\n path: [\n [\"M\", \"8\", \"100% - 6\"],\n [\"L\", \"100% - 5\", \"100% - 6\"],\n [\"L\", \"100% - 7\", \"100% - 0\"],\n [\"L\", \"10\", \"100% - 0\"],\n [\"L\", \"8\", \"100% - 6\"],\n ],\n },\n ]}\n />\n )}\n\n {customPaths?.map((customPath, i) => (\n <Frame key={i} paths={JSON.parse(customPath)} />\n ))}\n </div>\n <span>{children}</span>\n </button>\n );\n}\n\nexport default FutureButton;\n",
"type": "registry:component"
},
{
"path": "./src/components/nurui/future-frame.tsx",
"content": "\"use client\";\n\nimport type React from \"react\";\n\nimport { useRef, useEffect } from \"react\";\nimport { twMerge } from \"tailwind-merge\";\n// import type { Paths } from \"@/types/frame\"\n\ntype PathCommand = [\"M\" | \"L\", string, string];\n\ninterface PathProps {\n show: boolean;\n style: {\n strokeWidth: string;\n stroke: string;\n fill: string;\n };\n path: PathCommand[];\n}\n\ntype Paths = PathProps[];\n\nfunction setupSvgRenderer({\n el,\n paths,\n enableBackdropBlur,\n}: {\n el: SVGSVGElement;\n paths: Paths;\n enableBackdropBlur: boolean | undefined;\n}) {\n // Clear previous content\n while (el.firstChild) {\n el.removeChild(el.firstChild);\n }\n\n // Optionally add backdrop blur filter\n if (enableBackdropBlur) {\n const defs = document.createElementNS(\"http://www.w3.org/2000/svg\", \"defs\");\n const filter = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"filter\",\n );\n filter.setAttribute(\"id\", \"backdrop-blur\");\n filter.innerHTML = `\n <feGaussianBlur stdDeviation=\"8\" result=\"blur\"/>\n <feMerge>\n <feMergeNode in=\"blur\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n `;\n defs.appendChild(filter);\n el.appendChild(defs);\n }\n\n // Convert path commands to SVG path string\n const convertPathToString = (pathCommands: [string, string, string][]) => {\n return (\n pathCommands\n .map(([command, x, y]) => {\n // Replace percentage-based coordinates with actual values\n const processCoordinate = (coord: string, isX: boolean) => {\n if (coord.includes(\"%\")) {\n // For demo purposes, we'll use viewport-relative values\n return coord\n .replace(\"100%\", isX ? \"200\" : \"40\")\n .replace(\"0%\", \"0\");\n }\n return coord;\n };\n\n const processedX = processCoordinate(x, true);\n const processedY = processCoordinate(y, false);\n\n return `${command}${processedX},${processedY}`;\n })\n .join(\" \") + \" Z\"\n );\n };\n\n // Render each path\n paths.forEach((pathProps) => {\n if (!pathProps.show) return;\n\n const path = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n\n // Set the path data\n const pathString = convertPathToString(pathProps.path);\n path.setAttribute(\"d\", pathString);\n\n // Apply styles\n Object.entries(pathProps.style).forEach(([key, value]) => {\n if (key === \"strokeWidth\") {\n path.setAttribute(\"stroke-width\", value);\n } else {\n path.setAttribute(key, value);\n }\n });\n\n if (enableBackdropBlur) {\n path.setAttribute(\"filter\", \"url(#backdrop-blur)\");\n }\n\n el.appendChild(path);\n });\n\n return {\n destroy() {\n while (el.firstChild) {\n el.removeChild(el.firstChild);\n }\n },\n };\n}\n\nfunction Frame({\n className,\n paths,\n enableBackdropBlur,\n ...props\n}: {\n paths: Paths;\n enableBackdropBlur?: boolean;\n} & React.ComponentProps<\"svg\">) {\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n useEffect(() => {\n if (svgRef.current && svgRef.current.parentElement) {\n const instance = setupSvgRenderer({\n el: svgRef.current,\n paths,\n enableBackdropBlur,\n });\n\n return () => instance.destroy();\n }\n }, [paths, enableBackdropBlur]);\n\n return (\n <svg\n {...props}\n className={twMerge([\"absolute inset-0 size-full\", className])}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n />\n );\n}\n\nexport { Frame };\n",
"path": "./src/components/nurui/frame.tsx",
"content": "import { useRef, useEffect } from \"react\";\nimport { twMerge } from \"tailwind-merge\";\nimport { type Paths, setupSvgRenderer } from \"@left4code/svg-renderer\";\n\nfunction Frame({\n className,\n paths,\n enableBackdropBlur,\n enableViewBox,\n ...props\n}: {\n paths: Paths;\n enableBackdropBlur?: boolean;\n enableViewBox?: boolean;\n} & React.ComponentProps<\"svg\">) {\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n useEffect(() => {\n if (svgRef.current && svgRef.current.parentElement) {\n const instance = setupSvgRenderer({\n el: svgRef.current,\n paths,\n enableBackdropBlur,\n enableViewBox,\n });\n\n return () => instance.destroy();\n }\n }, [paths, enableViewBox, enableBackdropBlur]);\n\n return (\n <svg\n {...props}\n className={twMerge([\"absolute inset-0 size-full\", className])}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n />\n );\n}\n\nexport { Frame };\n",
"type": "registry:component"
}
]
Expand Down
Loading