Inicio NextJS 13 - eCommerce App - parte 2
Post
Cancel

NextJS 13 - eCommerce App - parte 2

Modo Claro y Oscuro

1
2
3
4
5
6
7
8
9
10
"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

  • Editar app/layout.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'
import { ThemeProvider } from '@/components/providers/theme-provider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="en" suppressHydrationWarning>
        <body className={inter.className}>
          <ThemeProvider
            attribute='class'
            defaultTheme='dark'
            enableSystem={true}
            storageKey='theme-lms'
          >
            {children}
          </ThemeProvider></body>
      </html>
    </ClerkProvider>
  )
}

  • Agregar botón de cambio de tema. Crear components/mode-toggle.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"use client"

import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function ModeToggle() {
  const { setTheme } = useTheme()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="icon">
          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">Cambiar tema</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>
          Claro
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>
          Oscuro
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>
          Sistema
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}


  • Instalar componente npx shadcn-ui@latest add dropdown-menu
  • Editar app/(dashboard)/(routes)/page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { ModeToggle } from "@/components/mode-toggle";
import { UserButton } from "@clerk/nextjs";

export default function Home() {
  return (
    <div>
      <UserButton
        afterSignOutUrl="/"
      />
      <ModeToggle />
    </div>
  );
}

  • Editar app/layout.tsx para personalizar temas y fuente 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import './globals.css'
import type { Metadata } from 'next'
import { Open_Sans } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'
import { ThemeProvider } from '@/components/providers/theme-provider'
import { cn } from '@/lib/utils'

const font = Open_Sans({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="en" suppressHydrationWarning>
        <body className={cn(
          font.className, 
          "bg-[#dddddd] dark:bg-[#313138]")}>
          <ThemeProvider
            attribute='class'
            defaultTheme='dark'
            enableSystem={true}
            storageKey='theme-lms'
          >
            {children}
          </ThemeProvider></body>
      </html>
    </ClerkProvider>
  )
}

Layout (Sidebar y Navbar)

  • Crear  app/(dashboard)/_components/Sidebar.tsx
1
2
3
4
5
6
7
8
9
10
const Sidebar = () => {
    return (
        <div className="h-full border-r flex flex-col overflow-y-auto bg-[#ededed] dark:bg-[#1f1f1f] shadow-sm">
            Sidebar!
        </div>
    );
}

export default Sidebar;

  • Crear app/(dashboard)/layout.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
    return (
        <div className="h-full">
            <div className="hidden md:flex h-full w-56 flex-col fixed inset-y-0 z-50">
                <Sidebar />
            </div>
            {children}
        </div>
    );
}

export default DashboardLayout;

  • Copiar imagen para logo en carpeta public
  • Crear app/dashboard/_components/Logo.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
import Image from "next/image";

export const Logo = () => {
    return (
        <Image
            src="/logo.png"
            alt="Logo"
            width={70}
            height={70}
        />
    )
};

  • Editar app/(dashboard)/_components/Sidebar.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Logo } from "./Logo";

const Sidebar = () => {
    return (
        <div className="h-full border-r flex flex-col overflow-y-auto bg-[#cfcfcf] dark:bg-[#1f1f1f] shadow-sm">
            <div className="p-6 flex items-center justify-center">
                <Logo />
            </div>
        </div>
    );
}

export default Sidebar;

  • Crear app/(dashboard)/_components/SidebarRoutes.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"use client";
import { Layout, Compass } from "lucide-react";

const guestRoutes = [
    {
        icon: Layout,
        label: "Dashboard",
        href: "/",
    },
    {
        icon: Compass,
        label: "Navegar",
        href: "/search",
    }
];

export const SidebarRoutes = () => {
    const routes = guestRoutes;

    return (
        <div className="flex flex-col w-full">
            Rutas!
        </div>
    )
};

  • Editar app/(dashboard)/_components/Sidebar.tsx 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Logo } from "./Logo";
import { SidebarRoutes } from "./SidebarRoutes";

const Sidebar = () => {
    return (
        <div className="h-full border-r flex flex-col overflow-y-auto bg-[#cfcfcf] dark:bg-[#1f1f1f] shadow-sm">
            <div className="p-6 flex items-center justify-center">
                <Logo />
            </div>
            <div className="flex flex-col w-full">
                <SidebarRoutes />
            </div>
        </div>
    );
}

export default Sidebar;

  • Crear app/(dashboard)/_components/SidebarItem.tsx 
1
2
3
4
5
6
7
8
9
10
"use client";

export const SidebarItem = () => {
    return (
        <div>
            Sidebar Item!!!
        </div>
    )
};

  • Editar app/(dashboard)/_components/SidebarRoutes.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"use client";
import { Layout, Compass } from "lucide-react";
import { SidebarItem } from "./SidebarItem";

const guestRoutes = [
    {
        icon: Layout,
        label: "Dashboard",
        href: "/",
    },
    {
        icon: Compass,
        label: "Navegar",
        href: "/search",
    }
];

export const SidebarRoutes = () => {
    const routes = guestRoutes;

    return (
        <div className="flex flex-col w-full">
            {routes.map((route) => (
                <SidebarItem 
                key={route.href}
                icon={route.icon}
                label={route.label}
                href={route.href}
            />
            ))}
        </div>
    )
};


  • Modificar app/(dashboard)/_components/SidebarItem.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"use client";

import { LucideIcon } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";

import { cn } from "@/lib/utils";

interface SidebarItemProps {
    icon: LucideIcon;
    label: string;
    href: string;
};

export const SidebarItem = ({
    icon: Icon,
    label,
    href,
}: SidebarItemProps) => {
    const pathname = usePathname();
    const router = useRouter();

    const isActive =
        (pathname === "/" && href === "/") ||
        pathname === href ||
        pathname?.startsWith(`${href}/`);

    const onClick = () => {
        router.push(href);
    }

    return (
        <button
            onClick={onClick}
            type="button"
            className={cn(
                "flex items-center gap-x-2 text-slate-500 dark:text-white text-sm font-[500] pl-6 transition-all hover:text-slate-600 hover:bg-teal-400 ",
                isActive && "text-sky-700 bg-sky-200/20 hover:bg-sky-200/20 hover:text-sky-700"
            )}
        >
            <div className="flex items-center gap-x-2 py-4">
                <Icon
                    size={22}
                    className={cn(
                        "text-slate-500 dark:text-white",
                        isActive && "text-sky-700"
                    )}
                />
                {label}
            </div>
            <div
                className={cn(
                    "ml-auto opacity-0 border-2 border-teal-500 dark:border-yellow-500 h-10 transition-all",
                    isActive && "opacity-100"
                )}
            />
        </button>
    )
}

  • Crear app/(dashboard)/(routes)/search/page.tsx
1
2
3
4
5
6
7
8
9
10
const SearchPage = () => {
    return (
        <div>
            <h1>Search Page</h1>
        </div>
    );
}

export default SearchPage;

  • Modificar app/(dashboard)/layout.tsx para mover el contenido a la derecha de la sidebar y agregar la div para la NavBar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Navbar from "./_components/Navbar";
import Sidebar from "./_components/Sidebar";

const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
    return (
        <div className="h-full">
            <div className="h-[80px] md:pl-56 fixed inset-y-0 w-full z-50">
                <Navbar />
            </div>
            <div className="hidden md:flex h-full w-56 flex-col fixed inset-y-0 z-50">
                <Sidebar />
            d</div>
            <main className="md:pl-56 h-full">
                {children}
            </main>
        </div>
    );
}

export default DashboardLayout;

  • Crear app/(dashboard)/_components/Navbar.tsx
1
2
3
4
5
6
7
8
9
10
export const Navbar = () => { 
    return (
        <div className="p-4 border-b h-full flex items-center bg-[#ededed] dark:bg-[#1f1f1f] shadow-sm">

        </div>
    ); 
}

export default Navbar;

  • Agregaremos un botón drawer para mostrar o esconder la sidebar en dispositivos mobiles. Crear app/(dashboard)/_components/MobileSidebar.tsx
1
2
3
4
5
6
7
8
9
10
import { Menu } from "lucide-react";

export const MobileSidebar = () => {
    return (
            <Menu />
    );
}

export default MobileSidebar;

  • Modificar app/(dashboard)/_components/Navbar.tsx para mostrar el nuevo componentes
1
2
3
4
5
6
7
8
9
10
11
12
import MobileSidebar from "./MobileSidebar";

export const Navbar = () => { 
    return (
        <div className="p-4 border-b h-full flex items-center bg-[#ededed] dark:bg-[#1f1f1f] shadow-sm">
            <MobileSidebar />
        </div>
    ); 
}

export default Navbar;

  • npx shadcn-ui@latest add sheet 
  • Modificar app/(dashboard)/_components/MobileSidebar.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Menu } from "lucide-react";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import Sidebar from "./Sidebar";

export const MobileSidebar = () => {
    return (
    <div>
        <Sheet>
            <SheetTrigger className="md:hidden pr-4 hover:opacity-75 transition">
                <Menu />
            </SheetTrigger>
            <SheetContent side="left" className="p-0  bg-[#ededed] dark:bg-[#1f1f1f] ">
                <Sidebar />
            </SheetContent>
        </Sheet>
    </div>
    );
}

export default MobileSidebar;

  • Agregar variante de botón customghost a components/ui/button.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const buttonVariants = cva(
  "inline-flex items-center justify-center 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",
        customghost: "hover:bg-teal-600 hover:text-white hover:dark:bg-yellow-500 hover:dark:text-black",
      },
      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",
    },
  }
)

  • Crear app/components/NavbarRoutes.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"use cñient";

import { UserButton } from "@clerk/nextjs";
import { ModeToggle } from "@/components/mode-toggle";
import { Button } from "@/components/ui/button";
import { LogOut } from "lucide-react";

export const NavbarRoutes = () => {
    return (
        <div className="flex gap-x-8 ml-auto">
            <Button size="sm" variant='customghost'>
                <LogOut className="h-4 w-4 mr-2"/>
                Salir
            </Button>
            <UserButton 
                afterSignOutUrl="/"
            />
            <ModeToggle />
        </div>
    );
}

  • Modificar app/(dashboard)/_components/Navbar.tsx para mostrar el nuevo componente
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { NavbarRoutes } from "@/components/NavbarRoutes";
import MobileSidebar from "./MobileSidebar";

export const Navbar = () => { 
    return (
        <div className="p-4 border-b h-full flex items-center bg-[#ededed] dark:bg-[#1f1f1f] shadow-sm">
            <MobileSidebar />
            <NavbarRoutes />
        </div>
    ); 
}

export default Navbar;

This post is licensed under CC BY 4.0 by the author.