Skip to content
Open
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
122 changes: 122 additions & 0 deletions src/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,116 @@ function generateVitePageComponent(page, config) {
`;
}

// Icon mapping for sidebar nav items (Lucide icon names)
const ICON_MAP = {
Home: 'Home', Dashboard: 'LayoutDashboard', Products: 'ShoppingBag',
Cart: 'ShoppingCart', Account: 'User', Wishlist: 'Heart',
Orders: 'Package', Categories: 'Grid3X3', Search: 'Search',
Support: 'HelpCircle', Students: 'Users', Courses: 'BookOpen',
Grades: 'Award', Attendance: 'ClipboardCheck', Schedule: 'Calendar',
Payments: 'CreditCard', Reports: 'BarChart3', Staff: 'Briefcase',
Analytics: 'TrendingUp', Users: 'Users', Billing: 'Receipt',
Settings: 'Settings', Integrations: 'Puzzle', Team: 'UserPlus',
Articles: 'FileText', Authors: 'Pen', Newsletter: 'Mail',
About: 'Info', Tags: 'Tag', Archive: 'Archive', Contact: 'MessageSquare',
Projects: 'FolderKanban', Skills: 'Zap', Blog: 'BookOpen',
Testimonials: 'Quote', Resume: 'FileUser', Services: 'Wrench',
};

// Templates that support sidebar
const SIDEBAR_TEMPLATES = ['ecommerce', 'school', 'saas', 'blog'];

function generateSidebarComponent(selectedPages, config, isVite) {
const iconImports = selectedPages
.map((p) => ICON_MAP[p.label] || 'Circle')
.filter((v, i, a) => a.indexOf(v) === i); // deduplicate
// Always include Circle as the fallback icon
if (!iconImports.includes('Circle')) iconImports.push('Circle');

const linkComponent = isVite ? 'Link' : 'Link';
const linkProp = isVite ? 'to' : 'href';
const linkImport = isVite
? "import { Link, useLocation } from 'react-router-dom';"
: "import Link from 'next/link';";
const navImport = isVite
? "import { navLinks } from '../lib/nav';"
: "import { navLinks } from '../lib/nav';";

const activeLogic = isVite
? ` const location = useLocation();
const isActive = (href: string) => location.pathname === href;`
: ` const isActive = (href: string) => false; // Replace with usePathname() for active state`;

const iconEntries = selectedPages.map((p) => {
const icon = ICON_MAP[p.label] || 'Circle';
return ` '${p.label}': ${icon},`;
}).join('\n');

return `'use client';

${linkImport}
import { ${iconImports.join(', ')} } from 'lucide-react';
${navImport}

const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
${iconEntries}
};

export default function Sidebar() {
${activeLogic}

return (
<aside className="w-64 border-r border-border bg-card min-h-screen flex flex-col">
{/* Brand */}
<div className="p-6 border-b border-border">
<${linkComponent} ${linkProp}="/" className="text-xl font-bold text-primary">
${config.projectName}
</${linkComponent}>
</div>

{/* Navigation */}
<nav className="flex-1 p-4">
<ul className="space-y-1">
{navLinks.map((link) => {
const Icon = iconMap[link.label] || Circle;
const active = isActive(link.href);
return (
<li key={link.href}>
<${linkComponent}
${linkProp}={link.href}
className={\`flex items-center gap-3 px-4 py-2.5 rounded-theme text-sm transition \${
active
? 'bg-primary text-white font-medium'
: 'text-text-secondary hover:bg-bg-secondary hover:text-foreground'
}\`}
>
<Icon className="w-4 h-4" />
{link.label}
</${linkComponent}>
</li>
);
})}
</ul>
</nav>

{/* Footer */}
<div className="p-4 border-t border-border">
<div className="flex items-center gap-3 px-4 py-2">
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center text-white text-xs font-medium">
U
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">User</p>
<p className="text-xs text-text-secondary truncate">user@example.com</p>
</div>
</div>
</div>
</aside>
);
}
`;
}

export function generateNavigation(projectPath, config) {
const pool = PAGE_POOLS[config.template];
if (!pool) return;
Expand Down Expand Up @@ -153,5 +263,17 @@ export function generateNavigation(projectPath, config) {
}
}

// 3. Generate Sidebar.tsx if template supports it and user selected sidebar
if (config.includeSidebar && SIDEBAR_TEMPLATES.includes(config.template)) {
const sidebarDir = isVite
? path.join(projectPath, 'src', 'components')
: path.join(projectPath, 'components');

if (!fs.existsSync(sidebarDir)) fs.mkdirSync(sidebarDir, { recursive: true });

const sidebarPath = path.join(sidebarDir, 'Sidebar.tsx');
fs.writeFileSync(sidebarPath, generateSidebarComponent(selectedPages, config, isVite));
}

return selectedPages;
}
Loading