feat: Refactor navigation structure by removing DoubleNavbar and integrating AppShellWithMenu; enhance styling with new Appshell.module.css

This commit is contained in:
2025-05-06 11:14:41 +02:00
parent 00fd103112
commit 9ab92c521a
7 changed files with 189 additions and 250 deletions
@@ -21,11 +21,6 @@
flex: 1;
}
.collapseButton {
margin-top: auto; /* Hace que el botón se empuje hacia abajo */
margin-bottom: 16px; /* ← Agrega separación del borde inferior */
}
.aside {
flex: 0 0 60px;
@@ -41,7 +36,12 @@
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
.topSection {
padding-top: 12px; /* o la cantidad que desees */
}
.mainLink {
width: 44px;
height: 44px;
border-radius: var(--mantine-radius-md);
@@ -63,27 +63,9 @@
}
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
margin-bottom: var(--mantine-spacing-xl);
background-color: var(--mantine-color-body);
padding: var(--mantine-spacing-md);
padding-top: 18px;
height: 60px;
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.logo {
width: 100%;
display: flex;
justify-content: center;
height: 60px;
padding-top: var(--mantine-spacing-md);
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
margin-bottom: var(--mantine-spacing-xl);
}
.link {
display: block;
+180 -38
View File
@@ -1,41 +1,183 @@
import { useDisclosure } from '@mantine/hooks';
import { AppShell, Burger, Group, Skeleton } from '@mantine/core';
import {
IconCalendarStats,
IconDeviceDesktopAnalytics,
IconFingerprint,
IconGauge,
IconHome2,
IconSettings,
IconUser,
IconArrowBarLeft,
IconArrowBarRight,
} from '@tabler/icons-react';
import {
AppShell,
Burger,
Group,
Skeleton,
Tooltip,
UnstyledButton,
ActionIcon,
Title,
} from '@mantine/core';
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { useEffect, useMemo, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import classes from './Appshell.module.css';
const mainLinksdata = [
{ icon: IconHome2, label: 'Home' },
{ icon: IconGauge, label: 'Dashboard' },
{ icon: IconDeviceDesktopAnalytics, label: 'Analytics' },
{ icon: IconCalendarStats, label: 'Releases' },
{ icon: IconUser, label: 'Account' },
{ icon: IconFingerprint, label: 'Security' },
{ icon: IconSettings, label: 'Settings' },
];
const submenuLinks = {
Home: [
{ label: 'Inicio', to: '/' },
{ label: 'Consulta Api', to: '/Consulta_API' },
{ label: 'Prueba_appshell', to: '/prueba_appshell' },
],
Dashboard: [
{ label: 'Resumen', to: '/dashboard/resumen' },
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
{ label: 'Usuarios', to: '/dashboard/usuarios' },
],
Analytics: [
{ label: 'Conversiones', to: '/analytics/conversiones' },
{ label: 'Tráfico', to: '/analytics/trafico' },
{ label: 'Tendencias', to: '/analytics/tendencias' },
],
Releases: [
{ label: 'Notas de versión', to: '/releases/notas-de-version' },
{ label: 'Historial', to: '/releases/historial' },
],
Account: [
{ label: 'Perfil', to: '/account/perfil' },
{ label: 'Suscripciones', to: '/account/suscripciones' },
],
Security: [
{ label: 'Contraseña', to: '/security/contraseña' },
{ label: '2FA', to: '/security/2fa' },
],
Settings: [
{ label: 'Preferencias', to: '/settings/preferencias' },
{ label: 'Notificaciones', to: '/settings/notificaciones' },
],
};
export function AppShellWithMenu() {
const location = useLocation();
const isMobile = useMediaQuery('(max-width: 768px)');
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
const isCollapsed = useMemo(() => (isMobile ? !mobileOpened : !desktopOpened), [isMobile, mobileOpened, desktopOpened]);
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) =>
items.some((item) => location.pathname.startsWith(item.to))
);
const routeBasedActive = matchedMain?.[0] ?? 'Home';
const active = manualActiveTab ?? routeBasedActive;
const activeLink = submenuLinks[active as keyof typeof submenuLinks]?.find((item) => location.pathname === item.to)?.label ?? '';
const mainLinks = mainLinksdata.map((link) => (
<Tooltip
label={link.label}
position="right"
withArrow
transitionProps={{ duration: 0 }}
key={link.label}
>
<UnstyledButton
onClick={() => {
setManualActiveTab(link.label);
if (isMobile) closeMobile();
}}
className={classes.mainLink}
data-active={link.label === active || undefined}
>
<link.icon size={22} stroke={1.5} />
</UnstyledButton>
</Tooltip>
));
const links = (submenuLinks[active as keyof typeof submenuLinks] || []).map((item) => (
<Link
className={classes.link}
data-active={activeLink === item.label || undefined}
to={item.to}
key={item.label}
style={{ display: isCollapsed ? 'none' : 'block' }}
onClick={() => {
if (isMobile) closeMobile();
}}
>
{item.label}
</Link>
));
useEffect(() => {
setManualActiveTab(null);
}, [location.pathname]);
useEffect(() => {
if (!isMobile) openDesktop();
}, [isMobile, openDesktop]);
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: isCollapsed ? 60 : 300,
breakpoint: 'sm',
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
}}
padding="md"
>
{/* Header */}
<AppShell.Header>
<Group h="100%" px="md">
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
<img src="/src/favicon.svg" alt="Logo" style={{ width: 30, height: 30 }} />
</Group>
</AppShell.Header>
export function CollapseDesktop() {
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure();
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
{/* Navbar */}
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: 'sm',
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
}}
padding="md"
>
<AppShell.Header>
<Group h="100%" px="md">
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
<img
src="/src/favicon.svg"
alt="Logo"
style={{ width: 30, height: 30 }}
/>
</Group>
</AppShell.Header>
<AppShell.Navbar p="md">
Navbar
{Array(15)
.fill(0)
.map((_, index) => (
<Skeleton key={index} h={28} mt="sm" animate={false} />
))}
</AppShell.Navbar>
<AppShell.Main>Main</AppShell.Main>
</AppShell>
);
}
<AppShell.Navbar>
<div className={classes.wrapper}>
<div className={classes.aside}>
<div className={classes.topSection}>
{mainLinks}
</div>
</div>
<div className={classes.main}>
{!isCollapsed && <Title order={4} className={classes.title}>{active}</Title>}
{links}
</div>
</div>
</AppShell.Navbar>
{/* Main Content */}
<AppShell.Main>
{/* Aquí va el contenido principal */}
Main Content
</AppShell.Main>
</AppShell>
);
}
-170
View File
@@ -1,170 +0,0 @@
import {
IconCalendarStats,
IconDeviceDesktopAnalytics,
IconFingerprint,
IconGauge,
IconHome2,
IconSettings,
IconUser,
IconArrowBarLeft,
IconArrowBarRight,
} from '@tabler/icons-react';
import {
Title,
Tooltip,
UnstyledButton,
ActionIcon,
} from '@mantine/core';
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { Link, useLocation } from 'react-router-dom';
import { useEffect, useMemo, useState } from 'react';
import classes from './DoubleNavbar.module.css';
const mainLinksdata = [
{ icon: IconHome2, label: 'Home' },
{ icon: IconGauge, label: 'Dashboard' },
{ icon: IconDeviceDesktopAnalytics, label: 'Analytics' },
{ icon: IconCalendarStats, label: 'Releases' },
{ icon: IconUser, label: 'Account' },
{ icon: IconFingerprint, label: 'Security' },
{ icon: IconSettings, label: 'Settings' },
];
const submenuLinks: Record<string, { label: string; to: string }[]> = {
Home: [
{ label: 'Inicio', to: '/' },
{ label: 'Consulta Api', to: '/Consulta_API' },
{ label: 'Prueba_appshell', to: '/prueba_appshell' },
],
Dashboard: [
{ label: 'Resumen', to: '/dashboard/resumen' },
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
{ label: 'Usuarios', to: '/dashboard/usuarios' },
],
Analytics: [
{ label: 'Conversiones', to: '/analytics/conversiones' },
{ label: 'Tráfico', to: '/analytics/trafico' },
{ label: 'Tendencias', to: '/analytics/tendencias' },
],
Releases: [
{ label: 'Notas de versión', to: '/releases/notas-de-version' },
{ label: 'Historial', to: '/releases/historial' },
],
Account: [
{ label: 'Perfil', to: '/account/perfil' },
{ label: 'Suscripciones', to: '/account/suscripciones' },
],
Security: [
{ label: 'Contraseña', to: '/security/contraseña' },
{ label: '2FA', to: '/security/2fa' },
],
Settings: [
{ label: 'Preferencias', to: '/settings/preferencias' },
{ label: 'Notificaciones', to: '/settings/notificaciones' },
],
};
export function DoubleNavbar() {
const location = useLocation();
const isMobile = useMediaQuery('(max-width: 768px)');
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
const isCollapsed = useMemo(() => (isMobile ? !mobileOpened : !desktopOpened), [isMobile, mobileOpened, desktopOpened]);
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) =>
items.some((item) => location.pathname.startsWith(item.to))
);
const routeBasedActive = matchedMain?.[0] ?? 'Home';
const active = manualActiveTab ?? routeBasedActive;
const activeLink =
submenuLinks[active]?.find((item) => location.pathname === item.to)?.label ?? '';
const mainLinks = mainLinksdata.map((link) => (
<Tooltip
label={link.label}
position="right"
withArrow
transitionProps={{ duration: 0 }}
key={link.label}
>
<UnstyledButton
onClick={() => {
setManualActiveTab(link.label);
if (isMobile) closeMobile();
}}
className={classes.mainLink}
data-active={link.label === active || undefined}
>
<link.icon size={22} stroke={1.5} />
</UnstyledButton>
</Tooltip>
));
const links = (submenuLinks[active] || []).map((item) => (
<Link
className={classes.link}
data-active={activeLink === item.label || undefined}
to={item.to}
key={item.label}
style={{ display: isCollapsed ? 'none' : 'block' }}
onClick={() => {
if (isMobile) closeMobile();
}}
>
{item.label}
</Link>
));
// Restaurar pestaña al cambiar ruta (opcional pero útil)
useEffect(() => {
setManualActiveTab(null);
}, [location.pathname]);
// Abrir navbar de escritorio si cambia a pantalla grande
useEffect(() => {
if (!isMobile) openDesktop();
}, [isMobile, openDesktop]);
return (
<nav className={`${classes.navbar} ${isCollapsed ? classes.collapsed : ''}`}>
<div className={classes.wrapper}>
<div className={classes.aside}>
<div className={classes.topSection}>
<div className={classes.logo}>
<img
src="/src/favicon.svg"
alt="Logo"
style={{ width: 30, height: 30 }}
/>
</div>
{mainLinks}
</div>
<ActionIcon
variant="subtle"
onClick={() => (isMobile ? toggleMobile() : toggleDesktop())}
className={classes.collapseButton}
size="lg"
>
{isCollapsed ? <IconArrowBarRight size={20} /> : <IconArrowBarLeft size={20} />}
</ActionIcon>
</div>
<div className={classes.main}>
{!isCollapsed && (
<Title order={4} className={classes.title}>
{active}
</Title>
)}
{links}
</div>
</div>
</nav>
);
}
-4
View File
@@ -1,16 +1,12 @@
import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/core';
import { IconArrowLeft } from '@tabler/icons-react';
import { Link } from 'react-router-dom';
import { DoubleNavbar } from '../components/DoubleNavbar'; // Ajusta ruta si es necesario
import { MantineCardWithShader } from '../components/HoloShader'; // Ajusta ruta si es necesario
export function Error_404() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar />
<Box style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '2rem' }}>
-2
View File
@@ -9,7 +9,6 @@ import {
Badge,
Group,
} from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { MetodoSelect } from '../components/MetodoSelect'; // 👈 Importación del nuevo componente
import { useEffect } from 'react';
@@ -88,7 +87,6 @@ export function Consulta_API() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar />
<Box style={{ flex: 1, padding: 40 }}>
<Center style={{ height: '100%' }}>
-8
View File
@@ -2,7 +2,6 @@ import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeTo
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { MantineCardWithShader } from '../components/HoloShader';
@@ -11,14 +10,7 @@ import { MantineCardWithShader } from '../components/HoloShader';
export function HomePage() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar /> {/* Sidebar fijo a la izquierda */}
{/* Contenido principal */}
<Box style={{ flex: 1, padding: '24px' }}>
<Welcome />
<ColorSchemeToggle />
</Box>
</Box>
);
}
+2 -3
View File
@@ -2,13 +2,12 @@ import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeTo
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { MantineCardWithShader } from '../components/HoloShader';
import { CollapseDesktop } from '../components/Appshell_collapse';
import {AppShellWithMenu } from '../components/Appshell_collapse';
export function Prueba_appshell() {
return (
<CollapseDesktop />
<AppShellWithMenu />
);
}