Modo Claro y Oscuro
- https://ui.shadcn.com/docs/dark-mode/next
- Instalar
npm install next-themes
- Crear
components/providers/theme-provider.tsx
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
acomponents/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;