feat: añadir funcionalidad de colapso y expansión en la barra lateral
This commit is contained in:
parent
f37cec6d50
commit
1f21a605cc
@ -12,6 +12,7 @@ body {
|
|||||||
:root {
|
:root {
|
||||||
--desk-app-background: #000;
|
--desk-app-background: #000;
|
||||||
--desk-content-background: #f7f9fb;
|
--desk-content-background: #f7f9fb;
|
||||||
|
--desk-header-background: #fff;
|
||||||
--desk-sidebar-background: #0e282c;
|
--desk-sidebar-background: #0e282c;
|
||||||
--desk-sidebar-top: #0c252a;
|
--desk-sidebar-top: #0c252a;
|
||||||
--desk-sidebar-bottom: #102c30;
|
--desk-sidebar-bottom: #102c30;
|
||||||
@ -27,6 +28,8 @@ body {
|
|||||||
--desk-radius-md: 10px;
|
--desk-radius-md: 10px;
|
||||||
--desk-radius-shell: 20px;
|
--desk-radius-shell: 20px;
|
||||||
--desk-sidebar-width: 228px;
|
--desk-sidebar-width: 228px;
|
||||||
|
--desk-sidebar-inset: 20px;
|
||||||
|
--desk-sidebar-collapsed-width: 72px;
|
||||||
--desk-header-height: 88px;
|
--desk-header-height: 88px;
|
||||||
--desk-search-height: 41px;
|
--desk-search-height: 41px;
|
||||||
--desk-nav-height: 48px;
|
--desk-nav-height: 48px;
|
||||||
@ -59,9 +62,12 @@ body:has(.desk-workspace) {
|
|||||||
background: var(--desk-app-background);
|
background: var(--desk-app-background);
|
||||||
}
|
}
|
||||||
.desk-sidebar {
|
.desk-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
width: var(--desk-sidebar-width);
|
width: var(--desk-sidebar-width);
|
||||||
flex: 0 0 var(--desk-sidebar-width);
|
flex: 0 0 var(--desk-sidebar-width);
|
||||||
overflow-y: auto;
|
overflow: hidden;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
var(--desk-sidebar-top),
|
var(--desk-sidebar-top),
|
||||||
@ -71,6 +77,7 @@ body:has(.desk-workspace) {
|
|||||||
color: var(--desk-sidebar-text);
|
color: var(--desk-sidebar-text);
|
||||||
}
|
}
|
||||||
.desk-brand {
|
.desk-brand {
|
||||||
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 13px;
|
gap: 13px;
|
||||||
@ -88,10 +95,13 @@ body:has(.desk-workspace) {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.desk-company-rail {
|
.desk-company-rail {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
padding: 28px 13px 24px 26px;
|
padding: 28px var(--desk-sidebar-inset) 24px;
|
||||||
}
|
}
|
||||||
.desk-space-button {
|
.desk-space-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -137,7 +147,7 @@ body:has(.desk-workspace) {
|
|||||||
.desk-rail-separator {
|
.desk-rail-separator {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: rgb(255 255 255 / 12%);
|
background: rgb(255 255 255 / 12%);
|
||||||
margin: 26px -3px 25px -5px;
|
margin: 26px 0 25px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.desk-catalog-navigation {
|
.desk-catalog-navigation {
|
||||||
@ -156,6 +166,58 @@ body:has(.desk-workspace) {
|
|||||||
background: rgb(255 255 255 / 8%);
|
background: rgb(255 255 255 / 8%);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
.desk-sidebar-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 12px var(--desk-sidebar-inset) 20px;
|
||||||
|
border-top: 1px solid rgb(255 255 255 / 12%);
|
||||||
|
}
|
||||||
|
.desk-sidebar-toggle[aria-expanded="true"] > svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
.desk-catalog-navigation > button {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.desk-catalog-navigation > button > svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.desk-sidebar[data-collapsed="true"] {
|
||||||
|
width: var(--desk-sidebar-collapsed-width);
|
||||||
|
flex-basis: var(--desk-sidebar-collapsed-width);
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-brand {
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-brand > span,
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-space-button > span,
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-nav-chevron,
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-catalog-navigation > button > span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-company-rail {
|
||||||
|
padding: 28px 12px 12px;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-sidebar-footer {
|
||||||
|
padding: 12px 12px 20px;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-space-button {
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-catalog-navigation {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.desk-sidebar[data-collapsed="true"] .desk-catalog-navigation > button {
|
||||||
|
justify-content: center;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
.desk-workspace-content {
|
.desk-workspace-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -167,7 +229,9 @@ body:has(.desk-workspace) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 28px;
|
gap: 28px;
|
||||||
min-height: var(--desk-header-height);
|
min-height: var(--desk-header-height);
|
||||||
padding: 31px 30px 16px 17px;
|
padding: 15px 30px 15px 17px;
|
||||||
|
background: var(--desk-header-background);
|
||||||
|
border-bottom: 1px solid var(--desk-border);
|
||||||
color: var(--desk-navy);
|
color: var(--desk-navy);
|
||||||
}
|
}
|
||||||
.desk-search {
|
.desk-search {
|
||||||
@ -561,7 +625,7 @@ body:has(.desk-workspace) {
|
|||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
}
|
}
|
||||||
.desk-brand {
|
.desk-brand {
|
||||||
padding: 20px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
.desk-company-rail {
|
.desk-company-rail {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -645,3 +709,16 @@ body:has(.desk-workspace) {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.desk-company-rail {
|
||||||
|
flex: none;
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
.desk-sidebar-footer {
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
.desk-sidebar-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -155,3 +155,13 @@ servicio de notificaciones. El perfil usa disclosure nativo con foco visible y
|
|||||||
conserva las acciones de identidad y selección de idioma. No hay primitives Avatar
|
conserva las acciones de identidad y selección de idioma. No hay primitives Avatar
|
||||||
ni DropdownMenu instalados; el avatar muestra iniciales del usuario real, sin
|
ni DropdownMenu instalados; el avatar muestra iniciales del usuario real, sin
|
||||||
inventar foto ni cargo. Configuración abre un FormDialog con las acciones existentes.
|
inventar foto ni cargo. Configuración abre un FormDialog con las acciones existentes.
|
||||||
|
|
||||||
|
La sidebar global permite contraerse en escritorio de 228px a 72px mediante
|
||||||
|
«Contraer menú» / «Expandir menú». Conserva iconos, nombres accesibles y títulos
|
||||||
|
de ayuda también para compañías y subcatálogos. La navegación se desplaza por
|
||||||
|
separado, manteniendo Configuración y el control de anchura en el pie visible.
|
||||||
|
En móvil se conserva la navegación horizontal existente.
|
||||||
|
|
||||||
|
El menú expandido y su pie comparten márgenes horizontales de 20px mediante
|
||||||
|
`--desk-sidebar-inset`. El header global usa `--desk-header-background` (#FFFFFF),
|
||||||
|
con un borde inferior de 1px en `--desk-border`, sobre el contenido #F7F9FB.
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
import {
|
import {
|
||||||
|
BoxIcon,
|
||||||
Building2Icon,
|
Building2Icon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
DatabaseIcon,
|
DatabaseIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
|
KeyRoundIcon,
|
||||||
|
LayersIcon,
|
||||||
|
LayoutTemplateIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TicketIcon,
|
TicketIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@repo/shadcn-ui/lib/icons";
|
} from "@repo/shadcn-ui/lib/icons";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import type { WorkspaceContent } from "./workspace-content";
|
import type { WorkspaceContent } from "./workspace-content";
|
||||||
@ -35,9 +40,10 @@ export function WorkspaceSidebar({
|
|||||||
const { t: resourceTranslation } = useTranslation("resources");
|
const { t: resourceTranslation } = useTranslation("resources");
|
||||||
const { t: assetTranslation } = useTranslation("assets");
|
const { t: assetTranslation } = useTranslation("assets");
|
||||||
const { t: accessTranslation } = useTranslation("access");
|
const { t: accessTranslation } = useTranslation("access");
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
return (
|
return (
|
||||||
<aside className="desk-sidebar">
|
<aside className="desk-sidebar" data-collapsed={collapsed}>
|
||||||
<div className="desk-brand">
|
<div aria-label="Metis Desk" className="desk-brand" role="img">
|
||||||
<svg aria-hidden="true" viewBox="0 0 36 30">
|
<svg aria-hidden="true" viewBox="0 0 36 30">
|
||||||
<path
|
<path
|
||||||
d="M5 24 13 7l9 17 9-18"
|
d="M5 24 13 7l9 17 9-18"
|
||||||
@ -66,6 +72,7 @@ export function WorkspaceSidebar({
|
|||||||
className="desk-space-button"
|
className="desk-space-button"
|
||||||
key={area}
|
key={area}
|
||||||
onClick={() => onNavigate({ area })}
|
onClick={() => onNavigate({ area })}
|
||||||
|
title={t(label)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<Icon aria-hidden="true" />
|
<Icon aria-hidden="true" />
|
||||||
@ -79,47 +86,58 @@ export function WorkspaceSidebar({
|
|||||||
<nav aria-label={t("globalCatalogs")} className="desk-catalog-navigation">
|
<nav aria-label={t("globalCatalogs")} className="desk-catalog-navigation">
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={accessTranslation("catalogTitle")}
|
||||||
aria-pressed={catalog === "access"}
|
aria-pressed={catalog === "access"}
|
||||||
onClick={() => onSelectCatalog("access")}
|
onClick={() => onSelectCatalog("access")}
|
||||||
|
title={accessTranslation("catalogTitle")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{accessTranslation("catalogTitle")}
|
<KeyRoundIcon aria-hidden="true" />
|
||||||
|
<span>{accessTranslation("catalogTitle")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={assetTranslation("title")}
|
||||||
aria-pressed={catalog === "assets"}
|
aria-pressed={catalog === "assets"}
|
||||||
onClick={() => onSelectCatalog("assets")}
|
onClick={() => onSelectCatalog("assets")}
|
||||||
|
title={assetTranslation("title")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{assetTranslation("title")}
|
<BoxIcon aria-hidden="true" />
|
||||||
|
<span>{assetTranslation("title")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={t("interventionTemplates.title")}
|
||||||
aria-pressed={catalog === "intervention-templates"}
|
aria-pressed={catalog === "intervention-templates"}
|
||||||
onClick={() => onSelectCatalog("intervention-templates")}
|
onClick={() => onSelectCatalog("intervention-templates")}
|
||||||
|
title={t("interventionTemplates.title")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t("interventionTemplates.title")}
|
<LayoutTemplateIcon aria-hidden="true" />
|
||||||
|
<span>{t("interventionTemplates.title")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={assetTranslation("brands.title")}
|
||||||
aria-pressed={catalog === "brands"}
|
aria-pressed={catalog === "brands"}
|
||||||
onClick={() => onSelectCatalog("brands")}
|
onClick={() => onSelectCatalog("brands")}
|
||||||
|
title={assetTranslation("brands.title")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{assetTranslation("brands.title")}
|
<LayersIcon aria-hidden="true" />
|
||||||
|
<span>{assetTranslation("brands.title")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={resourceTranslation("title")}
|
||||||
aria-pressed={catalog === "resources"}
|
aria-pressed={catalog === "resources"}
|
||||||
onClick={() => onSelectCatalog("resources")}
|
onClick={() => onSelectCatalog("resources")}
|
||||||
|
title={resourceTranslation("title")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{resourceTranslation("title")}
|
<DatabaseIcon aria-hidden="true" />
|
||||||
|
<span>{resourceTranslation("title")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
<Button className="desk-space-button" onClick={onSettings} variant="ghost">
|
|
||||||
<SettingsIcon aria-hidden="true" />
|
|
||||||
<span>{coreTranslation("shell.settings")}</span>
|
|
||||||
</Button>
|
|
||||||
<div className="desk-rail-separator" />
|
<div className="desk-rail-separator" />
|
||||||
{companies.map((company) => (
|
{companies.map((company) => (
|
||||||
<Button
|
<Button
|
||||||
@ -136,6 +154,29 @@ export function WorkspaceSidebar({
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
<footer className="desk-sidebar-footer">
|
||||||
|
<Button
|
||||||
|
aria-label={coreTranslation("shell.settings")}
|
||||||
|
className="desk-space-button"
|
||||||
|
onClick={onSettings}
|
||||||
|
title={coreTranslation("shell.settings")}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<SettingsIcon aria-hidden="true" />
|
||||||
|
<span>{coreTranslation("shell.settings")}</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
aria-label={coreTranslation(collapsed ? "shell.expandSidebar" : "shell.collapseSidebar")}
|
||||||
|
className="desk-space-button desk-sidebar-toggle"
|
||||||
|
onClick={() => setCollapsed((value) => !value)}
|
||||||
|
title={coreTranslation(collapsed ? "shell.expandSidebar" : "shell.collapseSidebar")}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon aria-hidden="true" />
|
||||||
|
<span>{coreTranslation("shell.collapseSidebar")}</span>
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@
|
|||||||
"searchScope": "Search available companies and sections.",
|
"searchScope": "Search available companies and sections.",
|
||||||
"notificationsUnavailable": "Notifications: service not yet available",
|
"notificationsUnavailable": "Notifications: service not yet available",
|
||||||
"profile": "User menu",
|
"profile": "User menu",
|
||||||
"supportWorkspace": "Support workspace"
|
"supportWorkspace": "Support workspace",
|
||||||
|
"collapseSidebar": "Collapse menu",
|
||||||
|
"expandSidebar": "Expand menu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@
|
|||||||
"searchScope": "Buscar en compañías y secciones disponibles.",
|
"searchScope": "Buscar en compañías y secciones disponibles.",
|
||||||
"notificationsUnavailable": "Notificaciones: servicio todavía no disponible",
|
"notificationsUnavailable": "Notificaciones: servicio todavía no disponible",
|
||||||
"profile": "Menú de usuario",
|
"profile": "Menú de usuario",
|
||||||
"supportWorkspace": "Espacio de soporte"
|
"supportWorkspace": "Espacio de soporte",
|
||||||
|
"collapseSidebar": "Contraer menú",
|
||||||
|
"expandSidebar": "Expandir menú"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user