| import { forwardRef } from "react"; | ||
| const VARIANTS = { | ||
| primary: { | ||
| base: "text-white border-transparent", | ||
| bg: "var(--atlas-gradient)", | ||
| shadow: "0 4px 12px -3px rgba(15, 23, 42, 0.32)", | ||
| hoverShadow: | ||
| "0 6px 16px -3px rgba(15, 23, 42, 0.42), inset 0 0 0 1px rgba(255,255,255,0.08)", | ||
| }, | ||
| outline: | ||
| "bg-card border border-border text-foreground hover:bg-atlas-surface-2 hover:border-atlas-border-strong", | ||
| ghost: | ||
| "bg-transparent border-transparent text-atlas-muted hover:bg-atlas-surface-2 hover:text-foreground", | ||
| default: | ||
| "bg-atlas-surface-2 border-transparent text-foreground hover:bg-atlas-surface-3", | ||
| danger: | ||
| "bg-atlas-danger border-transparent text-white hover:opacity-90", | ||
| }; | ||
| const SIZES = { | ||
| sm: "h-[30px] px-2.5 text-[12.5px] rounded-[7px]", | ||
| md: "h-[36px] px-3.5 text-[13px] rounded-lg", | ||
| lg: "h-[42px] px-[18px] text-[14px] rounded-lg", | ||
| }; | ||
| const ICON_SIZES = { | ||
| sm: "h-[30px] w-[30px] p-0 rounded-[7px]", | ||
| md: "h-[36px] w-[36px] p-0 rounded-lg", | ||
| lg: "h-[42px] w-[42px] p-0 rounded-lg", | ||
| }; | ||
| export const Button = forwardRef(function Button( | ||
| { | ||
| variant = "primary", | ||
| size = "md", | ||
| icon = false, | ||
| className = "", | ||
| disabled = false, | ||
| type = "button", | ||
| children, | ||
| ...rest | ||
| }, | ||
| ref | ||
| ) { | ||
| const isPrimary = variant === "primary"; | ||
| const variantStyle = VARIANTS[variant] || VARIANTS.primary; | ||
| const sizeCls = icon ? ICON_SIZES[size] || ICON_SIZES.md : SIZES[size] || SIZES.md; | ||
| const base = | ||
| "inline-flex items-center justify-center gap-1.5 font-medium whitespace-nowrap border transition-[background,box-shadow,transform,border-color,opacity] outline-none"; | ||
| const disabledCls = disabled ? "opacity-55 cursor-not-allowed" : "cursor-pointer"; | ||
| const classes = isPrimary | ||
| ? `${base} ${sizeCls} ${variantStyle.base} ${disabledCls} ${className}` | ||
| : `${base} ${sizeCls} ${variantStyle} ${disabledCls} ${className}`; | ||
| const styleProp = isPrimary | ||
| ? { | ||
| background: variantStyle.bg, | ||
| boxShadow: variantStyle.shadow, | ||
| } | ||
| : undefined; | ||
| return ( | ||
| <button | ||
| ref={ref} | ||
| type={type} | ||
| className={classes} | ||
| style={styleProp} | ||
| disabled={disabled} | ||
| onMouseEnter={ | ||
| isPrimary && !disabled | ||
| ? (e) => { | ||
| e.currentTarget.style.boxShadow = variantStyle.hoverShadow; | ||
| e.currentTarget.style.transform = "translateY(-1px)"; | ||
| } | ||
| : undefined | ||
| } | ||
| onMouseLeave={ | ||
| isPrimary && !disabled | ||
| ? (e) => { | ||
| e.currentTarget.style.boxShadow = variantStyle.shadow; | ||
| e.currentTarget.style.transform = "translateY(0)"; | ||
| } | ||
| : undefined | ||
| } | ||
| {...rest} | ||
| > | ||
| {children} | ||
| </button> | ||
| ); | ||
| }); | ||
| export default Button; |
+77
-59
@@ -92,2 +92,13 @@ #!/usr/bin/env node | ||
| if (chosenTheme !== "default") { | ||
| if (!has.tailwind) { | ||
| has.tailwind = true; | ||
| console.log(chalk.yellow(` ${chosenTheme} theme requires Tailwind CSS — enabled automatically.\n`)); | ||
| } | ||
| if (!has.router) { | ||
| has.router = true; | ||
| console.log(chalk.yellow(` ${chosenTheme} theme requires React Router — enabled automatically.\n`)); | ||
| } | ||
| } | ||
| const targetDir = path.resolve(process.cwd(), projectName); | ||
@@ -118,2 +129,41 @@ | ||
| const pkgPath = path.join(targetDir, "package.json"); | ||
| const pkg = fs.readJsonSync(pkgPath); | ||
| pkg.name = projectName; | ||
| if (!has.redux) { | ||
| delete pkg.dependencies["@reduxjs/toolkit"]; | ||
| delete pkg.dependencies["react-redux"]; | ||
| fs.removeSync(path.join(targetDir, "src/features")); | ||
| } | ||
| if (!has.router) { | ||
| delete pkg.dependencies["react-router-dom"]; | ||
| fs.removeSync(path.join(targetDir, "src/layout")); | ||
| } | ||
| if (!has.shadcn) { | ||
| delete pkg.dependencies["@radix-ui/react-alert-dialog"]; | ||
| delete pkg.dependencies["@radix-ui/react-dialog"]; | ||
| delete pkg.dependencies["@radix-ui/react-dropdown-menu"]; | ||
| delete pkg.dependencies["@radix-ui/react-slot"]; | ||
| delete pkg.dependencies["@radix-ui/react-switch"]; | ||
| delete pkg.dependencies["class-variance-authority"]; | ||
| delete pkg.dependencies["clsx"]; | ||
| delete pkg.dependencies["tailwind-merge"]; | ||
| delete pkg.dependencies["tailwindcss-animate"]; | ||
| if (chosenTheme === "default") { | ||
| delete pkg.dependencies["lucide-react"]; | ||
| } | ||
| fs.removeSync(path.join(targetDir, "src/components/ui")); | ||
| fs.removeSync(path.join(targetDir, "src/lib")); | ||
| fs.removeSync(path.join(targetDir, "src/hooks/alert")); | ||
| } | ||
| if (!has.tailwind) { | ||
| delete pkg.devDependencies["tailwindcss"]; | ||
| delete pkg.devDependencies["@tailwindcss/vite"]; | ||
| delete pkg.dependencies["tailwindcss-animate"]; | ||
| } | ||
| if (has.tailwind && chosenTheme !== "default") { | ||
@@ -174,39 +224,2 @@ const themeDir = path.join(targetDir, "themes", chosenTheme); | ||
| const pkgPath = path.join(targetDir, "package.json"); | ||
| const pkg = fs.readJsonSync(pkgPath); | ||
| pkg.name = projectName; | ||
| if (!has.redux) { | ||
| delete pkg.dependencies["@reduxjs/toolkit"]; | ||
| delete pkg.dependencies["react-redux"]; | ||
| fs.removeSync(path.join(targetDir, "src/features")); | ||
| } | ||
| if (!has.router) { | ||
| delete pkg.dependencies["react-router-dom"]; | ||
| fs.removeSync(path.join(targetDir, "src/layout")); | ||
| } | ||
| if (!has.shadcn) { | ||
| delete pkg.dependencies["@radix-ui/react-alert-dialog"]; | ||
| delete pkg.dependencies["@radix-ui/react-dialog"]; | ||
| delete pkg.dependencies["@radix-ui/react-dropdown-menu"]; | ||
| delete pkg.dependencies["@radix-ui/react-slot"]; | ||
| delete pkg.dependencies["@radix-ui/react-switch"]; | ||
| delete pkg.dependencies["class-variance-authority"]; | ||
| delete pkg.dependencies["clsx"]; | ||
| delete pkg.dependencies["tailwind-merge"]; | ||
| delete pkg.dependencies["tailwindcss-animate"]; | ||
| delete pkg.dependencies["lucide-react"]; | ||
| fs.removeSync(path.join(targetDir, "src/components/ui")); | ||
| fs.removeSync(path.join(targetDir, "src/lib")); | ||
| fs.removeSync(path.join(targetDir, "src/hooks/alert")); | ||
| } | ||
| if (!has.tailwind) { | ||
| delete pkg.devDependencies["tailwindcss"]; | ||
| delete pkg.devDependencies["@tailwindcss/vite"]; | ||
| delete pkg.dependencies["tailwindcss-animate"]; | ||
| } | ||
| fs.writeJsonSync(pkgPath, pkg, { spaces: 2 }); | ||
@@ -223,3 +236,3 @@ | ||
| if (!has.shadcn) { | ||
| patchFilesWithoutShadcn(targetDir, has); | ||
| patchFilesWithoutShadcn(targetDir, has, chosenTheme); | ||
| } | ||
@@ -449,3 +462,4 @@ | ||
| function patchFilesWithoutShadcn(targetDir, has) { | ||
| function patchFilesWithoutShadcn(targetDir, has, theme = "default") { | ||
| const isOverlayTheme = theme !== "default"; | ||
| const customerPath = path.join(targetDir, "src/pages/views/master/customer/customer.jsx"); | ||
@@ -479,14 +493,16 @@ if (fs.existsSync(customerPath)) { | ||
| const authPath = path.join(targetDir, "src/pages/views/auth/auth.jsx"); | ||
| if (fs.existsSync(authPath)) { | ||
| let content = fs.readFileSync(authPath, "utf-8"); | ||
| content = content.replace(/import { Button } from "@\/components\/ui\/button";\n/, ""); | ||
| content = content.replace( | ||
| /<Button type="submit" className="w-full">Sign In<\/Button>/, | ||
| `<button type="submit" style={{ width: "100%", padding: "8px 16px", background: "#171717", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer" }}>Sign In</button>` | ||
| ); | ||
| if (!has.tailwind) { | ||
| content = content.replace(/className="[^"]*"/g, ""); | ||
| if (!isOverlayTheme) { | ||
| const authPath = path.join(targetDir, "src/pages/views/auth/auth.jsx"); | ||
| if (fs.existsSync(authPath)) { | ||
| let content = fs.readFileSync(authPath, "utf-8"); | ||
| content = content.replace(/import { Button } from "@\/components\/ui\/button";\n/, ""); | ||
| content = content.replace( | ||
| /<Button type="submit" className="w-full">Sign In<\/Button>/, | ||
| `<button type="submit" style={{ width: "100%", padding: "8px 16px", background: "#171717", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer" }}>Sign In</button>` | ||
| ); | ||
| if (!has.tailwind) { | ||
| content = content.replace(/className="[^"]*"/g, ""); | ||
| } | ||
| fs.writeFileSync(authPath, content); | ||
| } | ||
| fs.writeFileSync(authPath, content); | ||
| } | ||
@@ -508,3 +524,3 @@ | ||
| if (has.router) { | ||
| if (has.router && !isOverlayTheme) { | ||
| const sidebarPath = path.join(targetDir, "src/layout/sidebar.jsx"); | ||
@@ -532,10 +548,12 @@ if (fs.existsSync(sidebarPath)) { | ||
| const menuDataPath = path.join(targetDir, "src/constants/data/menu.data.js"); | ||
| if (fs.existsSync(menuDataPath)) { | ||
| let content = fs.readFileSync(menuDataPath, "utf-8"); | ||
| content = content.replace(/import { LayoutDashboard, Users, Shield } from "lucide-react";\n\n/, ""); | ||
| content = content.replace(/icon: LayoutDashboard,/g, "icon: null,"); | ||
| content = content.replace(/icon: Users,/g, "icon: null,"); | ||
| content = content.replace(/icon: Shield,/g, "icon: null,"); | ||
| fs.writeFileSync(menuDataPath, content); | ||
| if (!isOverlayTheme) { | ||
| const menuDataPath = path.join(targetDir, "src/constants/data/menu.data.js"); | ||
| if (fs.existsSync(menuDataPath)) { | ||
| let content = fs.readFileSync(menuDataPath, "utf-8"); | ||
| content = content.replace(/import { LayoutDashboard, Users, Shield } from "lucide-react";\n\n/, ""); | ||
| content = content.replace(/icon: LayoutDashboard,/g, "icon: null,"); | ||
| content = content.replace(/icon: Users,/g, "icon: null,"); | ||
| content = content.replace(/icon: Shield,/g, "icon: null,"); | ||
| fs.writeFileSync(menuDataPath, content); | ||
| } | ||
| } | ||
@@ -542,0 +560,0 @@ } |
+1
-1
| { | ||
| "name": "rvx-cli", | ||
| "version": "1.2.1", | ||
| "version": "1.2.2", | ||
| "description": "Scaffold a production-ready Vite + React project with permissions, routing, Redux, and shadcn/ui", | ||
@@ -5,0 +5,0 @@ "bin": { |
@@ -110,5 +110,4 @@ import fs from "fs"; | ||
| import { use${Name}List } from "./${name}.service"; | ||
| ${config.router ? `import { useNavigate } from "react-router-dom";\n` : ""}import { Button } from "@/components/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
| import { ListPage, StatusPill } from "@/components/atlas"; | ||
| ${config.router ? `import { useNavigate } from "react-router-dom";\n` : ""}import { Plus } from "lucide-react"; | ||
| import { Button, ListPage, StatusPill } from "@/components/atlas"; | ||
@@ -218,5 +217,5 @@ export default function ${Name}() { | ||
| function getAtlasAddPageFile() { | ||
| return `${config.router ? `import { useNavigate } from "react-router-dom";\n` : ""}import { Button } from "@/components/ui/button"; | ||
| import { ArrowLeft } from "lucide-react"; | ||
| return `${config.router ? `import { useNavigate } from "react-router-dom";\n` : ""}import { ArrowLeft } from "lucide-react"; | ||
| import { | ||
| Button, | ||
| PageHeader, | ||
@@ -223,0 +222,0 @@ FormSection, |
@@ -0,1 +1,2 @@ | ||
| export { Button } from "./button"; | ||
| export { PageHeader } from "./page-header"; | ||
@@ -2,0 +3,0 @@ export { StatusPill } from "./status-pill"; |
| import { useState } from "react"; | ||
| import { Button } from "@/components/atlas"; | ||
| import { useAuth } from "@/context/auth/auth.context"; | ||
| import { useNavigate } from "react-router-dom"; | ||
| import { Mail, Lock, Eye, EyeOff, ArrowRight } from "lucide-react"; | ||
| import { Button } from "@/components/ui/button"; | ||
@@ -7,0 +7,0 @@ export default function Auth() { |
@@ -11,5 +11,3 @@ import { | ||
| } from "lucide-react"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { | ||
| PageHeader, | ||
| import { Button, PageHeader, | ||
| KPI, | ||
@@ -20,4 +18,3 @@ Card, | ||
| TileList, | ||
| Timeline, | ||
| } from "@/components/atlas"; | ||
| Timeline, } from "@/components/atlas"; | ||
@@ -24,0 +21,0 @@ const sparkRev = [82, 91, 78, 102, 96, 118, 124, 142]; |
@@ -1,9 +0,6 @@ | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
| import { | ||
| PageHeader, | ||
| import { Button, PageHeader, | ||
| Badge, | ||
| StatusPill, | ||
| AccentPicker, | ||
| } from "@/components/atlas"; | ||
| AccentPicker, } from "@/components/atlas"; | ||
| import { ACCENTS } from "@/lib/accent"; | ||
@@ -10,0 +7,0 @@ import { Showcase } from "../shared/showcase"; |
@@ -1,3 +0,2 @@ | ||
| import { PageHeader, useCommandPalette } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Button, PageHeader, useCommandPalette } from "@/components/atlas"; | ||
| import { Search } from "lucide-react"; | ||
@@ -4,0 +3,0 @@ import { Showcase } from "../shared/showcase"; |
@@ -1,9 +0,6 @@ | ||
| import { | ||
| PageHeader, | ||
| import { Button, PageHeader, | ||
| Dropdown, | ||
| DropdownItem, | ||
| DropdownDivider, | ||
| DropdownLabel, | ||
| } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| DropdownLabel, } from "@/components/atlas"; | ||
| import { ChevronDown, Edit, Copy, Trash2, Star } from "lucide-react"; | ||
@@ -48,3 +45,2 @@ import { Showcase } from "../shared/showcase"; | ||
| } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { ChevronDown, Edit, Trash2 } from "lucide-react"; | ||
@@ -51,0 +47,0 @@ |
@@ -1,4 +0,3 @@ | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Inbox, Plus, FileText } from "lucide-react"; | ||
| import { PageHeader, EmptyState } from "@/components/atlas"; | ||
| import { Button, PageHeader, EmptyState } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -33,3 +32,2 @@ | ||
| code={`import { EmptyState } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Inbox, Plus } from "lucide-react"; | ||
@@ -36,0 +34,0 @@ |
| import { useState } from "react"; | ||
| import { Mail } from "lucide-react"; | ||
| import { | ||
| PageHeader, | ||
| import { Button, PageHeader, | ||
| Input, | ||
@@ -12,5 +11,3 @@ Select, | ||
| FormGrid, | ||
| Field, | ||
| } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| Field, } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -149,3 +146,2 @@ | ||
| code={`import { FormSection, FormGrid, Field, Input, Select } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
@@ -152,0 +148,0 @@ <FormSection |
@@ -1,4 +0,3 @@ | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
| import { PageHeader, ListPage, StatusPill } from "@/components/atlas"; | ||
| import { Button, PageHeader, ListPage, StatusPill } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -70,3 +69,2 @@ | ||
| code={`import { ListPage, StatusPill } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
@@ -73,0 +71,0 @@ |
| import { useState } from "react"; | ||
| import { PageHeader, Modal } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Button, PageHeader, Modal } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -5,0 +4,0 @@ |
@@ -1,4 +0,3 @@ | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Plus, Download } from "lucide-react"; | ||
| import { PageHeader } from "@/components/atlas"; | ||
| import { Button, PageHeader } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -40,3 +39,2 @@ | ||
| code={`import { PageHeader } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
@@ -43,0 +41,0 @@ |
| import { useState } from "react"; | ||
| import { PageHeader, Stepper } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Button, PageHeader, Stepper } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -5,0 +4,0 @@ |
@@ -1,3 +0,2 @@ | ||
| import { PageHeader, useToast } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Button, PageHeader, useToast } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -4,0 +3,0 @@ |
| import { Mail, MapPin, CreditCard, Calendar } from "lucide-react"; | ||
| import { | ||
| PageHeader, | ||
| import { Button, PageHeader, | ||
| ViewHeader, | ||
@@ -8,5 +7,3 @@ KvGrid, | ||
| Card, | ||
| CardHead, | ||
| } from "@/components/atlas"; | ||
| import { Button } from "@/components/ui/button"; | ||
| CardHead, } from "@/components/atlas"; | ||
| import { Showcase } from "../shared/showcase"; | ||
@@ -13,0 +10,0 @@ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
342323
0.77%114
0.88%8101
0.95%10
11.11%