diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..3c359c2 --- /dev/null +++ b/frontend/components.json @@ -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" +} diff --git a/frontend/package.json b/frontend/package.json index a62a7dc..e9162bf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,14 +10,18 @@ "preview": "vite preview" }, "dependencies": { + "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-form": "^1.28.3", "@tanstack/react-query": "^5.90.21", "@tanstack/react-router": "^1.162.2", @@ -32,6 +36,7 @@ "@eslint/js": "^9.39.1", "@radix-ui/react-slot": "^1.2.4", "@tailwindcss/postcss": "^4.2.0", + "@tailwindcss/vite": "^4.2.0", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", diff --git a/frontend/skills-lock.json b/frontend/skills-lock.json new file mode 100644 index 0000000..4de9310 --- /dev/null +++ b/frontend/skills-lock.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "skills": { + "shadcn-ui": { + "source": "google-labs-code/stitch-skills", + "sourceType": "github", + "computedHash": "dadbca54d35a33fe73e40018611af2179689305bf6da812ab2643987fefd9da3" + } + } +} diff --git a/frontend/src/components/layout/Layout.tsx b/frontend/src/components/layout/Layout.tsx index 6ae508f..ee98c86 100644 --- a/frontend/src/components/layout/Layout.tsx +++ b/frontend/src/components/layout/Layout.tsx @@ -1,38 +1,14 @@ -import { useState } from 'react'; import { Outlet } from '@tanstack/react-router'; -import { Header } from './Header'; -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); - }; +import { AppSidebar } from './Sidebar'; +export function Layout() { return ( -
- {/* Sidebar */} - handleSidebarToggle(false)} /> - - {/* Contenido principal */} -
- {/* Header */} -
handleSidebarToggle(true)} /> - - {/* Main content */} -
-
- -
-
-
-
+ +
+
+ +
+
+
); } diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 790220d..408fa62 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -1,119 +1,309 @@ -import { Link, useLocation } from '@tanstack/react-router'; -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'; +'use client'; -interface SidebarProps { - isOpen?: boolean; - onClose?: () => void; -} +import * as React from 'react'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +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 = [ - { name: 'Dashboard', href: '/', icon: Home, count: null }, - { name: 'Juegos', href: '/games', icon: Gamepad2, count: null }, - { - name: 'Plataformas', - href: '/platforms', - icon: Database, - count: null, +const data = { + user: { + name: 'Game Library', + email: 'admin@gamelibrary.com', + avatar: '/avatars/default.jpg', }, - { - name: 'Etiquetas', - href: '/tags', - icon: Tag, - count: null, - }, - { name: 'Importar ROMs', href: '/import', icon: Download, count: null }, - { name: 'Exportar', href: '/export', icon: Upload, count: null }, - { name: 'Configuración', href: '/settings', icon: Settings, count: null }, -]; + teams: [ + { + name: 'Personal', + logo: Gamepad2, + plan: 'Standard', + }, + { + name: 'Work', + 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) { - const location = useLocation(); +function TeamSwitcher({ + teams, +}: { + teams: { + name: string; + logo: React.ElementType; + plan: string; + }[]; +}) { + const { isMobile } = useSidebar(); + const [activeTeam, setActiveTeam] = React.useState(teams[0]); + + if (!activeTeam) { + return null; + } return ( - <> - {/* Overlay para móviles */} - {isOpen &&
} - - {/* Sidebar */} -
-
- {/* Header del sidebar */} -
-
- - Quasar -
- -
- - {/* Contenido del sidebar */} - - - - {/* Sección adicional con información */} -
-

- Información -

-
-
- Versión - 1.0.0 -
-
- Última actualización - 2024-01-15 -
+ + + + + +
+
-
- - - {/* Footer del sidebar */} -
-
- © 2024 Quasar -
-
-
-
- +
+ {activeTeam.name} + {activeTeam.plan} +
+ + + + + + Teams + {teams.map((team) => ( + setActiveTeam(team)} + className="cursor-pointer" + > +
+ +
+
+ {team.name} + {team.plan} +
+
+ ))} +
+
+ + + + ); +} + +function NavMain({ + items, +}: { + items: { + title: string; + url: string; + icon?: React.ElementType; + isActive?: boolean; + items?: { + title: string; + url: string; + }[]; + }[]; +}) { + return ( + + Main Navigation + + {items.map((item) => ( + + + }> + {item.icon && } + {item.title} + + + + + {item.items?.map((subItem) => ( + + }> + {subItem.title} + + + ))} + + + + + ))} + + + ); +} + +function NavUser({ user }: { user: { name: string; email: string; avatar: string } }) { + const { isMobile } = useSidebar(); + + return ( + + + + + + + + {user.name.charAt(0)} + +
+ {user.name} + {user.email} +
+ +
+
+ + + Logged in as + + + + + Profile + + + + Settings + + + + + Log out + + +
+
+
+ ); +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + + + + + + + +
+
+ +
+ +

Game Library

+
+
+
+
+
); } diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index f589dc9..baa1e66 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -1,6 +1,5 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss/preflight"; +@import "tailwindcss"; @layer base { :root { diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index f144ef5..0c6481c 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -38,6 +38,16 @@ const config: Config = { DEFAULT: 'hsl(var(--secondary))', 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: { lg: 'var(--radius)', diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index f4e8f1c..965c1e3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,10 +1,11 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; import path from 'path'; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], resolve: { alias: { '@': path.resolve(__dirname, './src'), diff --git a/yarn.lock b/yarn.lock index 5f4f70c..23b014a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1395,6 +1395,29 @@ __metadata: languageName: node 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": version: 1.3.3 resolution: "@radix-ui/react-checkbox@npm:1.3.3" @@ -1421,6 +1444,32 @@ __metadata: languageName: node 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": version: 1.1.7 resolution: "@radix-ui/react-collection@npm:1.1.7" @@ -1469,6 +1518,19 @@ __metadata: languageName: node 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": version: 1.1.15 resolution: "@radix-ui/react-dialog@npm:1.1.15" @@ -1865,6 +1927,25 @@ __metadata: languageName: node 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": version: 1.2.3 resolution: "@radix-ui/react-slot@npm:1.2.3" @@ -1951,6 +2032,36 @@ __metadata: languageName: node 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": version: 1.1.1 resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" @@ -2010,6 +2121,21 @@ __metadata: languageName: node 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": version: 1.1.1 resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" @@ -2627,6 +2753,19 @@ __metadata: languageName: node 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": version: 0.4.0 resolution: "@tanstack/devtools-event-client@npm:0.4.0" @@ -5095,16 +5234,21 @@ __metadata: resolution: "frontend@workspace:frontend" dependencies: "@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-collapsible": "npm:^1.1.12" "@radix-ui/react-dialog": "npm:^1.1.15" "@radix-ui/react-dropdown-menu": "npm:^2.1.16" "@radix-ui/react-label": "npm:^2.1.8" "@radix-ui/react-scroll-area": "npm:^1.2.10" "@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-tabs": "npm:^1.1.13" "@radix-ui/react-toast": "npm:^1.2.15" + "@radix-ui/react-tooltip": "npm:^1.2.8" "@tailwindcss/postcss": "npm:^4.2.0" + "@tailwindcss/vite": "npm:^4.2.0" "@tanstack/react-form": "npm:^1.28.3" "@tanstack/react-query": "npm:^5.90.21" "@tanstack/react-router": "npm:^1.162.2" @@ -7841,7 +7985,7 @@ __metadata: languageName: node 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 resolution: "use-sync-external-store@npm:1.6.0" peerDependencies: