diff --git a/public/sidebar.png b/public/sidebar.png new file mode 100644 index 0000000..d168c8b Binary files /dev/null and b/public/sidebar.png differ diff --git a/public/unam.png b/public/unam.png new file mode 100644 index 0000000..84f9d78 Binary files /dev/null and b/public/unam.png differ diff --git a/src/app/(components)/input/page.tsx b/src/app/(components)/input/page.tsx index c38e1d8..f15c3f7 100644 --- a/src/app/(components)/input/page.tsx +++ b/src/app/(components)/input/page.tsx @@ -82,7 +82,7 @@ export default function Page() {

Props del componente

- +
diff --git a/src/app/(components)/select/page.tsx b/src/app/(components)/select/page.tsx index 386aae1..52a9676 100644 --- a/src/app/(components)/select/page.tsx +++ b/src/app/(components)/select/page.tsx @@ -26,7 +26,7 @@ export default function Page() { />

- 📑 Componente: Select + Componente: Select

@@ -38,7 +38,7 @@ export default function Page() {


-

⚙️ Props del componente

+

Props del componente

  • label: string – Etiqueta visible del campo. diff --git a/src/app/(components)/sidebar/page.tsx b/src/app/(components)/sidebar/page.tsx new file mode 100644 index 0000000..6eac9c2 --- /dev/null +++ b/src/app/(components)/sidebar/page.tsx @@ -0,0 +1,229 @@ +"use client"; +import IndiceContenido from "@/components/content-index"; +import Sidebar, { SidebarLink } from "@/components/sidebar"; +import VistaComponente from "@/components/vista-componente"; +import React from "react"; + +const basicLinks: SidebarLink[] = [ + { icon: "bi-house", text: "Inicio", href: "/inicio" }, + { + icon: "bi-person", + text: "Perfil", + href: "/perfil", + className: "hover-bg-success", + }, + { + icon: "bi-envelope", + text: "Mensajes", + href: "/mensajes", + className: "hover-bg-info", + }, + { + icon: "bi-gear", + text: "Configuración", + href: "/configuracion", + className: "hover-bg-warning", + }, + { + icon: "bi-bar-chart", + text: "Reportes", + href: "/reportes", + className: "hover-bg-danger", + }, +]; + +const onClickLinks: SidebarLink[] = [ + { + icon: "bi-lightning", + text: "Acción rápida", + onClick: () => alert("¡Ejecutando acción rápida!"), + className: "hover-bg-warning text-warning", + }, + { + icon: "bi-question-circle", + text: "Ayuda", + onClick: () => alert("Mostrando ayuda..."), + className: "hover-bg-info text-info", + }, + { + icon: "bi-bell", + text: "Notificaciones", + onClick: () => alert("Mostrando notificaciones..."), + className: "hover-bg-primary text-primary", + }, +]; + +const renderItems: SidebarLink[] = [ + { + render: () => ( +
  • +
    + + Componente especial +
    +
  • + ), + }, + { + render: () => ( +
  • +
    + + Favoritos +
    +
  • + ), + }, + { + render: () => ( +
  • +
    + + Alertas +
    +
  • + ), + }, + { + render: () => ( +
  • +
    + + Ideas +
    +
  • + ), + }, +]; + +export default function Page() { + return ( + <> + + +
    +

    + Componente: Sidebar +

    + +

    + El Sidebar es un componente versátil que funciona como un + menú lateral adaptable, diseñado para ser accesible tanto en + dispositivos móviles como en pantallas de escritorio. Permite incluir + enlaces, acciones personalizadas, colores personalizables por clase y + elementos renderizados dinámicamente. +

    + +
    + + +
    + +
    + +
    +
    +
    + + +
    + +
    +

    Contenido interactivo

    +

    + Cada enlace ejecuta una acción mediante onClick. + Este patrón es útil para accesos rápidos o elementos no + vinculados a rutas. +

    +
    +
    +
    + + +
    + +
    +

    Elementos renderizados

    +

    + La propiedad render permite insertar cualquier JSX + personalizado dentro del menú, ideal para incluir botones, + badges o estados especiales. +

    +
    +
    +
    + + +
    + +
    +

    Hover personalizados

    +

    + Gracias a las clases hover-bg-COLOR generadas a + partir de $theme-colors, cada elemento del menú + puede mostrar un color diferente al pasar el cursor. +

    +
    +
    +
    +
    + + ); +} diff --git a/src/app/(components)/styles/page.tsx b/src/app/(components)/styles/page.tsx new file mode 100644 index 0000000..a5cb65a --- /dev/null +++ b/src/app/(components)/styles/page.tsx @@ -0,0 +1,134 @@ +"use client"; +import React from "react"; +import IndiceContenido from "@/components/content-index"; +import VistaComponente from "@/components/vista-componente"; + +export default function Page() { + return ( + <> + +
    +

    Estilos

    + +

    + Los estilos de la aplicación se basan en Bootstrap 5 y se personalizan + mediante clases SCSS para mantener una apariencia coherente y + flexible. +

    + +
    + +
    + + Leer la documentación de .box +
    + +

    div con className .box

    +
    `} + > +
    +

    + div con className .box +

    +
    + + + + Gradiente desde arriba (top) con color primario +
`} + > +
+ Gradiente desde arriba (top) con color primario +
+
+ Gradiente desde la derecha (right) con color success +
+
+ Gradiente desde abajo (bottom) con color warning +
+
+ Gradiente desde la izquierda (left) con color info +
+ +

Ejemplo de gradiente de Bootstrap 5

+

+ Bootstrap incluye su propio estilo .bg-gradient que + aplica un efecto sutil sobre el fondo. +

+
+ Fondo con .bg-primary.bg-gradient +
+ + Escala 1.05 +
Escala 1.15
+
Escala 1.25
`} + > +
+ {[1, 2, 3, 4, 5].map((scale) => ( +
+ {`scale-hover-${scale}`} +
({(1 + scale * 0.05).toFixed(2)}x) +
+ ))} +
+
+ + Hover primario +
Elemento activo con fondo success
`} + > +
+ {["primary", "success", "warning", "danger", "azul"].map( + (color) => ( +
+ {`hover-bg-${color}`} +
+ {color === "primary" && "(hover o active)"} +
+ ) + )} +
+ +

+ Ejemplo con .nav-link +

+
+ + Enlace dentro de hover-bg-primary + +
+
+ + + ); +} diff --git a/src/app/(components)/table/page.tsx b/src/app/(components)/table/page.tsx index 2831f8d..bf3a8e2 100644 --- a/src/app/(components)/table/page.tsx +++ b/src/app/(components)/table/page.tsx @@ -224,7 +224,7 @@ export default function Page() { {/* PROPS */}

Props del componente

-
Prop
+
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 53df080..6c86bfd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Roboto } from "next/font/google"; import 'bootstrap-icons/font/bootstrap-icons.css'; import '@/styles/sass/bootstrap.scss'; +import BootstrapClient from "@/components/bootstrap-client"; + import Navbar from "@/components/navbar"; const roboto = Roboto({ @@ -26,6 +28,7 @@ export default function RootLayout({ return ( + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 98bf2e4..3f4affd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,14 @@ "use client"; + import ComponentCard from "@/components/component-card"; import Image from "next/image"; import React from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism"; export default function Page() { return ( -
+
-

Libreria de componentes de CIDWA

+

+ Librería de componentes de CIDWA +

-
-

Componentes

+ +
+

Uso de componentes

+

+ Esta librería ofrece una colección de componentes reutilizables, diseñados según las guías de estilo de CIDWA, para facilitar la construcción de interfaces consistentes y accesibles. +

+ +
+ +

Instalación

+

+ Para comenzar a utilizar los componentes, primero instala las siguientes dependencias: +

+ + + {`npm i bootstrap bootstrap-icons +npm i -D sass`} + + +

+ Luego, copia las carpetas components y styles dentro de tu directorio src. Estas contienen los componentes y estilos necesarios (SCSS y CSS). +

+ +

Configuración

+

+ Asegúrate de importar los estilos y el componente BootstrapClient en tu archivo layout.tsx, como se muestra a continuación: +

+ + + {`import 'bootstrap-icons/font/bootstrap-icons.css'; +import '@/styles/sass/bootstrap.scss'; +import BootstrapClient from "@/components/bootstrap-client";`} + + +

+ Es importante incluir el componente BootstrapClient para que los scripts de Bootstrap se carguen correctamente y sus componentes interactivos funcionen como se espera. +

+ +

Aquí tienes un ejemplo del uso dentro del layout:

+ + + {` + + + {children} + +`} + + +
+ +

Componentes disponibles

@@ -28,11 +83,7 @@ export default function Page() {
- +
@@ -40,6 +91,9 @@ export default function Page() {
+
+ +
diff --git a/src/components/bootstrap-client.tsx b/src/components/bootstrap-client.tsx new file mode 100644 index 0000000..f0f92f1 --- /dev/null +++ b/src/components/bootstrap-client.tsx @@ -0,0 +1,17 @@ +'use client'; +import { useEffect } from 'react'; + +export default function BootstrapClient() { + useEffect(() => { + const loadBootstrap = async () => { + if (typeof document !== 'undefined') { + // @ts-expect-error: Bootstrap is loaded dynamically + await import('bootstrap/dist/js/bootstrap.bundle.min.js'); + } + }; + + loadBootstrap(); + }, []); + + return null; +} diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index b44ed6d..18c9c3f 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -5,39 +5,66 @@ const Navbar: React.FC = () => { return ( diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx new file mode 100644 index 0000000..b380e0b --- /dev/null +++ b/src/components/sidebar.tsx @@ -0,0 +1,174 @@ +"use client"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import React, { useEffect, useState } from "react"; + +export interface SidebarLink { + icon?: string; + text?: string; + href?: string; + show?: boolean; + className?: string; + onClick?: () => void; + render?: () => React.ReactNode; +} + +interface SidebarProps { + id: string; + userName?: string; + userRole?: string; + logoSrc?: string; + appTitle?: string; + links: SidebarLink[]; +} + +export default function Sidebar({ + userName = "Usuario", + userRole = "Administrador", + logoSrc = "/unam.png", + appTitle = "Sistema de -------", + links, + id, +}: SidebarProps) { + const pathname = usePathname(); + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const open = localStorage.getItem(id) === "true"; + setIsOpen(open); + }, [id]); + + const toggleSidebar = () => { + localStorage.setItem(id, `${!isOpen}`); + setIsOpen(!isOpen); + }; + + return ( + + ); +} diff --git a/src/components/toggle-theme.tsx b/src/components/toggle-theme.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/sass/_gradients.scss b/src/styles/sass/_gradients.scss index e9db7e4..b6199d6 100644 --- a/src/styles/sass/_gradients.scss +++ b/src/styles/sass/_gradients.scss @@ -11,7 +11,7 @@ .bg-gradient-#{$color-name}-bottom { background-image: linear-gradient( to bottom, - rgba($color-value, 0.3), + rgba($color-value, 0.5), $body-bg ); } diff --git a/src/styles/sass/_hover.scss b/src/styles/sass/_hover.scss new file mode 100644 index 0000000..5f47014 --- /dev/null +++ b/src/styles/sass/_hover.scss @@ -0,0 +1,15 @@ +// Clases personalizadas de hover por color +@each $name, $color in $theme-colors { + .hover-bg-#{$name} { + transition: background-color 0.4s; + + &:hover, + &.active { + background-color: rgba($color, 0.75) !important; + .nav-link { + color: #ffffff !important; + } + } + } + } + \ No newline at end of file diff --git a/src/styles/sass/_sidebar.scss b/src/styles/sass/_sidebar.scss new file mode 100644 index 0000000..fa3de02 --- /dev/null +++ b/src/styles/sass/_sidebar.scss @@ -0,0 +1,90 @@ +.sidebar { + @extend .py-0; + @extend .box; + @extend .m-lg-2; + + .nav-item { + @extend .rounded-pill; + @extend .w-100; + transition: background-color 0.4s; + + // Hover por defecto (solo si no tiene clase hover-bg-*) + &:not([class*="hover-bg-"]):hover, + &:not([class*="hover-bg-"]).active { + @extend .bg-primary; + @extend .bg-opacity-75; + + .nav-link { + color: #ffffff !important; + } + } + + .nav-link { + i { + @extend .me-2; + } + } + } + + @include media-breakpoint-up(lg) { + @extend .py-lg-2; + position: sticky; + top: 0; + left: 0; + width: clamp(250px, 100%, 300px); + max-width: 300px; + transition: width 0.3s ease; + + .offcanvas-body { + display: block; + } + + &.open { + .nav-link { + overflow: hidden; + white-space: nowrap; + + span { + transition: opacity 0.3s, visibility 0.3s; + opacity: 1; + visibility: visible; + } + } + } + + &.close { + width: 80px; + + .nav-item:hover { + position: relative; + + &::after { + content: attr(data-tooltip); + position: absolute; + top: 50%; + left: 100%; + transform: translate(10px, -50%); + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 5px 10px; + border-radius: 5px; + opacity: 0; + animation: fadeInOut 4s ease-in-out; + font-style: normal; + max-width: 200px; + } + } + + .nav-link { + overflow: hidden; + white-space: nowrap; + + span { + transition: opacity 0.3s, visibility 0.3s; + opacity: 0; + visibility: hidden; + } + } + } + } +} \ No newline at end of file diff --git a/src/styles/sass/bootstrap.scss b/src/styles/sass/bootstrap.scss index 1aa33e6..4cf44c9 100644 --- a/src/styles/sass/bootstrap.scss +++ b/src/styles/sass/bootstrap.scss @@ -16,31 +16,35 @@ $custom-colors: ( $theme-colors: map-merge($theme-colors, $custom-colors); -@import "gradients"; -@import "extends"; -@import "table"; -@import "utilities"; - @import "bootstrap/scss/maps"; @import "bootstrap/scss/mixins"; @import "bootstrap/scss/bootstrap"; +@import "gradients"; +@import "extends"; +@import "table"; +@import "utilities"; +@import 'sidebar'; +@import 'hover'; + //Add additional custom code here -@media (min-width: 992px) { +@include media-breakpoint-up(lg) { .bd-layout { display: grid; grid-template-areas: "sidebar main"; grid-template-columns: 1fr 5fr; - gap: 1.5rem; + gap: $spacer * 1.5; } } -.bd-sidebar { - grid-area: sidebar; - position: sticky; - top: ($spacer * 3); - height: calc(100vh - 6rem); +@include media-breakpoint-up(lg) { + .bd-sidebar { + grid-area: sidebar; + position: sticky; + top: ($spacer * 3); + height: calc(100vh - 6rem); + } } ul.list-unstyled li {
Prop