358 lines
9.4 KiB
Bash
358 lines
9.4 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# Initialize a Vite + React + TypeScript + Tailwind + shadcn/ui project
|
||
|
|
# Usage: bash scripts/init-vite.sh <project-name>
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
PROJECT_NAME="${1:-my-site}"
|
||
|
|
|
||
|
|
# Check Node version
|
||
|
|
NODE_VERSION=$(node -v 2>/dev/null | cut -d'v' -f2 | cut -d'.' -f1)
|
||
|
|
if [ -z "$NODE_VERSION" ] || [ "$NODE_VERSION" -lt 18 ]; then
|
||
|
|
echo "❌ Error: Node.js 18+ is required. Current: $(node -v 2>/dev/null || echo 'not installed')"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "🚀 Creating Vite project: $PROJECT_NAME"
|
||
|
|
|
||
|
|
# Create Vite project
|
||
|
|
npm create vite@latest "$PROJECT_NAME" -- --template react-ts
|
||
|
|
cd "$PROJECT_NAME"
|
||
|
|
|
||
|
|
# Create .nvmrc for Node version
|
||
|
|
echo "18" > .nvmrc
|
||
|
|
|
||
|
|
echo "📦 Installing dependencies..."
|
||
|
|
|
||
|
|
# Install Tailwind CSS
|
||
|
|
npm install -D tailwindcss postcss autoprefixer
|
||
|
|
npx tailwindcss init -p
|
||
|
|
|
||
|
|
# Install animation library
|
||
|
|
npm install framer-motion
|
||
|
|
|
||
|
|
# Install shadcn/ui base dependencies
|
||
|
|
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge
|
||
|
|
npm install lucide-react
|
||
|
|
npm install @radix-ui/react-slot
|
||
|
|
|
||
|
|
echo "⚙️ Configuring Tailwind..."
|
||
|
|
|
||
|
|
# Create tailwind.config.ts
|
||
|
|
cat > tailwind.config.ts << 'EOF'
|
||
|
|
import type { Config } from "tailwindcss"
|
||
|
|
import { fontFamily } from "tailwindcss/defaultTheme"
|
||
|
|
import tailwindcssAnimate from "tailwindcss-animate"
|
||
|
|
|
||
|
|
const config: Config = {
|
||
|
|
darkMode: ["class"],
|
||
|
|
content: [
|
||
|
|
"./index.html",
|
||
|
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||
|
|
],
|
||
|
|
theme: {
|
||
|
|
container: {
|
||
|
|
center: true,
|
||
|
|
padding: "2rem",
|
||
|
|
screens: {
|
||
|
|
"2xl": "1400px",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
extend: {
|
||
|
|
colors: {
|
||
|
|
border: "hsl(var(--border))",
|
||
|
|
input: "hsl(var(--input))",
|
||
|
|
ring: "hsl(var(--ring))",
|
||
|
|
background: "hsl(var(--background))",
|
||
|
|
foreground: "hsl(var(--foreground))",
|
||
|
|
primary: {
|
||
|
|
DEFAULT: "hsl(var(--primary))",
|
||
|
|
foreground: "hsl(var(--primary-foreground))",
|
||
|
|
},
|
||
|
|
secondary: {
|
||
|
|
DEFAULT: "hsl(var(--secondary))",
|
||
|
|
foreground: "hsl(var(--secondary-foreground))",
|
||
|
|
},
|
||
|
|
destructive: {
|
||
|
|
DEFAULT: "hsl(var(--destructive))",
|
||
|
|
foreground: "hsl(var(--destructive-foreground))",
|
||
|
|
},
|
||
|
|
muted: {
|
||
|
|
DEFAULT: "hsl(var(--muted))",
|
||
|
|
foreground: "hsl(var(--muted-foreground))",
|
||
|
|
},
|
||
|
|
accent: {
|
||
|
|
DEFAULT: "hsl(var(--accent))",
|
||
|
|
foreground: "hsl(var(--accent-foreground))",
|
||
|
|
},
|
||
|
|
popover: {
|
||
|
|
DEFAULT: "hsl(var(--popover))",
|
||
|
|
foreground: "hsl(var(--popover-foreground))",
|
||
|
|
},
|
||
|
|
card: {
|
||
|
|
DEFAULT: "hsl(var(--card))",
|
||
|
|
foreground: "hsl(var(--card-foreground))",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
borderRadius: {
|
||
|
|
lg: "var(--radius)",
|
||
|
|
md: "calc(var(--radius) - 2px)",
|
||
|
|
sm: "calc(var(--radius) - 4px)",
|
||
|
|
},
|
||
|
|
fontFamily: {
|
||
|
|
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||
|
|
},
|
||
|
|
keyframes: {
|
||
|
|
"accordion-down": {
|
||
|
|
from: { height: "0" },
|
||
|
|
to: { height: "var(--radix-accordion-content-height)" },
|
||
|
|
},
|
||
|
|
"accordion-up": {
|
||
|
|
from: { height: "var(--radix-accordion-content-height)" },
|
||
|
|
to: { height: "0" },
|
||
|
|
},
|
||
|
|
},
|
||
|
|
animation: {
|
||
|
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||
|
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
plugins: [tailwindcssAnimate],
|
||
|
|
}
|
||
|
|
|
||
|
|
export default config
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Create base CSS
|
||
|
|
cat > src/index.css << 'EOF'
|
||
|
|
@tailwind base;
|
||
|
|
@tailwind components;
|
||
|
|
@tailwind utilities;
|
||
|
|
|
||
|
|
@layer base {
|
||
|
|
:root {
|
||
|
|
--background: 0 0% 100%;
|
||
|
|
--foreground: 222.2 84% 4.9%;
|
||
|
|
--card: 0 0% 100%;
|
||
|
|
--card-foreground: 222.2 84% 4.9%;
|
||
|
|
--popover: 0 0% 100%;
|
||
|
|
--popover-foreground: 222.2 84% 4.9%;
|
||
|
|
--primary: 222.2 47.4% 11.2%;
|
||
|
|
--primary-foreground: 210 40% 98%;
|
||
|
|
--secondary: 210 40% 96.1%;
|
||
|
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||
|
|
--muted: 210 40% 96.1%;
|
||
|
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||
|
|
--accent: 210 40% 96.1%;
|
||
|
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||
|
|
--destructive: 0 84.2% 60.2%;
|
||
|
|
--destructive-foreground: 210 40% 98%;
|
||
|
|
--border: 214.3 31.8% 91.4%;
|
||
|
|
--input: 214.3 31.8% 91.4%;
|
||
|
|
--ring: 222.2 84% 4.9%;
|
||
|
|
--radius: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dark {
|
||
|
|
--background: 222.2 84% 4.9%;
|
||
|
|
--foreground: 210 40% 98%;
|
||
|
|
--card: 222.2 84% 4.9%;
|
||
|
|
--card-foreground: 210 40% 98%;
|
||
|
|
--popover: 222.2 84% 4.9%;
|
||
|
|
--popover-foreground: 210 40% 98%;
|
||
|
|
--primary: 210 40% 98%;
|
||
|
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||
|
|
--secondary: 217.2 32.6% 17.5%;
|
||
|
|
--secondary-foreground: 210 40% 98%;
|
||
|
|
--muted: 217.2 32.6% 17.5%;
|
||
|
|
--muted-foreground: 215 20.2% 65.1%;
|
||
|
|
--accent: 217.2 32.6% 17.5%;
|
||
|
|
--accent-foreground: 210 40% 98%;
|
||
|
|
--destructive: 0 62.8% 30.6%;
|
||
|
|
--destructive-foreground: 210 40% 98%;
|
||
|
|
--border: 217.2 32.6% 17.5%;
|
||
|
|
--input: 217.2 32.6% 17.5%;
|
||
|
|
--ring: 212.7 26.8% 83.9%;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@layer base {
|
||
|
|
* {
|
||
|
|
@apply border-border;
|
||
|
|
}
|
||
|
|
body {
|
||
|
|
@apply bg-background text-foreground;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Create utils
|
||
|
|
mkdir -p src/lib
|
||
|
|
cat > src/lib/utils.ts << 'EOF'
|
||
|
|
import { type ClassValue, clsx } from "clsx"
|
||
|
|
import { twMerge } from "tailwind-merge"
|
||
|
|
|
||
|
|
export function cn(...inputs: ClassValue[]) {
|
||
|
|
return twMerge(clsx(inputs))
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Create components directory
|
||
|
|
mkdir -p src/components/ui
|
||
|
|
|
||
|
|
# Create Button component
|
||
|
|
cat > src/components/ui/button.tsx << 'EOF'
|
||
|
|
import * as React from "react"
|
||
|
|
import { Slot } from "@radix-ui/react-slot"
|
||
|
|
import { cva, type VariantProps } from "class-variance-authority"
|
||
|
|
import { cn } from "@/lib/utils"
|
||
|
|
|
||
|
|
const buttonVariants = cva(
|
||
|
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||
|
|
{
|
||
|
|
variants: {
|
||
|
|
variant: {
|
||
|
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||
|
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||
|
|
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||
|
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||
|
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||
|
|
link: "text-primary underline-offset-4 hover:underline",
|
||
|
|
},
|
||
|
|
size: {
|
||
|
|
default: "h-10 px-4 py-2",
|
||
|
|
sm: "h-9 rounded-md px-3",
|
||
|
|
lg: "h-11 rounded-md px-8",
|
||
|
|
icon: "h-10 w-10",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
defaultVariants: {
|
||
|
|
variant: "default",
|
||
|
|
size: "default",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
export interface ButtonProps
|
||
|
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||
|
|
VariantProps<typeof buttonVariants> {
|
||
|
|
asChild?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||
|
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||
|
|
const Comp = asChild ? Slot : "button"
|
||
|
|
return (
|
||
|
|
<Comp
|
||
|
|
className={cn(buttonVariants({ variant, size, className }))}
|
||
|
|
ref={ref}
|
||
|
|
{...props}
|
||
|
|
/>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
Button.displayName = "Button"
|
||
|
|
|
||
|
|
export { Button, buttonVariants }
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Update vite.config.ts with path aliases
|
||
|
|
cat > vite.config.ts << 'EOF'
|
||
|
|
import path from "path"
|
||
|
|
import react from "@vitejs/plugin-react"
|
||
|
|
import { defineConfig } from "vite"
|
||
|
|
|
||
|
|
export default defineConfig({
|
||
|
|
plugins: [react()],
|
||
|
|
resolve: {
|
||
|
|
alias: {
|
||
|
|
"@": path.resolve(__dirname, "./src"),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Update tsconfig.json
|
||
|
|
cat > tsconfig.json << 'EOF'
|
||
|
|
{
|
||
|
|
"compilerOptions": {
|
||
|
|
"target": "ES2020",
|
||
|
|
"useDefineForClassFields": true,
|
||
|
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||
|
|
"module": "ESNext",
|
||
|
|
"skipLibCheck": true,
|
||
|
|
"moduleResolution": "bundler",
|
||
|
|
"allowImportingTsExtensions": true,
|
||
|
|
"resolveJsonModule": true,
|
||
|
|
"isolatedModules": true,
|
||
|
|
"noEmit": true,
|
||
|
|
"jsx": "react-jsx",
|
||
|
|
"strict": true,
|
||
|
|
"noUnusedLocals": true,
|
||
|
|
"noUnusedParameters": true,
|
||
|
|
"noFallthroughCasesInSwitch": true,
|
||
|
|
"baseUrl": ".",
|
||
|
|
"paths": {
|
||
|
|
"@/*": ["./src/*"]
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"include": ["src"],
|
||
|
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Create components.json for shadcn CLI
|
||
|
|
cat > components.json << 'EOF'
|
||
|
|
{
|
||
|
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||
|
|
"style": "default",
|
||
|
|
"rsc": false,
|
||
|
|
"tsx": true,
|
||
|
|
"tailwind": {
|
||
|
|
"config": "tailwind.config.ts",
|
||
|
|
"css": "src/index.css",
|
||
|
|
"baseColor": "slate",
|
||
|
|
"cssVariables": true,
|
||
|
|
"prefix": ""
|
||
|
|
},
|
||
|
|
"aliases": {
|
||
|
|
"components": "@/components",
|
||
|
|
"utils": "@/lib/utils",
|
||
|
|
"ui": "@/components/ui",
|
||
|
|
"lib": "@/lib",
|
||
|
|
"hooks": "@/hooks"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "📦 Installing shadcn/ui components..."
|
||
|
|
|
||
|
|
# Install common components via shadcn CLI
|
||
|
|
npx shadcn@latest add button badge card accordion dialog navigation-menu tabs sheet separator avatar alert -y || {
|
||
|
|
echo "⚠️ Warning: Some shadcn components may not have installed. Run 'npx shadcn@latest add [name]' manually."
|
||
|
|
}
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "✅ Project created successfully!"
|
||
|
|
echo ""
|
||
|
|
echo "Installed:"
|
||
|
|
echo " ✓ React 18 + TypeScript + Vite"
|
||
|
|
echo " ✓ Tailwind CSS with shadcn/ui theming"
|
||
|
|
echo " ✓ Framer Motion for animations"
|
||
|
|
echo " ✓ 10 shadcn/ui components (add more with: npx shadcn@latest add [component])"
|
||
|
|
echo " ✓ Path aliases (@/) configured"
|
||
|
|
echo ""
|
||
|
|
echo "Next steps:"
|
||
|
|
echo " cd $PROJECT_NAME"
|
||
|
|
echo " npm run dev"
|
||
|
|
echo ""
|
||
|
|
echo "Add more components:"
|
||
|
|
echo " npx shadcn@latest add [component-name]"
|
||
|
|
echo " npx shadcn@latest add --all # Install all components"
|
||
|
|
echo ""
|
||
|
|
echo "Build for production:"
|
||
|
|
echo " npm run build"
|
||
|
|
echo ""
|