feat: add layout and sidebar components with navigation structure
chore: update dependencies and configuration for Tailwind CSS docs: create components.json and skills-lock.json for project structure
This commit is contained in:
21
frontend/components.json
Normal file
21
frontend/components.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/styles/globals.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
@@ -10,14 +10,18 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-form": "^1.28.3",
|
"@tanstack/react-form": "^1.28.3",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"@tanstack/react-router": "^1.162.2",
|
"@tanstack/react-router": "^1.162.2",
|
||||||
@@ -32,6 +36,7 @@
|
|||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@tailwindcss/postcss": "^4.2.0",
|
"@tailwindcss/postcss": "^4.2.0",
|
||||||
|
"@tailwindcss/vite": "^4.2.0",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
|||||||
10
frontend/skills-lock.json
Normal file
10
frontend/skills-lock.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"skills": {
|
||||||
|
"shadcn-ui": {
|
||||||
|
"source": "google-labs-code/stitch-skills",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "dadbca54d35a33fe73e40018611af2179689305bf6da812ab2643987fefd9da3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,14 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { Outlet } from '@tanstack/react-router';
|
import { Outlet } from '@tanstack/react-router';
|
||||||
import { Header } from './Header';
|
import { AppSidebar } from './Sidebar';
|
||||||
import { Sidebar } from './Sidebar';
|
|
||||||
|
|
||||||
interface LayoutProps {
|
|
||||||
sidebarOpen?: boolean;
|
|
||||||
onSidebarChange?: (open: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Layout({ sidebarOpen = false, onSidebarChange }: LayoutProps) {
|
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(sidebarOpen);
|
|
||||||
|
|
||||||
const handleSidebarToggle = (open: boolean) => {
|
|
||||||
setIsSidebarOpen(open);
|
|
||||||
onSidebarChange?.(open);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export function Layout() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<AppSidebar>
|
||||||
{/* Sidebar */}
|
<main className="flex-1 p-6">
|
||||||
<Sidebar isOpen={isSidebarOpen} onClose={() => handleSidebarToggle(false)} />
|
<div className="space-y-6">
|
||||||
|
<Outlet />
|
||||||
{/* Contenido principal */}
|
</div>
|
||||||
<div className="md:pl-64">
|
</main>
|
||||||
{/* Header */}
|
</AppSidebar>
|
||||||
<Header onMenuToggle={() => handleSidebarToggle(true)} />
|
|
||||||
|
|
||||||
{/* Main content */}
|
|
||||||
<main className="container mx-auto p-6">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,309 @@
|
|||||||
import { Link, useLocation } from '@tanstack/react-router';
|
'use client';
|
||||||
import { Home, Gamepad2, FileText, Settings, Database, Tag, Download, Upload } from 'lucide-react';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
|
|
||||||
interface SidebarProps {
|
import * as React from 'react';
|
||||||
isOpen?: boolean;
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
onClose?: () => void;
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
}
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarInset,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
SidebarProvider,
|
||||||
|
SidebarRail,
|
||||||
|
SidebarTrigger,
|
||||||
|
useSidebar,
|
||||||
|
} from '@/components/ui/sidebar';
|
||||||
|
import {
|
||||||
|
Gamepad2,
|
||||||
|
Home,
|
||||||
|
Settings,
|
||||||
|
User,
|
||||||
|
Database,
|
||||||
|
Star,
|
||||||
|
TrendingUp,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronsUpDown,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
const navigation = [
|
const data = {
|
||||||
{ name: 'Dashboard', href: '/', icon: Home, count: null },
|
user: {
|
||||||
{ name: 'Juegos', href: '/games', icon: Gamepad2, count: null },
|
name: 'Game Library',
|
||||||
{
|
email: 'admin@gamelibrary.com',
|
||||||
name: 'Plataformas',
|
avatar: '/avatars/default.jpg',
|
||||||
href: '/platforms',
|
|
||||||
icon: Database,
|
|
||||||
count: null,
|
|
||||||
},
|
},
|
||||||
{
|
teams: [
|
||||||
name: 'Etiquetas',
|
{
|
||||||
href: '/tags',
|
name: 'Personal',
|
||||||
icon: Tag,
|
logo: Gamepad2,
|
||||||
count: null,
|
plan: 'Standard',
|
||||||
},
|
},
|
||||||
{ name: 'Importar ROMs', href: '/import', icon: Download, count: null },
|
{
|
||||||
{ name: 'Exportar', href: '/export', icon: Upload, count: null },
|
name: 'Work',
|
||||||
{ name: 'Configuración', href: '/settings', icon: Settings, count: null },
|
logo: Database,
|
||||||
];
|
plan: 'Professional',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: 'Dashboard',
|
||||||
|
url: '/',
|
||||||
|
icon: Home,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Games',
|
||||||
|
url: '/games',
|
||||||
|
icon: Gamepad2,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'All Games',
|
||||||
|
url: '/games',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Favorites',
|
||||||
|
url: '/games/favorites',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Recently Played',
|
||||||
|
url: '/games/recent',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Collections',
|
||||||
|
url: '/collections',
|
||||||
|
icon: Star,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'My Collections',
|
||||||
|
url: '/collections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Shared Collections',
|
||||||
|
url: '/collections/shared',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Statistics',
|
||||||
|
url: '/stats',
|
||||||
|
icon: TrendingUp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Settings',
|
||||||
|
url: '/settings',
|
||||||
|
icon: Settings,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
function TeamSwitcher({
|
||||||
const location = useLocation();
|
teams,
|
||||||
|
}: {
|
||||||
|
teams: {
|
||||||
|
name: string;
|
||||||
|
logo: React.ElementType;
|
||||||
|
plan: string;
|
||||||
|
}[];
|
||||||
|
}) {
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
const [activeTeam, setActiveTeam] = React.useState(teams[0]);
|
||||||
|
|
||||||
|
if (!activeTeam) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SidebarMenu>
|
||||||
{/* Overlay para móviles */}
|
<SidebarMenuItem>
|
||||||
{isOpen && <div className="fixed inset-0 z-40 bg-black/50 md:hidden" onClick={onClose} />}
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
{/* Sidebar */}
|
<SidebarMenuButton
|
||||||
<div
|
size="lg"
|
||||||
className={cn(
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
'fixed left-0 top-0 z-50 h-full w-64 transform border-r bg-background transition-transform duration-300 ease-in-out md:relative md:translate-x-0',
|
>
|
||||||
isOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
|
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||||
)}
|
<activeTeam.logo className="size-4" />
|
||||||
>
|
|
||||||
<div className="flex h-full flex-col">
|
|
||||||
{/* Header del sidebar */}
|
|
||||||
<div className="flex h-14 items-center border-b px-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Gamepad2 className="h-6 w-6" />
|
|
||||||
<span className="font-bold">Quasar</span>
|
|
||||||
</div>
|
|
||||||
<Button variant="ghost" size="sm" className="ml-auto md:hidden" onClick={onClose}>
|
|
||||||
✕
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contenido del sidebar */}
|
|
||||||
<ScrollArea className="flex-1">
|
|
||||||
<nav className="p-4 space-y-1">
|
|
||||||
{navigation.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = location.pathname === item.href;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={item.name}
|
|
||||||
to={item.href}
|
|
||||||
onClick={onClose}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground',
|
|
||||||
isActive ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<Icon className="h-4 w-4" />
|
|
||||||
<span>{item.name}</span>
|
|
||||||
</div>
|
|
||||||
{item.count !== null && (
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
{item.count}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Sección adicional con información */}
|
|
||||||
<div className="mt-8 p-4 border-t">
|
|
||||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">
|
|
||||||
Información
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-2 text-xs text-muted-foreground">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>Versión</span>
|
|
||||||
<span>1.0.0</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>Última actualización</span>
|
|
||||||
<span>2024-01-15</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
</ScrollArea>
|
<span className="truncate font-medium">{activeTeam.name}</span>
|
||||||
|
<span className="truncate text-xs">{activeTeam.plan}</span>
|
||||||
{/* Footer del sidebar */}
|
</div>
|
||||||
<div className="flex h-14 items-center border-t px-4">
|
<ChevronsUpDown className="ml-auto" />
|
||||||
<div className="flex items-center space-x-2 text-xs text-muted-foreground">
|
</SidebarMenuButton>
|
||||||
<span>© 2024 Quasar</span>
|
</DropdownMenuTrigger>
|
||||||
</div>
|
<DropdownMenuContent
|
||||||
</div>
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
</div>
|
align="start"
|
||||||
</div>
|
side={isMobile ? 'bottom' : 'right'}
|
||||||
</>
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel>
|
||||||
|
{teams.map((team) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={team.name}
|
||||||
|
onClick={() => setActiveTeam(team)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg mr-2">
|
||||||
|
<team.logo className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-medium">{team.name}</span>
|
||||||
|
<span className="truncate text-xs">{team.plan}</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavMain({
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
items: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon?: React.ElementType;
|
||||||
|
isActive?: boolean;
|
||||||
|
items?: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>Main Navigation</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Collapsible key={item.title} defaultOpen={item.isActive} className="group/collapsible">
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger render={<SidebarMenuButton tooltip={item.title} />}>
|
||||||
|
{item.icon && <item.icon />}
|
||||||
|
<span>{item.title}</span>
|
||||||
|
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{item.items?.map((subItem) => (
|
||||||
|
<SidebarMenuSubItem key={subItem.title}>
|
||||||
|
<SidebarMenuSubButton render={<a href={subItem.url} />}>
|
||||||
|
<span>{subItem.title}</span>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavUser({ user }: { user: { name: string; email: string; avatar: string } }) {
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<Avatar className="h-8 w-8 rounded-full">
|
||||||
|
<AvatarImage src={user.avatar} alt={user.name} />
|
||||||
|
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-medium">{user.name}</span>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">{user.email}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
|
align="start"
|
||||||
|
side={isMobile ? 'bottom' : 'right'}
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||||
|
Logged in as
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<User className="mr-2 h-4 w-4" />
|
||||||
|
<span>Profile</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Settings className="mr-2 h-4 w-4" />
|
||||||
|
<span>Settings</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span>Log out</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
|
return (
|
||||||
|
<SidebarProvider>
|
||||||
|
<Sidebar collapsible="icon" {...props}>
|
||||||
|
<SidebarHeader>
|
||||||
|
<TeamSwitcher teams={data.teams} />
|
||||||
|
</SidebarHeader>
|
||||||
|
<SidebarContent>
|
||||||
|
<NavMain items={data.navMain} />
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser user={data.user} />
|
||||||
|
</SidebarFooter>
|
||||||
|
<SidebarRail />
|
||||||
|
</Sidebar>
|
||||||
|
<SidebarInset>
|
||||||
|
<header className="flex h-16 shrink-0 items-center gap-2 border-b bg-background px-4 transition-[width,height] ease-linear group-has-[collapsible=icon]/sidebar-wrapper:h-12">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SidebarTrigger className="-ml-1" />
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Gamepad2 className="h-6 w-6" />
|
||||||
|
<h1 className="text-lg font-semibold">Game Library</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</SidebarInset>
|
||||||
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss/preflight";
|
||||||
@tailwind components;
|
@import "tailwindcss";
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ const config: Config = {
|
|||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))',
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
},
|
},
|
||||||
|
sidebar: {
|
||||||
|
DEFAULT: 'hsl(var(--sidebar))',
|
||||||
|
foreground: 'hsl(var(--sidebar-foreground))',
|
||||||
|
primary: 'hsl(var(--sidebar-primary))',
|
||||||
|
primaryForeground: 'hsl(var(--sidebar-primary-foreground))',
|
||||||
|
accent: 'hsl(var(--sidebar-accent))',
|
||||||
|
accentForeground: 'hsl(var(--sidebar-accent-foreground))',
|
||||||
|
border: 'hsl(var(--sidebar-border))',
|
||||||
|
ring: 'hsl(var(--sidebar-ring))',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(), tailwindcss()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src'),
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
|||||||
146
yarn.lock
146
yarn.lock
@@ -1395,6 +1395,29 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-avatar@npm:^1.1.11":
|
||||||
|
version: 1.1.11
|
||||||
|
resolution: "@radix-ui/react-avatar@npm:1.1.11"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-context": "npm:1.1.3"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.4"
|
||||||
|
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-is-hydrated": "npm:0.1.0"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/b1b3d4b11a8e05a8479d2410fb4e7b1bf825135c4cd42f7e5152568a54a55a3073bd87d50325150417a29306e7b1b371289dc3c4f11739af8a2a7bb8dd7c38c9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-checkbox@npm:^1.3.3":
|
"@radix-ui/react-checkbox@npm:^1.3.3":
|
||||||
version: 1.3.3
|
version: 1.3.3
|
||||||
resolution: "@radix-ui/react-checkbox@npm:1.3.3"
|
resolution: "@radix-ui/react-checkbox@npm:1.3.3"
|
||||||
@@ -1421,6 +1444,32 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-collapsible@npm:^1.1.12":
|
||||||
|
version: 1.1.12
|
||||||
|
resolution: "@radix-ui/react-collapsible@npm:1.1.12"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive": "npm:1.1.3"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-presence": "npm:1.1.5"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.3"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/777cced73fbbec9cfafe6325aa5605e90f49d889af2778f4c4a6be101c07cacd69ae817d0b41cc27e3181f49392e2c06db7f32d6b084db047a51805ec70729b3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-collection@npm:1.1.7":
|
"@radix-ui/react-collection@npm:1.1.7":
|
||||||
version: 1.1.7
|
version: 1.1.7
|
||||||
resolution: "@radix-ui/react-collection@npm:1.1.7"
|
resolution: "@radix-ui/react-collection@npm:1.1.7"
|
||||||
@@ -1469,6 +1518,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-context@npm:1.1.3":
|
||||||
|
version: 1.1.3
|
||||||
|
resolution: "@radix-ui/react-context@npm:1.1.3"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/0f271b4100dbb007ad2675f2529453f07454f214b7ce796d72680bf2dff050d0719083ee6e8962919a74048ff853eff2e50de07d8f8c674d6be91bfa76204cc2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-dialog@npm:^1.1.15":
|
"@radix-ui/react-dialog@npm:^1.1.15":
|
||||||
version: 1.1.15
|
version: 1.1.15
|
||||||
resolution: "@radix-ui/react-dialog@npm:1.1.15"
|
resolution: "@radix-ui/react-dialog@npm:1.1.15"
|
||||||
@@ -1865,6 +1927,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-separator@npm:^1.1.8":
|
||||||
|
version: 1.1.8
|
||||||
|
resolution: "@radix-ui/react-separator@npm:1.1.8"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.4"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/92e1353f696a22167c90f2c610b440be1fae3c05128287560914f124eef83d74c06ad25431923f3595032e6d89c23d479c95434390f4c0d9d4a68ec8d563ae0c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-slot@npm:1.2.3":
|
"@radix-ui/react-slot@npm:1.2.3":
|
||||||
version: 1.2.3
|
version: 1.2.3
|
||||||
resolution: "@radix-ui/react-slot@npm:1.2.3"
|
resolution: "@radix-ui/react-slot@npm:1.2.3"
|
||||||
@@ -1951,6 +2032,36 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-tooltip@npm:^1.2.8":
|
||||||
|
version: 1.2.8
|
||||||
|
resolution: "@radix-ui/react-tooltip@npm:1.2.8"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive": "npm:1.1.3"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-dismissable-layer": "npm:1.1.11"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-popper": "npm:1.2.8"
|
||||||
|
"@radix-ui/react-portal": "npm:1.1.9"
|
||||||
|
"@radix-ui/react-presence": "npm:1.1.5"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.3"
|
||||||
|
"@radix-ui/react-slot": "npm:1.2.3"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-visually-hidden": "npm:1.2.3"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/de0cbae9c571a00671f160928d819e59502f59be8749f536ab4b180181d9d70aee3925a5b2555f8f32d0bea622bc35f65b70ca7ff0449e4844f891302310cc48
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-callback-ref@npm:1.1.1":
|
"@radix-ui/react-use-callback-ref@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
|
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
|
||||||
@@ -2010,6 +2121,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-is-hydrated@npm:0.1.0":
|
||||||
|
version: 0.1.0
|
||||||
|
resolution: "@radix-ui/react-use-is-hydrated@npm:0.1.0"
|
||||||
|
dependencies:
|
||||||
|
use-sync-external-store: "npm:^1.5.0"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/635079bafe32829fc7405895154568ea94a22689b170489fd6d77668e4885e72ff71ed6d0ea3d602852841ef0f1927aa400fee2178d5dfbeb8bc9297da7d6498
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-layout-effect@npm:1.1.1":
|
"@radix-ui/react-use-layout-effect@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
|
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
|
||||||
@@ -2627,6 +2753,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tailwindcss/vite@npm:^4.2.0":
|
||||||
|
version: 4.2.0
|
||||||
|
resolution: "@tailwindcss/vite@npm:4.2.0"
|
||||||
|
dependencies:
|
||||||
|
"@tailwindcss/node": "npm:4.2.0"
|
||||||
|
"@tailwindcss/oxide": "npm:4.2.0"
|
||||||
|
tailwindcss: "npm:4.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^5.2.0 || ^6 || ^7
|
||||||
|
checksum: 10c0/4bd28ea2984907930a2ea4818581ce24bbb7276ffc4d316b32143eef2f2bf9f82bd2b937fe6bb78c9be96b61df5464d884b9c7b1e71f0eb3df2c4a890d34ff79
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tanstack/devtools-event-client@npm:^0.4.0":
|
"@tanstack/devtools-event-client@npm:^0.4.0":
|
||||||
version: 0.4.0
|
version: 0.4.0
|
||||||
resolution: "@tanstack/devtools-event-client@npm:0.4.0"
|
resolution: "@tanstack/devtools-event-client@npm:0.4.0"
|
||||||
@@ -5095,16 +5234,21 @@ __metadata:
|
|||||||
resolution: "frontend@workspace:frontend"
|
resolution: "frontend@workspace:frontend"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/js": "npm:^9.39.1"
|
"@eslint/js": "npm:^9.39.1"
|
||||||
|
"@radix-ui/react-avatar": "npm:^1.1.11"
|
||||||
"@radix-ui/react-checkbox": "npm:^1.3.3"
|
"@radix-ui/react-checkbox": "npm:^1.3.3"
|
||||||
|
"@radix-ui/react-collapsible": "npm:^1.1.12"
|
||||||
"@radix-ui/react-dialog": "npm:^1.1.15"
|
"@radix-ui/react-dialog": "npm:^1.1.15"
|
||||||
"@radix-ui/react-dropdown-menu": "npm:^2.1.16"
|
"@radix-ui/react-dropdown-menu": "npm:^2.1.16"
|
||||||
"@radix-ui/react-label": "npm:^2.1.8"
|
"@radix-ui/react-label": "npm:^2.1.8"
|
||||||
"@radix-ui/react-scroll-area": "npm:^1.2.10"
|
"@radix-ui/react-scroll-area": "npm:^1.2.10"
|
||||||
"@radix-ui/react-select": "npm:^2.2.6"
|
"@radix-ui/react-select": "npm:^2.2.6"
|
||||||
|
"@radix-ui/react-separator": "npm:^1.1.8"
|
||||||
"@radix-ui/react-slot": "npm:^1.2.4"
|
"@radix-ui/react-slot": "npm:^1.2.4"
|
||||||
"@radix-ui/react-tabs": "npm:^1.1.13"
|
"@radix-ui/react-tabs": "npm:^1.1.13"
|
||||||
"@radix-ui/react-toast": "npm:^1.2.15"
|
"@radix-ui/react-toast": "npm:^1.2.15"
|
||||||
|
"@radix-ui/react-tooltip": "npm:^1.2.8"
|
||||||
"@tailwindcss/postcss": "npm:^4.2.0"
|
"@tailwindcss/postcss": "npm:^4.2.0"
|
||||||
|
"@tailwindcss/vite": "npm:^4.2.0"
|
||||||
"@tanstack/react-form": "npm:^1.28.3"
|
"@tanstack/react-form": "npm:^1.28.3"
|
||||||
"@tanstack/react-query": "npm:^5.90.21"
|
"@tanstack/react-query": "npm:^5.90.21"
|
||||||
"@tanstack/react-router": "npm:^1.162.2"
|
"@tanstack/react-router": "npm:^1.162.2"
|
||||||
@@ -7841,7 +7985,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"use-sync-external-store@npm:^1.6.0":
|
"use-sync-external-store@npm:^1.5.0, use-sync-external-store@npm:^1.6.0":
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
resolution: "use-sync-external-store@npm:1.6.0"
|
resolution: "use-sync-external-store@npm:1.6.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user