@stackcraftmx/calcita 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -2
- package/dist/appShell/AppLayout.js +5 -1
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +12 -1
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SideMenu.js +3 -1
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +27 -2
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/downloads/downloadQueue.js +3 -1
- package/dist/downloads/downloadQueue.js.map +1 -1
- package/dist/downloads/downloadsMachine.js +8 -0
- package/dist/downloads/downloadsMachine.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types/appShell/AppLayout.d.ts +1 -0
- package/dist/types/appShell/MobileTabBar.d.ts +1 -0
- package/dist/types/appShell/SideMenu.d.ts +1 -0
- package/dist/types/appShell/SideMenuEntry.d.ts +6 -0
- package/dist/types/downloads/downloadQueue.d.ts +5 -1
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -574,6 +574,10 @@ es lo de omisión. El botón de la barra fija claro u oscuro; Automático es la
|
|
|
574
574
|
Una marca que es una palabra larga va en `wordmark` (a 24 px de alto, en lugar del logo cuadrado y el nombre). Una entrada
|
|
575
575
|
del menú puede llevar a otro sistema (`href`) y traer su ícono ya pintado (`icon`) en lugar del componente (`Icon`).
|
|
576
576
|
|
|
577
|
+
Un nombre del menú que no cabe se corta con «…». Con `menuLabelOverflow={MENU_LABEL_OVERFLOW.wrap}` baja a un segundo
|
|
578
|
+
renglón y se lee completo («Administración de clientes»): en el menú lateral, en el cajón del teléfono y en «Más». También
|
|
579
|
+
desde el tema, para todo el proyecto: `components.CalcitaAppLayout.defaultProps.menuLabelOverflow`.
|
|
580
|
+
|
|
577
581
|
El menú no se escribe en una lista central: cada módulo trae su `menu.js` y `composeMenu` los junta, los ordena y deja
|
|
578
582
|
solo los que la sesión puede ver (también dentro de los submenús).
|
|
579
583
|
|
|
@@ -818,6 +822,23 @@ const template = await api.download('/documents/template', {method: 'POST', body
|
|
|
818
822
|
- Cada respuesta le avisa al aviso de versión nueva qué compilación está publicada (header `X-Frontend-Build`).
|
|
819
823
|
- Un Excel que llegó cortado no se entrega (también en una descarga con POST).
|
|
820
824
|
|
|
825
|
+
Para que la descarga siga mientras la persona trabaja, `startDownload` la pone en el panel de descargas (`DownloadManager`,
|
|
826
|
+
montado una vez en la raíz) con su avance, su cancelación y el motivo si falla. Un lote de varios archivos que cuenta como
|
|
827
|
+
**una** descarga dice en cuál va con `onLabelChange`, en el mismo renglón:
|
|
828
|
+
|
|
829
|
+
```js
|
|
830
|
+
startDownload({
|
|
831
|
+
label: t('files.label'),
|
|
832
|
+
request: async ({signal, onDownloadProgress, onLabelChange}) => {
|
|
833
|
+
for (const [position, file] of files.entries()) {
|
|
834
|
+
onLabelChange(t('files.progress', {position: position + 1, total: files.length, name: file.name}));
|
|
835
|
+
// …baja el archivo con {signal, onDownloadProgress}
|
|
836
|
+
}
|
|
837
|
+
return {blob, fileName}; // el lote ya armado en uno solo
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
```
|
|
841
|
+
|
|
821
842
|
Si se publica otra versión con la pestaña abierta, la pantalla que no se había abierto pide un archivo que ya no existe.
|
|
822
843
|
`withChunkRecovery` (de `/react-router`) recarga una vez y `RouteErrorScreen` dice «Hay una versión nueva» con su botón;
|
|
823
844
|
sin router de datos, `recoverFromStaleChunk` en el `.catch()` del import y `StaleBuildBoundary` alrededor de las pantallas.
|
|
@@ -1053,8 +1074,8 @@ import {FormRow, FormSection} from '@stackcraftmx/calcita';
|
|
|
1053
1074
|
</FormSection>
|
|
1054
1075
|
```
|
|
1055
1076
|
|
|
1056
|
-
Los campos
|
|
1057
|
-
|
|
1077
|
+
Los campos pueden ir directo dentro de la `FormSection` o dentro de lo que la pantalla necesite (un Grid de dos columnas, un
|
|
1078
|
+
Stack, un componente propio): cada renglón dibuja su propia línea arriba y la de hasta arriba la esconde la tarjeta.
|
|
1058
1079
|
|
|
1059
1080
|
En el teléfono la etiqueta se lleva el 38 % del renglón y un nombre largo se parte en tres renglones: con
|
|
1060
1081
|
`labelPlacement={FORM_LABEL_PLACEMENT.topOnPhone}` la etiqueta sube arriba del campo por debajo de `sm`, y en pantallas más
|
|
@@ -167,6 +167,7 @@ const AppLayoutFrame = ({
|
|
|
167
167
|
defaultCollapsed,
|
|
168
168
|
minimizeTabBarOnScroll,
|
|
169
169
|
contentMaxWidth,
|
|
170
|
+
menuLabelOverflow,
|
|
170
171
|
onMenuCollapse,
|
|
171
172
|
onMobileMenuChange,
|
|
172
173
|
onMobileTabsChange,
|
|
@@ -197,7 +198,7 @@ const AppLayoutFrame = ({
|
|
|
197
198
|
const isBarMerged = isGlass && isSideMenu && collapsed && !ribbons && !usesBarCapsules;
|
|
198
199
|
const drawerWidth = collapsed ? DRAWER_WIDTH_COLLAPSED : DRAWER_WIDTH_EXPANDED;
|
|
199
200
|
const activeItem = activeMenuItemOf(menuItems, pathname);
|
|
200
|
-
const sideMenuProps = { logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, menuBadges, footer: sidebarFooter };
|
|
201
|
+
const sideMenuProps = { logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, menuBadges, footer: sidebarFooter, labelOverflow: menuLabelOverflow };
|
|
201
202
|
const [barActionsSlot, setBarActionsSlot] = useState(null);
|
|
202
203
|
const usesPhoneFloatingActions = usesTabBar && usesBarCapsules;
|
|
203
204
|
const [phoneBarActionsSlot, setPhoneBarActionsSlot] = useState(null);
|
|
@@ -332,6 +333,7 @@ const AppLayoutFrame = ({
|
|
|
332
333
|
menuBadges,
|
|
333
334
|
moreFooter: sidebarFooter,
|
|
334
335
|
minimizeOnScroll: minimizeTabBarOnScroll,
|
|
336
|
+
labelOverflow: menuLabelOverflow,
|
|
335
337
|
onPinnedTabsChange: onMobileTabsChange,
|
|
336
338
|
account: /* @__PURE__ */ jsx(
|
|
337
339
|
HeaderTools,
|
|
@@ -500,6 +502,7 @@ const AppLayoutContent = (props) => {
|
|
|
500
502
|
defaultCollapsed = false,
|
|
501
503
|
minimizeTabBarOnScroll = true,
|
|
502
504
|
contentMaxWidth = 1400,
|
|
505
|
+
menuLabelOverflow,
|
|
503
506
|
onMenuCollapse,
|
|
504
507
|
onMobileMenuChange,
|
|
505
508
|
onMobileTabsChange,
|
|
@@ -538,6 +541,7 @@ const AppLayoutContent = (props) => {
|
|
|
538
541
|
defaultCollapsed,
|
|
539
542
|
minimizeTabBarOnScroll,
|
|
540
543
|
contentMaxWidth,
|
|
544
|
+
menuLabelOverflow,
|
|
541
545
|
onMenuCollapse,
|
|
542
546
|
onMobileMenuChange,
|
|
543
547
|
onMobileTabsChange,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppLayout.js","sources":["../../src/appShell/AppLayout.jsx"],"sourcesContent":["// El marco de las pantallas con sesión, al estilo de las plantillas de MUI (y del marco de walk-ot). Dos estilos de\n// barra con las mismas props, para que el proyecto (o el `init` del CLI) solo elija:\n//\n// menuPosition=\"side\" (por omisión) menuPosition=\"top\" (apps de pocos módulos)\n// ┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐\n// │ ☰ Logo Nombre · Título al deslizar · ⚙ (A)│ │ Logo · Inicio Pedidos Clientes ▾ · ⚙ (A) │\n// ├────────┬─────────────────────────────────┤ ├──────────────────────────────────────────┤\n// │ Menú │ contenido │ │ contenido │\n// │ por │ │ │ │\n// │ secc. │ │ │ │\n// └────────┴─────────────────────────────────┘ └──────────────────────────────────────────┘\n//\n// En el teléfono los dos son iguales: hamburguesa, título y ajustes en la barra; el menú sale en un cajón con el logo.\n//\n// Lo que trae, con su porqué:\n// - La barra va a todo lo ancho, con el logo y el nombre de la app a la izquierda, y el menú lateral debajo de ella\n// (como el DashboardLayout de MUI). La ☰ de la barra encoge el menú a solo íconos y lo recuerda (useDrawerCollapsed);\n// antes era un botón montado en la orilla del menú y se encimaba con él.\n// - El título de la página sube a la barra cuando su título grande sale de la vista (pageTitle.jsx).\n// - Escritorio y teléfono se eligen con CSS, no con useMediaQuery: el servidor (Next.js) ya pinta lo correcto.\n// - Dentro de la barra, para que se queden pegados al deslizar: los avisos del proyecto (ribbons), el de versión nueva\n// (NewBuildNotice; con vidrio flota aparte en su cápsula, ver NewBuildNotice.jsx) y la rayita de «cargando la pantalla»\n// (isNavigating). La barra publica su alto real, y el menú lateral empieza justo debajo de ella aunque crezca con un aviso.\n// - Con vidrio, en el teléfono la barra flota ABAJO en una cápsula, como la de Safari en el iPhone; y en escritorio, con\n// el menú encogido, la barra desaparece y se funde con el menú (☰ y logo arriba, ajustes y cuenta abajo).\n// - El primer Tab lleva a «Saltar al contenido» (WCAG 2.4.1) y el contenido es <main>. Respeta el área segura del\n// iPhone (la muesca) cuando la app corre instalada.\n// - Huecos para extender sin tocar el framework: headerActions, sidebarFooter, accountMenuItems, accountSettings, ribbons.\n// - Los ajustes de la app (letra, idioma, claro u oscuro y los del proyecto) van DENTRO del menú de la cuenta, no sueltos\n// en la barra (HeaderTools).\n// - El menú queda al alcance de las páginas (appMenuContext): PageHeading arma sus migas sola.\n// - Con el vidrio del tema (createToolkitTheme({glass: true})): el lienzo lleva manchas de color, y en escritorio la\n// barra y el menú lateral flotan a 8 px de la orilla y entre ellos, con esquinas redondas, para que el contenido pase\n// por detrás y se vea desenfocado; encogido, el menú es una cápsula. En el teléfono la barra sigue de orilla a orilla\n// (como en iOS). Sin vidrio, todo igual que siempre: el marco lo sabe por el tema (isGlassTheme), sin otra prop.\n// - Con vidrio y con el minimalista la barra no lleva franja, como las ventanas de macOS Tahoe (TOP_BAR.capsules, lo de\n// omisión): la ☰ con el nombre, el menú de arriba y la cuenta van cada uno en su cápsula, y el título queda suelto.\n// Con vidrio las cápsulas son de vidrio y el contenido pasa por debajo, desenfocado en la orilla (scrollEdgeStylesOf);\n// con el minimalista son bloques del tono de las tarjetas sobre la barra del color del fondo, también en el teléfono.\n// Con cápsulas el menú encogido ya no se funde con la barra: la cuenta y los ajustes se quedan arriba. Con\n// bar: TOP_BAR.band, la franja de lado a lado. Mismo alto y mismo lugar las dos: el menú lateral y las páginas no se\n// mueven.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAppLayout con defaultProps y\n// styleOverrides de sus partes: skipLink, brand, brandLogo, brandText, brandName, brandCaption, titleArea, phoneTitle,\n// desktopTitle, railBrand, railLogo, collapseButton, root, scrollEdge, bar, toolbar, menuButton, spacer, tools,\n// progress, body, drawer, main, phoneSlot, content, phoneDrawer.\n//\n// Lo renderiza la app: arrow (regla de la casa).\nimport {useMemo, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation, useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport AppBar from '@mui/material/AppBar';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Drawer from '@mui/material/Drawer';\nimport Fade from '@mui/material/Fade';\nimport IconButton from '@mui/material/IconButton';\nimport LinearProgress from '@mui/material/LinearProgress';\nimport Link from '@mui/material/Link';\nimport Toolbar from '@mui/material/Toolbar';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport MenuOpenIcon from '@mui/icons-material/MenuOpen';\n\nimport {AppMenuContext} from '@/appShell/appMenuContext.js';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {BarActionsSlotContext} from '@/appShell/barActionsSlot.js';\nimport HeaderTools from '@/appShell/HeaderTools.jsx';\nimport MobileTabBar from '@/appShell/MobileTabBar.jsx';\nimport Wordmark from '@/appShell/Wordmark.jsx';\nimport {PageTitleProvider, usePageTitle} from '@/appShell/pageTitle.jsx';\nimport {\n APP_BAR_HEIGHT_PIXELS, DESKTOP_MEDIA_QUERY, DRAWER_WIDTH_COLLAPSED, DRAWER_WIDTH_EXPANDED, GLASS_PANEL_GAP_PIXELS, MAIN_CONTENT_ID,\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, MOBILE_TAB_BAR_HEIGHT_PIXELS, PHONE_MEDIA_QUERY, drawerTransitionOf,\n} from '@/appShell/shellSizes.js';\nimport SideMenu from '@/appShell/SideMenu.jsx';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport TopMenu from '@/appShell/TopMenu.jsx';\nimport {useDrawerCollapsed} from '@/appShell/useDrawerCollapsed.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport NewBuildNotice, {NEW_BUILD_NOTICE_VARIANT} from '@/feedback/NewBuildNotice.jsx';\nimport {activeMenuItemOf, menuItemsWithIconsOf} from '@/navigation/appMenu.js';\nimport {useNavigationTrail} from '@/navigationTrail.js';\nimport {THEME_LOOK, TOP_BAR} from '@/theme/createToolkitTheme.js';\nimport {PANEL_RADIUS, ambientBackgroundOf, isGlassTheme, scrollEdgeStylesOf} from '@/theme/glassSurface.js';\nimport GlassLensFilter from '@/ui/layout/GlassLensFilter.jsx';\nimport {APP_BAR_HEIGHT, useAppBarHeightPublisher} from '@/ui/layout/appBarHeight.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAppLayout)\nconst THEME_NAME = 'CalcitaAppLayout';\nconst LayoutSkipLink = piecePart(THEME_NAME, 'SkipLink', Link);\nconst LayoutBrand = piecePart(THEME_NAME, 'Brand', ButtonBase);\nconst LayoutBrandLogo = piecePart(THEME_NAME, 'BrandLogo', Box);\nconst LayoutBrandText = piecePart(THEME_NAME, 'BrandText', Box);\nconst LayoutBrandName = piecePart(THEME_NAME, 'BrandName', Typography);\nconst LayoutBrandCaption = piecePart(THEME_NAME, 'BrandCaption', Typography);\nconst LayoutTitleArea = piecePart(THEME_NAME, 'TitleArea', Box);\nconst LayoutPhoneTitle = piecePart(THEME_NAME, 'PhoneTitle', Typography);\nconst LayoutDesktopTitle = piecePart(THEME_NAME, 'DesktopTitle', Typography);\nconst LayoutRailBrand = piecePart(THEME_NAME, 'RailBrand', ButtonBase);\nconst LayoutRailLogo = piecePart(THEME_NAME, 'RailLogo', Box);\nconst LayoutCollapseButton = piecePart(THEME_NAME, 'CollapseButton', IconButton);\nconst LayoutRoot = piecePart(THEME_NAME, 'Root', Box);\nconst LayoutScrollEdge = piecePart(THEME_NAME, 'ScrollEdge', Box);\nconst LayoutBar = piecePart(THEME_NAME, 'Bar', AppBar);\nconst LayoutToolbar = piecePart(THEME_NAME, 'Toolbar', Toolbar);\nconst LayoutMenuButton = piecePart(THEME_NAME, 'MenuButton', IconButton);\nconst LayoutSpacer = piecePart(THEME_NAME, 'Spacer', Box);\nconst LayoutTools = piecePart(THEME_NAME, 'Tools', Box);\nconst LayoutProgress = piecePart(THEME_NAME, 'Progress', LinearProgress);\nconst LayoutBody = piecePart(THEME_NAME, 'Body', Box);\nconst LayoutDrawer = piecePart(THEME_NAME, 'Drawer', Drawer);\nconst LayoutMain = piecePart(THEME_NAME, 'Main', Box);\nconst LayoutPhoneSlot = piecePart(THEME_NAME, 'PhoneSlot', Box);\nconst LayoutContent = piecePart(THEME_NAME, 'Content', Box);\nconst LayoutPhoneDrawer = piecePart(THEME_NAME, 'PhoneDrawer', Drawer);\n\n/** Los dos estilos de barra. */\nexport const MENU_POSITION = Object.freeze({side: 'side', top: 'top'});\n\nconst NAVIGATING_BAR_PIXELS = 2;\n\n/**\n * Un grupo de la barra: con la barra en cápsulas, su cápsula (BarCapsule: de vidrio, o con el minimalista un bloque del\n * tono de las tarjetas); con la franja, sus piezas tal cual (sin caja de más, para que la franja se quede idéntica).\n */\nconst BarGroup = ({isCapsule, sx, children}) => {\n if (!isCapsule) {\n return children;\n }\n // presentation: lo de adentro (la ☰, el nombre, la cuenta) ya se anuncia solo; el grupo es solo la forma\n return <BarCapsule role=\"presentation\" sx={sx}>{children}</BarCapsule>;\n};\n\n/** «Saltar al contenido»: escondido arriba hasta que llega el foco del teclado. */\nconst SkipToContentLink = () => {\n const {t} = useToolkitTranslation('layout');\n return (\n <LayoutSkipLink href={`#${MAIN_CONTENT_ID}`} ownSx={{\n position: 'absolute', left: 8, top: -48, zIndex: (theme) => theme.zIndex.tooltip + 1, bgcolor: 'background.paper',\n px: 1.5, py: 0.75, borderRadius: 1, boxShadow: 3, '&:focus': {top: 8},\n }}>\n {t('skipToContent')}\n </LayoutSkipLink>\n );\n};\n\n/** El logo y el nombre de la app en la barra (con su renglón chico, si lo hay), o el logo ancho; lleva al inicio. */\nconst BarBrand = ({logo, wordmark, brandName, brandCaption, homePath, sx}) => {\n const {Link: RouterLink} = useToolkitNavigation();\n return (\n <LayoutBrand component={RouterLink} to={homePath} aria-label={wordmark ? brandName : undefined}\n ownSx={[{alignItems: 'center', gap: 1.25, borderRadius: 1, flexShrink: 0, minWidth: 0, pr: 1, textAlign: 'left'}, sx]}>\n {wordmark ? <Wordmark>{wordmark}</Wordmark> : (\n <>\n {logo && <LayoutBrandLogo ownSx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</LayoutBrandLogo>}\n <LayoutBrandText ownSx={{minWidth: 0}}>\n <LayoutBrandName variant=\"subtitle2\" component=\"span\" noWrap ownSx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</LayoutBrandName>\n {/* el renglón chico hereda el color de la barra (sobre el naranja, el gris del texto secundario no se leía) */}\n {brandCaption && <LayoutBrandCaption variant=\"caption\" component=\"span\" noWrap ownSx={{display: 'block', lineHeight: 1.3, opacity: 0.8}}>{brandCaption}</LayoutBrandCaption>}\n </LayoutBrandText>\n </>\n )}\n </LayoutBrand>\n );\n};\n\n/**\n * El título en la barra: en escritorio, después del nombre de la app y solo cuando el título grande ya no se ve; en el\n * teléfono siempre (o el nombre de la app).\n */\nconst BarTitle = ({brandName}) => {\n const {title, isTitleVisible} = usePageTitle();\n return (\n <LayoutTitleArea ownSx={{flexGrow: 1, minWidth: 0}}>\n <LayoutPhoneTitle variant=\"subtitle1\" component=\"p\" noWrap ownSx={{display: 'block', [DESKTOP_MEDIA_QUERY]: {display: 'none'}, fontWeight: 600}}>{title ?? brandName}</LayoutPhoneTitle>\n <Fade in={Boolean(title) && !isTitleVisible}>\n <LayoutDesktopTitle variant=\"subtitle1\" component=\"p\" noWrap\n ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'}, fontWeight: 600, ml: 1, pl: 2, borderLeft: '1px solid color-mix(in srgb, currentColor 30%, transparent)'}}>\n {title}\n </LayoutDesktopTitle>\n </Fade>\n </LayoutTitleArea>\n );\n};\n\n/** El logo solo, arriba del menú encogido que hace de barra; lleva al inicio. */\nconst RailBrand = ({logo, brandName, homePath}) => {\n const {Link: RouterLink} = useToolkitNavigation();\n return (\n <Tooltip title={brandName} placement=\"right\">\n <LayoutRailBrand component={RouterLink} to={homePath} aria-label={brandName}\n ownSx={{width: 44, height: 44, borderRadius: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>\n <LayoutRailLogo ownSx={{width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</LayoutRailLogo>\n </LayoutRailBrand>\n </Tooltip>\n );\n};\n\n/** La ☰ de escritorio: encoge el menú lateral a solo íconos o lo vuelve a abrir. */\nconst CollapseMenuButton = ({collapsed, onToggle}) => {\n const {t} = useToolkitTranslation('layout');\n const label = collapsed ? t('drawer.expand') : t('drawer.collapse');\n return (\n <Tooltip title={label}>\n <LayoutCollapseButton aria-label={label} aria-expanded={!collapsed} onClick={onToggle} ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'inline-flex'}, color: 'inherit'}}>\n {collapsed ? <MenuIcon /> : <MenuOpenIcon />}\n </LayoutCollapseButton>\n </Tooltip>\n );\n};\n\nconst AppLayoutFrame = ({\n logo, wordmark, brandName, brandCaption, homePath, menuItems, menuPosition, user, profilePath, accountMenuItems, onSignOut, isSigningOut, headerActions,\n accountSettings, menuBadges, sidebarFooter, showFontSizeToggle, showLanguageToggle, showColorSchemeToggle, ribbons, isNavigating, defaultCollapsed, minimizeTabBarOnScroll, contentMaxWidth,\n onMenuCollapse, onMobileMenuChange, onMobileTabsChange, children,\n}) => {\n const {t} = useToolkitTranslation('layout');\n const {useLocation} = useToolkitNavigation();\n const {pathname} = useLocation();\n const {collapsed, toggle} = useDrawerCollapsed(defaultCollapsed);\n const [isMobileMenuOpen, setIsMobileMenuOpenState] = useState(false);\n // cada cambio del menú también sale afuera (para medir, o para que el proyecto guarde la preferencia en su servidor)\n const toggleCollapsed = () => {\n toggle();\n onMenuCollapse?.(!collapsed);\n };\n const setIsMobileMenuOpen = (isOpen) => {\n setIsMobileMenuOpenState(isOpen);\n onMobileMenuChange?.(isOpen);\n };\n const appBarRef = useAppBarHeightPublisher();\n useNavigationTrail();\n\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n // la barra de pestañas del teléfono (estilo Apple): con vidrio, y con el minimalista en cápsulas (usesMobileTabBar);\n // sin vidrio, o con el minimalista en franja, se queda la barra de MUI arriba con su cajón\n const usesTabBar = usesMobileTabBar(theme);\n\n const isSideMenu = menuPosition !== MENU_POSITION.top;\n // con vidrio y el menú encogido, en escritorio la barra de arriba desaparece y se funde con el menú: la ☰ y el logo van\n // arriba del menú y los ajustes y la cuenta abajo (lo pidió el autor). Si el proyecto trae avisos en la barra (ribbons),\n // la barra se queda: los avisos no tendrían dónde ir\n // la barra sin franja, en cápsulas (lo de omisión con vidrio y con el minimalista; TOP_BAR.band pide la franja). El\n // estilo de Stackcraft sin vidrio se queda con su barra de siempre\n const isMinimalLook = theme.toolkit?.look === THEME_LOOK.minimal;\n const usesBarCapsules = (isGlass || isMinimalLook) && theme.toolkit?.bar === TOP_BAR.capsules;\n // con vidrio, o con cápsulas, la barra y el menú lateral van a GLASS_PANEL_GAP_PIXELS de la orilla: la columna del menú\n // mide eso de más. OJO: con el minimalista en cápsulas era 0 y las cápsulas quedaban pegadas arriba (a 4 px) y el menú\n // pegado a la izquierda (lo reportó el autor; medido en Chrome)\n const panelGap = isGlass || usesBarCapsules ? GLASS_PANEL_GAP_PIXELS : 0;\n // con cápsulas no se funde: no hay franja que esconder, y la cuenta y los ajustes se quedan arriba en su cápsula en\n // lugar de bajar al pie del menú encogido (lo pidió el autor)\n const isBarMerged = isGlass && isSideMenu && collapsed && !ribbons && !usesBarCapsules;\n const drawerWidth = collapsed ? DRAWER_WIDTH_COLLAPSED : DRAWER_WIDTH_EXPANDED;\n const activeItem = activeMenuItemOf(menuItems, pathname);\n const sideMenuProps = {logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, menuBadges, footer: sidebarFooter};\n // el lugar de la barra donde caen las acciones de cada pantalla (BarActions); con la barra fundida con el menú no hay\n // barra en escritorio y se quedan en la pantalla\n const [barActionsSlot, setBarActionsSlot] = useState(null);\n // en el teléfono con cápsulas no hay barra de arriba (la de pestañas va abajo): las acciones flotan arriba a la derecha\n const usesPhoneFloatingActions = usesTabBar && usesBarCapsules;\n const [phoneBarActionsSlot, setPhoneBarActionsSlot] = useState(null);\n const barActions = useMemo(() => ({\n slot: isBarMerged ? null : barActionsSlot,\n phoneSlot: usesPhoneFloatingActions ? phoneBarActionsSlot : null,\n usesCapsules: usesBarCapsules,\n }), [isBarMerged, barActionsSlot, usesPhoneFloatingActions, phoneBarActionsSlot, usesBarCapsules]);\n\n return (\n <BarActionsSlotContext.Provider value={barActions}>\n <LayoutRoot ownSx={[{display: 'flex', flexDirection: 'column', minHeight: '100vh', bgcolor: 'background.default'}, isGlass && ambientBackgroundOf]}>\n <SkipToContentLink />\n <GlassLensFilter />\n\n {/* en cápsulas, lo que pasa por debajo de la barra se desenfoca en la orilla: debajo del menú lateral, que sigue nítido */}\n {isGlass && usesBarCapsules && (\n <LayoutScrollEdge aria-hidden=\"true\"\n ownSx={(theme) => ({\n ...scrollEdgeStylesOf(theme), display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'},\n position: 'fixed', top: 0, left: 0, right: 0, height: `calc(${APP_BAR_HEIGHT} + ${panelGap}px)`,\n zIndex: theme.zIndex.drawer - 1, pointerEvents: 'none',\n })} />\n )}\n {/* en cápsulas con vidrio la barra va transparente (color=\"transparent\": el tema no le pone vidrio) y cada grupo\n lleva el suyo; con el minimalista se queda del color del fondo, que tapa lo que pasa por debajo */}\n <LayoutBar ref={appBarRef} position=\"sticky\" color={isGlass && usesBarCapsules ? 'transparent' : undefined}\n ownSx={(theme) => ({\n pt: 'env(safe-area-inset-top)',\n // encima del menú lateral, como en el DashboardLayout de MUI\n zIndex: theme.zIndex.drawer + 1,\n // con vidrio flota: en escritorio arriba, separada de las orillas; en el teléfono ABAJO, en una cápsula,\n // como la barra de Safari en el iPhone (en el teléfono se veía cuadrada y pegada arriba; lo reportaron).\n // Abajo se suma la zona del gesto de inicio del iPhone (safe-area) a la separación\n ...(isGlass && {\n // en cápsulas no se recorta: la sombra de cada cápsula se saldría cortada\n overflow: usesBarCapsules ? 'visible' : 'hidden',\n [DESKTOP_MEDIA_QUERY]: {\n top: panelGap, mt: `${panelGap}px`, mx: `${panelGap}px`, width: 'auto',\n borderRadius: PANEL_RADIUS,\n // sin la raya de abajo de la barra fija: no hay franja que separar\n ...(usesBarCapsules && {borderBottom: 0, boxShadow: 'none'}),\n ...(isBarMerged && {display: 'none'}),\n },\n }),\n // el minimalista en cápsulas no flota (su barra es del color del fondo y tapa lo que pasa por debajo):\n // la separación de las orillas va por dentro, para que las cápsulas queden donde las del vidrio\n ...(!isGlass && usesBarCapsules && {\n [DESKTOP_MEDIA_QUERY]: {pt: `calc(env(safe-area-inset-top) + ${panelGap}px)`, px: `${panelGap}px`},\n }),\n // en el teléfono la barra de pestañas (MobileTabBar) la reemplaza: con ella van el menú y la cuenta\n ...(usesTabBar && {[PHONE_MEDIA_QUERY]: {display: 'none'}}),\n })}>\n {/* a la izquierda, la ☰ queda alineada con los íconos del menú lateral (los dos a 32 px de su orilla) */}\n <LayoutToolbar disableGutters ownSx={{minHeight: {xs: APP_BAR_HEIGHT_PIXELS, sm: APP_BAR_HEIGHT_PIXELS}, gap: 1, pl: {xs: 1.5, md: isSideMenu ? 1.5 : 3}, pr: {xs: 1.5, md: 3}}}>\n {/* la ☰ del teléfono va en la primera cápsula (en escritorio la de encoger el menú y el nombre). OJO: dentro de\n una cápsula, sin el margen negativo de «orilla de inicio» de MUI (-12 px): el botón quedaba pegado a la\n curva. Así su círculo queda concéntrico con la cápsula */}\n <BarGroup isCapsule={usesBarCapsules}>\n <LayoutMenuButton edge=\"start\" aria-label={t('drawer.open')} aria-expanded={isMobileMenuOpen} onClick={() => setIsMobileMenuOpen(true)}\n ownSx={{display: 'inline-flex', [DESKTOP_MEDIA_QUERY]: {display: 'none'}, color: 'inherit', ...((isGlass || usesBarCapsules) && {ml: 0})}}>\n <MenuIcon />\n </LayoutMenuButton>\n {isSideMenu && <CollapseMenuButton collapsed={collapsed} onToggle={toggleCollapsed} />}\n <BarBrand logo={logo} wordmark={wordmark} brandName={brandName} brandCaption={brandCaption} homePath={homePath}\n sx={isSideMenu ? {display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}} : {display: 'flex'}} />\n </BarGroup>\n {isSideMenu ? (\n <BarTitle brandName={brandName} />\n ) : (\n <>\n {/* el menú de arriba no sale en el teléfono: su cápsula tampoco (vacía se vería) */}\n <BarGroup isCapsule={usesBarCapsules} sx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}}}>\n <TopMenu menuItems={menuItems} activeItem={activeItem} />\n </BarGroup>\n <LayoutSpacer ownSx={{flexGrow: 1}} />\n </>\n )}\n {/* las acciones de la pantalla abierta (BarActions, y las de PageHeading con cápsulas): cada grupo cae aquí,\n antes de la cuenta. Solo en escritorio: en el teléfono flotan arriba (phoneBarActionsSlot, en el contenido) */}\n <LayoutTools ref={setBarActionsSlot} ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}, alignItems: 'center', gap: 1, minWidth: 0}} />\n <BarGroup isCapsule={usesBarCapsules}>\n <HeaderTools headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle} showLanguageToggle={showLanguageToggle}\n showColorSchemeToggle={showColorSchemeToggle} user={user} profilePath={profilePath} accountMenuItems={accountMenuItems}\n onSignOut={onSignOut} isSigningOut={isSigningOut} />\n </BarGroup>\n </LayoutToolbar>\n {ribbons}\n {/* con la barra de pestañas el aviso de versión nueva flota aparte, en su cápsula (en el teléfono esta barra no\n se ve); sin ella, franja dentro de la barra */}\n {!usesTabBar && <NewBuildNotice />}\n {isNavigating && (\n <LayoutProgress aria-label={t('navigating')} color=\"inherit\"\n ownSx={{position: 'absolute', bottom: 0, left: 0, right: 0, height: NAVIGATING_BAR_PIXELS}} />\n )}\n </LayoutBar>\n {usesTabBar && <NewBuildNotice variant={NEW_BUILD_NOTICE_VARIANT.floating} />}\n {usesTabBar && (\n <MobileTabBar menuItems={menuItems} activeItem={activeItem} menuBadges={menuBadges} moreFooter={sidebarFooter} minimizeOnScroll={minimizeTabBarOnScroll}\n onPinnedTabsChange={onMobileTabsChange}\n account={(\n <HeaderTools headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} user={user}\n profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut} isSigningOut={isSigningOut} />\n )} />\n )}\n\n <LayoutBody ownSx={{display: 'flex', flexGrow: 1, minWidth: 0}}>\n {isSideMenu && (\n <LayoutDrawer variant=\"permanent\"\n ownSx={(theme) => ({\n display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'}, width: drawerWidth + panelGap, flexShrink: 0, whiteSpace: 'nowrap',\n transition: drawerTransitionOf(theme),\n '& .MuiDrawer-paper': {\n width: drawerWidth, left: panelGap, boxSizing: 'border-box', overflowX: 'hidden', transition: drawerTransitionOf(theme, ['width']),\n // justo debajo de la barra (su alto real, con avisos o sin ellos) y hasta abajo; si la barra se\n // fundió con el menú, de arriba a abajo\n ...(isBarMerged\n ? {top: panelGap, height: `calc(100% - ${panelGap * 2}px)`}\n : {top: `calc(${APP_BAR_HEIGHT} + ${panelGap}px)`, height: `calc(100% - ${APP_BAR_HEIGHT} - ${panelGap * 2}px)`}),\n // el panel flotante: separado de las orillas y sin la línea de la derecha (lo separa su brillo).\n // Encogido es una cápsula (radio de la mitad de su ancho), como las barras de pestañas de vidrio\n ...(isGlass && {\n border: 0,\n borderRadius: PANEL_RADIUS,\n }),\n },\n })}>\n <SideMenu {...sideMenuProps} showBrand={false} collapsed={collapsed}\n railTop={isBarMerged ? (\n <>\n <CollapseMenuButton collapsed={collapsed} onToggle={toggleCollapsed} />\n {logo && <RailBrand logo={logo} brandName={brandName} homePath={homePath} />}\n </>\n ) : undefined}\n railBottom={isBarMerged ? (\n <HeaderTools vertical headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} user={user}\n profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut}\n isSigningOut={isSigningOut} />\n ) : undefined} />\n </LayoutDrawer>\n )}\n\n <LayoutMain component=\"main\" id={MAIN_CONTENT_ID} tabIndex={-1}\n ownSx={(theme) => ({\n flexGrow: 1, minWidth: 0, outline: 'none', px: {xs: 2, md: 3, lg: 4}, pt: 3, pb: 6,\n // con la barra de pestañas abajo (teléfono): arriba, lugar para la hora y la cámara del iPhone; abajo,\n // lugar para que la barra (que flota a GLASS_PANEL_GAP_PIXELS) no tape el final de la página\n ...(usesTabBar && {\n [PHONE_MEDIA_QUERY]: {\n pt: `calc(${theme.spacing(3)} + env(safe-area-inset-top))`,\n pb: `calc(${MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + GLASS_PANEL_GAP_PIXELS * 2}px + env(safe-area-inset-bottom) + ${theme.spacing(3)} + var(${FLOATING_BOTTOM_SPACE_CSS_VARIABLE}, 0px))`,\n },\n }),\n })}>\n {/* teléfono con cápsulas: el renglón de las acciones de la pantalla, arriba a la derecha. Va pegado (sticky)\n y no fijo: así ocupa su lugar arriba de la página (no tapa las migas ni el título) y al bajar se queda\n flotando. Vacío no se ve. Solo las cápsulas reciben clics: el hueco de la izquierda deja pasar el dedo\n a lo de abajo */}\n {usesPhoneFloatingActions && (\n <LayoutPhoneSlot ref={setPhoneBarActionsSlot} data-phone-bar-actions=\"\"\n ownSx={(theme) => ({\n display: 'none',\n [PHONE_MEDIA_QUERY]: {\n display: 'flex', '&:empty': {display: 'none'},\n position: 'sticky', top: `calc(env(safe-area-inset-top) + ${GLASS_PANEL_GAP_PIXELS}px)`, zIndex: theme.zIndex.appBar,\n justifyContent: 'flex-end', alignItems: 'center', gap: 1, mb: 2,\n pointerEvents: 'none', '& > *': {pointerEvents: 'auto'},\n },\n })} />\n )}\n <LayoutContent ownSx={{maxWidth: contentMaxWidth, mx: 'auto'}}>{children}</LayoutContent>\n </LayoutMain>\n </LayoutBody>\n\n {/* el cajón del teléfono (los dos estilos): con el logo arriba, se cierra al elegir una pantalla */}\n <LayoutPhoneDrawer variant=\"temporary\" open={isMobileMenuOpen} onClose={() => setIsMobileMenuOpen(false)} ModalProps={{keepMounted: true}}\n ownSx={{[DESKTOP_MEDIA_QUERY]: {display: 'none'}}}\n slotProps={{paper: {sx: {width: DRAWER_WIDTH_EXPANDED, boxSizing: 'border-box', pt: 'env(safe-area-inset-top)', pb: 'env(safe-area-inset-bottom)'}}}}>\n <SideMenu {...sideMenuProps} collapsed={false} onNavigate={() => setIsMobileMenuOpen(false)} />\n </LayoutPhoneDrawer>\n </LayoutRoot>\n </BarActionsSlotContext.Provider>\n );\n};\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), en la barra junto al nombre y en el cajón del teléfono\n * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo\n * ancho, la marca con su nombre en una sola imagen (como el de muchas empresas), o uno para cada modo ({light, dark}: el de\n * color en claro y el blanco en oscuro): va en lugar del logo y el nombre en la barra y arriba del menú del teléfono, a 24 px de alto. Con el menú\n * encogido se queda el logo chico (logo), si lo hay. brandName sigue siendo su nombre para el lector de pantalla\n * @param {string} [props.brandName] el nombre de la app; por omisión el appName de ToolkitProvider\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} [props.homePath] por omisión '/'\n * @param {object[]} [props.menuItems] lo que regresa composeMenu\n * @param {'side'|'top'} [props.menuPosition] por omisión 'side' (MENU_POSITION)\n * @param {{name: string, email?: string, subtitle?: string, avatarUrl?: string}|null} [props.user] quién está dentro\n * @param {string} [props.profilePath] la ruta de Mi perfil\n * @param {object[]} [props.accountMenuItems] renglones propios en el menú de la cuenta ({to | onClick, label | labelKey, Icon})\n * @param {() => void} [props.onSignOut] sin él no sale «Salir» (una app sin sesión)\n * @param {boolean} [props.isSigningOut]\n * @param {import('react').ReactNode} [props.headerActions] botones propios en la barra, antes de la cuenta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey de cada entrada del menú\n * (useMenuBadges): lo pendiente de cada pantalla, como numerito\n * @param {import('react').ReactNode} [props.accountSettings] ajustes propios del proyecto dentro del menú de la cuenta, debajo de\n * letra, idioma y claro u oscuro (con SettingSection, para que se vean igual)\n * @param {import('react').ReactNode} [props.sidebarFooter] lo de abajo del menú lateral (una tarjeta de ayuda…)\n * @param {boolean} [props.showFontSizeToggle] por omisión sí\n * @param {boolean} [props.showLanguageToggle] por omisión sí\n * @param {boolean} [props.showColorSchemeToggle] por omisión sí\n * @param {import('react').ReactNode} [props.ribbons] avisos propios dentro de la barra\n * @param {boolean} [props.isNavigating] la pantalla siguiente se está descargando (react-router: useIsNavigating)\n * @param {boolean} [props.defaultCollapsed] el menú lateral empieza encogido (la persona lo cambia y se recuerda)\n * @param {boolean} [props.minimizeTabBarOnScroll] en el teléfono con vidrio, la barra de pestañas se encoge al leer hacia\n * abajo y regresa al subir (como iOS 26); por omisión sí\n * @param {number|string} [props.contentMaxWidth] por omisión 1400\n * @param {(collapsed: boolean) => void} [props.onMenuCollapse] la persona encogió (true) o abrió (false) el menú lateral\n * @param {(isOpen: boolean) => void} [props.onMobileMenuChange] se abrió o se cerró el menú del teléfono (el cajón)\n * @param {(keys: string[]|null) => void} [props.onMobileTabsChange] la persona eligió sus pestañas del teléfono (las llaves\n * de menuItemKeyOf; null: regresó a las de omisión). Se recuerdan en el navegador; con esto el proyecto las puede guardar\n * en su servidor\n * @param {object} [props.texts] los textos que cambian, con la forma de `layout` en los textos del paquete (para todo el marco)\n * @param {import('react').ReactNode} props.children el contenido\n */\nconst AppLayoutContent = (props) => {\n // lo que el proyecto puso en su tema (components.CalcitaAppLayout.defaultProps) y encima lo que llega aquí\n const {\n logo, wordmark, brandName, brandCaption, homePath = '/', menuItems = [], menuPosition = MENU_POSITION.side, user = null, profilePath, accountMenuItems = [], onSignOut,\n isSigningOut = false, headerActions, accountSettings, menuBadges, sidebarFooter, showFontSizeToggle = true, showLanguageToggle = true, showColorSchemeToggle = true, ribbons,\n isNavigating = false, defaultCollapsed = false, minimizeTabBarOnScroll = true, contentMaxWidth = 1400, onMenuCollapse, onMobileMenuChange, onMobileTabsChange,\n texts, children,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {appName} = useToolkitSettings();\n // un ícono ya pintado (icon) se vuelve componente aquí, una vez: las piezas del menú pintan siempre Icon\n const shownMenuItems = useMemo(() => menuItemsWithIconsOf(menuItems), [menuItems]);\n const appMenu = useMemo(() => ({menuItems: shownMenuItems}), [shownMenuItems]);\n // los textos del marco llegan a todas sus piezas (la cuenta, el menú, las pestañas del teléfono), cada una lee los suyos\n return (\n <ComponentTexts namespace=\"layout\" texts={texts}>\n <PageTitleProvider>\n <AppMenuContext.Provider value={appMenu}>\n <AppLayoutFrame logo={logo} wordmark={wordmark} brandName={brandName ?? appName} brandCaption={brandCaption} homePath={homePath} menuItems={shownMenuItems} menuPosition={menuPosition}\n user={user} profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut} isSigningOut={isSigningOut}\n headerActions={headerActions} accountSettings={accountSettings} menuBadges={menuBadges} sidebarFooter={sidebarFooter} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} ribbons={ribbons}\n isNavigating={isNavigating} defaultCollapsed={defaultCollapsed} minimizeTabBarOnScroll={minimizeTabBarOnScroll}\n contentMaxWidth={contentMaxWidth} onMenuCollapse={onMenuCollapse} onMobileMenuChange={onMobileMenuChange}\n onMobileTabsChange={onMobileTabsChange}>\n {children}\n </AppLayoutFrame>\n </AppMenuContext.Provider>\n </PageTitleProvider>\n </ComponentTexts>\n );\n};\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nexport const AppLayout = withPieceSlots(AppLayoutContent, THEME_NAME);\n"],"names":["theme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppLayout.js","sources":["../../src/appShell/AppLayout.jsx"],"sourcesContent":["// El marco de las pantallas con sesión, al estilo de las plantillas de MUI (y del marco de walk-ot). Dos estilos de\n// barra con las mismas props, para que el proyecto (o el `init` del CLI) solo elija:\n//\n// menuPosition=\"side\" (por omisión) menuPosition=\"top\" (apps de pocos módulos)\n// ┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐\n// │ ☰ Logo Nombre · Título al deslizar · ⚙ (A)│ │ Logo · Inicio Pedidos Clientes ▾ · ⚙ (A) │\n// ├────────┬─────────────────────────────────┤ ├──────────────────────────────────────────┤\n// │ Menú │ contenido │ │ contenido │\n// │ por │ │ │ │\n// │ secc. │ │ │ │\n// └────────┴─────────────────────────────────┘ └──────────────────────────────────────────┘\n//\n// En el teléfono los dos son iguales: hamburguesa, título y ajustes en la barra; el menú sale en un cajón con el logo.\n//\n// Lo que trae, con su porqué:\n// - La barra va a todo lo ancho, con el logo y el nombre de la app a la izquierda, y el menú lateral debajo de ella\n// (como el DashboardLayout de MUI). La ☰ de la barra encoge el menú a solo íconos y lo recuerda (useDrawerCollapsed);\n// antes era un botón montado en la orilla del menú y se encimaba con él.\n// - El título de la página sube a la barra cuando su título grande sale de la vista (pageTitle.jsx).\n// - Escritorio y teléfono se eligen con CSS, no con useMediaQuery: el servidor (Next.js) ya pinta lo correcto.\n// - Dentro de la barra, para que se queden pegados al deslizar: los avisos del proyecto (ribbons), el de versión nueva\n// (NewBuildNotice; con vidrio flota aparte en su cápsula, ver NewBuildNotice.jsx) y la rayita de «cargando la pantalla»\n// (isNavigating). La barra publica su alto real, y el menú lateral empieza justo debajo de ella aunque crezca con un aviso.\n// - Con vidrio, en el teléfono la barra flota ABAJO en una cápsula, como la de Safari en el iPhone; y en escritorio, con\n// el menú encogido, la barra desaparece y se funde con el menú (☰ y logo arriba, ajustes y cuenta abajo).\n// - El primer Tab lleva a «Saltar al contenido» (WCAG 2.4.1) y el contenido es <main>. Respeta el área segura del\n// iPhone (la muesca) cuando la app corre instalada.\n// - Huecos para extender sin tocar el framework: headerActions, sidebarFooter, accountMenuItems, accountSettings, ribbons.\n// - Los ajustes de la app (letra, idioma, claro u oscuro y los del proyecto) van DENTRO del menú de la cuenta, no sueltos\n// en la barra (HeaderTools).\n// - El menú queda al alcance de las páginas (appMenuContext): PageHeading arma sus migas sola.\n// - Con el vidrio del tema (createToolkitTheme({glass: true})): el lienzo lleva manchas de color, y en escritorio la\n// barra y el menú lateral flotan a 8 px de la orilla y entre ellos, con esquinas redondas, para que el contenido pase\n// por detrás y se vea desenfocado; encogido, el menú es una cápsula. En el teléfono la barra sigue de orilla a orilla\n// (como en iOS). Sin vidrio, todo igual que siempre: el marco lo sabe por el tema (isGlassTheme), sin otra prop.\n// - Con vidrio y con el minimalista la barra no lleva franja, como las ventanas de macOS Tahoe (TOP_BAR.capsules, lo de\n// omisión): la ☰ con el nombre, el menú de arriba y la cuenta van cada uno en su cápsula, y el título queda suelto.\n// Con vidrio las cápsulas son de vidrio y el contenido pasa por debajo, desenfocado en la orilla (scrollEdgeStylesOf);\n// con el minimalista son bloques del tono de las tarjetas sobre la barra del color del fondo, también en el teléfono.\n// Con cápsulas el menú encogido ya no se funde con la barra: la cuenta y los ajustes se quedan arriba. Con\n// bar: TOP_BAR.band, la franja de lado a lado. Mismo alto y mismo lugar las dos: el menú lateral y las páginas no se\n// mueven.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAppLayout con defaultProps y\n// styleOverrides de sus partes: skipLink, brand, brandLogo, brandText, brandName, brandCaption, titleArea, phoneTitle,\n// desktopTitle, railBrand, railLogo, collapseButton, root, scrollEdge, bar, toolbar, menuButton, spacer, tools,\n// progress, body, drawer, main, phoneSlot, content, phoneDrawer.\n//\n// Lo renderiza la app: arrow (regla de la casa).\nimport {useMemo, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation, useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport AppBar from '@mui/material/AppBar';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Drawer from '@mui/material/Drawer';\nimport Fade from '@mui/material/Fade';\nimport IconButton from '@mui/material/IconButton';\nimport LinearProgress from '@mui/material/LinearProgress';\nimport Link from '@mui/material/Link';\nimport Toolbar from '@mui/material/Toolbar';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport MenuOpenIcon from '@mui/icons-material/MenuOpen';\n\nimport {AppMenuContext} from '@/appShell/appMenuContext.js';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {BarActionsSlotContext} from '@/appShell/barActionsSlot.js';\nimport HeaderTools from '@/appShell/HeaderTools.jsx';\nimport MobileTabBar from '@/appShell/MobileTabBar.jsx';\nimport Wordmark from '@/appShell/Wordmark.jsx';\nimport {PageTitleProvider, usePageTitle} from '@/appShell/pageTitle.jsx';\nimport {\n APP_BAR_HEIGHT_PIXELS, DESKTOP_MEDIA_QUERY, DRAWER_WIDTH_COLLAPSED, DRAWER_WIDTH_EXPANDED, GLASS_PANEL_GAP_PIXELS, MAIN_CONTENT_ID,\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, MOBILE_TAB_BAR_HEIGHT_PIXELS, PHONE_MEDIA_QUERY, drawerTransitionOf,\n} from '@/appShell/shellSizes.js';\nimport SideMenu from '@/appShell/SideMenu.jsx';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport TopMenu from '@/appShell/TopMenu.jsx';\nimport {useDrawerCollapsed} from '@/appShell/useDrawerCollapsed.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport NewBuildNotice, {NEW_BUILD_NOTICE_VARIANT} from '@/feedback/NewBuildNotice.jsx';\nimport {activeMenuItemOf, menuItemsWithIconsOf} from '@/navigation/appMenu.js';\nimport {useNavigationTrail} from '@/navigationTrail.js';\nimport {THEME_LOOK, TOP_BAR} from '@/theme/createToolkitTheme.js';\nimport {PANEL_RADIUS, ambientBackgroundOf, isGlassTheme, scrollEdgeStylesOf} from '@/theme/glassSurface.js';\nimport GlassLensFilter from '@/ui/layout/GlassLensFilter.jsx';\nimport {APP_BAR_HEIGHT, useAppBarHeightPublisher} from '@/ui/layout/appBarHeight.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAppLayout)\nconst THEME_NAME = 'CalcitaAppLayout';\nconst LayoutSkipLink = piecePart(THEME_NAME, 'SkipLink', Link);\nconst LayoutBrand = piecePart(THEME_NAME, 'Brand', ButtonBase);\nconst LayoutBrandLogo = piecePart(THEME_NAME, 'BrandLogo', Box);\nconst LayoutBrandText = piecePart(THEME_NAME, 'BrandText', Box);\nconst LayoutBrandName = piecePart(THEME_NAME, 'BrandName', Typography);\nconst LayoutBrandCaption = piecePart(THEME_NAME, 'BrandCaption', Typography);\nconst LayoutTitleArea = piecePart(THEME_NAME, 'TitleArea', Box);\nconst LayoutPhoneTitle = piecePart(THEME_NAME, 'PhoneTitle', Typography);\nconst LayoutDesktopTitle = piecePart(THEME_NAME, 'DesktopTitle', Typography);\nconst LayoutRailBrand = piecePart(THEME_NAME, 'RailBrand', ButtonBase);\nconst LayoutRailLogo = piecePart(THEME_NAME, 'RailLogo', Box);\nconst LayoutCollapseButton = piecePart(THEME_NAME, 'CollapseButton', IconButton);\nconst LayoutRoot = piecePart(THEME_NAME, 'Root', Box);\nconst LayoutScrollEdge = piecePart(THEME_NAME, 'ScrollEdge', Box);\nconst LayoutBar = piecePart(THEME_NAME, 'Bar', AppBar);\nconst LayoutToolbar = piecePart(THEME_NAME, 'Toolbar', Toolbar);\nconst LayoutMenuButton = piecePart(THEME_NAME, 'MenuButton', IconButton);\nconst LayoutSpacer = piecePart(THEME_NAME, 'Spacer', Box);\nconst LayoutTools = piecePart(THEME_NAME, 'Tools', Box);\nconst LayoutProgress = piecePart(THEME_NAME, 'Progress', LinearProgress);\nconst LayoutBody = piecePart(THEME_NAME, 'Body', Box);\nconst LayoutDrawer = piecePart(THEME_NAME, 'Drawer', Drawer);\nconst LayoutMain = piecePart(THEME_NAME, 'Main', Box);\nconst LayoutPhoneSlot = piecePart(THEME_NAME, 'PhoneSlot', Box);\nconst LayoutContent = piecePart(THEME_NAME, 'Content', Box);\nconst LayoutPhoneDrawer = piecePart(THEME_NAME, 'PhoneDrawer', Drawer);\n\n/** Los dos estilos de barra. */\nexport const MENU_POSITION = Object.freeze({side: 'side', top: 'top'});\n\nconst NAVIGATING_BAR_PIXELS = 2;\n\n/**\n * Un grupo de la barra: con la barra en cápsulas, su cápsula (BarCapsule: de vidrio, o con el minimalista un bloque del\n * tono de las tarjetas); con la franja, sus piezas tal cual (sin caja de más, para que la franja se quede idéntica).\n */\nconst BarGroup = ({isCapsule, sx, children}) => {\n if (!isCapsule) {\n return children;\n }\n // presentation: lo de adentro (la ☰, el nombre, la cuenta) ya se anuncia solo; el grupo es solo la forma\n return <BarCapsule role=\"presentation\" sx={sx}>{children}</BarCapsule>;\n};\n\n/** «Saltar al contenido»: escondido arriba hasta que llega el foco del teclado. */\nconst SkipToContentLink = () => {\n const {t} = useToolkitTranslation('layout');\n return (\n <LayoutSkipLink href={`#${MAIN_CONTENT_ID}`} ownSx={{\n position: 'absolute', left: 8, top: -48, zIndex: (theme) => theme.zIndex.tooltip + 1, bgcolor: 'background.paper',\n px: 1.5, py: 0.75, borderRadius: 1, boxShadow: 3, '&:focus': {top: 8},\n }}>\n {t('skipToContent')}\n </LayoutSkipLink>\n );\n};\n\n/** El logo y el nombre de la app en la barra (con su renglón chico, si lo hay), o el logo ancho; lleva al inicio. */\nconst BarBrand = ({logo, wordmark, brandName, brandCaption, homePath, sx}) => {\n const {Link: RouterLink} = useToolkitNavigation();\n return (\n <LayoutBrand component={RouterLink} to={homePath} aria-label={wordmark ? brandName : undefined}\n ownSx={[{alignItems: 'center', gap: 1.25, borderRadius: 1, flexShrink: 0, minWidth: 0, pr: 1, textAlign: 'left'}, sx]}>\n {wordmark ? <Wordmark>{wordmark}</Wordmark> : (\n <>\n {logo && <LayoutBrandLogo ownSx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</LayoutBrandLogo>}\n <LayoutBrandText ownSx={{minWidth: 0}}>\n <LayoutBrandName variant=\"subtitle2\" component=\"span\" noWrap ownSx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</LayoutBrandName>\n {/* el renglón chico hereda el color de la barra (sobre el naranja, el gris del texto secundario no se leía) */}\n {brandCaption && <LayoutBrandCaption variant=\"caption\" component=\"span\" noWrap ownSx={{display: 'block', lineHeight: 1.3, opacity: 0.8}}>{brandCaption}</LayoutBrandCaption>}\n </LayoutBrandText>\n </>\n )}\n </LayoutBrand>\n );\n};\n\n/**\n * El título en la barra: en escritorio, después del nombre de la app y solo cuando el título grande ya no se ve; en el\n * teléfono siempre (o el nombre de la app).\n */\nconst BarTitle = ({brandName}) => {\n const {title, isTitleVisible} = usePageTitle();\n return (\n <LayoutTitleArea ownSx={{flexGrow: 1, minWidth: 0}}>\n <LayoutPhoneTitle variant=\"subtitle1\" component=\"p\" noWrap ownSx={{display: 'block', [DESKTOP_MEDIA_QUERY]: {display: 'none'}, fontWeight: 600}}>{title ?? brandName}</LayoutPhoneTitle>\n <Fade in={Boolean(title) && !isTitleVisible}>\n <LayoutDesktopTitle variant=\"subtitle1\" component=\"p\" noWrap\n ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'}, fontWeight: 600, ml: 1, pl: 2, borderLeft: '1px solid color-mix(in srgb, currentColor 30%, transparent)'}}>\n {title}\n </LayoutDesktopTitle>\n </Fade>\n </LayoutTitleArea>\n );\n};\n\n/** El logo solo, arriba del menú encogido que hace de barra; lleva al inicio. */\nconst RailBrand = ({logo, brandName, homePath}) => {\n const {Link: RouterLink} = useToolkitNavigation();\n return (\n <Tooltip title={brandName} placement=\"right\">\n <LayoutRailBrand component={RouterLink} to={homePath} aria-label={brandName}\n ownSx={{width: 44, height: 44, borderRadius: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>\n <LayoutRailLogo ownSx={{width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</LayoutRailLogo>\n </LayoutRailBrand>\n </Tooltip>\n );\n};\n\n/** La ☰ de escritorio: encoge el menú lateral a solo íconos o lo vuelve a abrir. */\nconst CollapseMenuButton = ({collapsed, onToggle}) => {\n const {t} = useToolkitTranslation('layout');\n const label = collapsed ? t('drawer.expand') : t('drawer.collapse');\n return (\n <Tooltip title={label}>\n <LayoutCollapseButton aria-label={label} aria-expanded={!collapsed} onClick={onToggle} ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'inline-flex'}, color: 'inherit'}}>\n {collapsed ? <MenuIcon /> : <MenuOpenIcon />}\n </LayoutCollapseButton>\n </Tooltip>\n );\n};\n\nconst AppLayoutFrame = ({\n logo, wordmark, brandName, brandCaption, homePath, menuItems, menuPosition, user, profilePath, accountMenuItems, onSignOut, isSigningOut, headerActions,\n accountSettings, menuBadges, sidebarFooter, showFontSizeToggle, showLanguageToggle, showColorSchemeToggle, ribbons, isNavigating, defaultCollapsed, minimizeTabBarOnScroll, contentMaxWidth,\n menuLabelOverflow, onMenuCollapse, onMobileMenuChange, onMobileTabsChange, children,\n}) => {\n const {t} = useToolkitTranslation('layout');\n const {useLocation} = useToolkitNavigation();\n const {pathname} = useLocation();\n const {collapsed, toggle} = useDrawerCollapsed(defaultCollapsed);\n const [isMobileMenuOpen, setIsMobileMenuOpenState] = useState(false);\n // cada cambio del menú también sale afuera (para medir, o para que el proyecto guarde la preferencia en su servidor)\n const toggleCollapsed = () => {\n toggle();\n onMenuCollapse?.(!collapsed);\n };\n const setIsMobileMenuOpen = (isOpen) => {\n setIsMobileMenuOpenState(isOpen);\n onMobileMenuChange?.(isOpen);\n };\n const appBarRef = useAppBarHeightPublisher();\n useNavigationTrail();\n\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n // la barra de pestañas del teléfono (estilo Apple): con vidrio, y con el minimalista en cápsulas (usesMobileTabBar);\n // sin vidrio, o con el minimalista en franja, se queda la barra de MUI arriba con su cajón\n const usesTabBar = usesMobileTabBar(theme);\n\n const isSideMenu = menuPosition !== MENU_POSITION.top;\n // con vidrio y el menú encogido, en escritorio la barra de arriba desaparece y se funde con el menú: la ☰ y el logo van\n // arriba del menú y los ajustes y la cuenta abajo (lo pidió el autor). Si el proyecto trae avisos en la barra (ribbons),\n // la barra se queda: los avisos no tendrían dónde ir\n // la barra sin franja, en cápsulas (lo de omisión con vidrio y con el minimalista; TOP_BAR.band pide la franja). El\n // estilo de Stackcraft sin vidrio se queda con su barra de siempre\n const isMinimalLook = theme.toolkit?.look === THEME_LOOK.minimal;\n const usesBarCapsules = (isGlass || isMinimalLook) && theme.toolkit?.bar === TOP_BAR.capsules;\n // con vidrio, o con cápsulas, la barra y el menú lateral van a GLASS_PANEL_GAP_PIXELS de la orilla: la columna del menú\n // mide eso de más. OJO: con el minimalista en cápsulas era 0 y las cápsulas quedaban pegadas arriba (a 4 px) y el menú\n // pegado a la izquierda (lo reportó el autor; medido en Chrome)\n const panelGap = isGlass || usesBarCapsules ? GLASS_PANEL_GAP_PIXELS : 0;\n // con cápsulas no se funde: no hay franja que esconder, y la cuenta y los ajustes se quedan arriba en su cápsula en\n // lugar de bajar al pie del menú encogido (lo pidió el autor)\n const isBarMerged = isGlass && isSideMenu && collapsed && !ribbons && !usesBarCapsules;\n const drawerWidth = collapsed ? DRAWER_WIDTH_COLLAPSED : DRAWER_WIDTH_EXPANDED;\n const activeItem = activeMenuItemOf(menuItems, pathname);\n const sideMenuProps = {logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, menuBadges, footer: sidebarFooter, labelOverflow: menuLabelOverflow};\n // el lugar de la barra donde caen las acciones de cada pantalla (BarActions); con la barra fundida con el menú no hay\n // barra en escritorio y se quedan en la pantalla\n const [barActionsSlot, setBarActionsSlot] = useState(null);\n // en el teléfono con cápsulas no hay barra de arriba (la de pestañas va abajo): las acciones flotan arriba a la derecha\n const usesPhoneFloatingActions = usesTabBar && usesBarCapsules;\n const [phoneBarActionsSlot, setPhoneBarActionsSlot] = useState(null);\n const barActions = useMemo(() => ({\n slot: isBarMerged ? null : barActionsSlot,\n phoneSlot: usesPhoneFloatingActions ? phoneBarActionsSlot : null,\n usesCapsules: usesBarCapsules,\n }), [isBarMerged, barActionsSlot, usesPhoneFloatingActions, phoneBarActionsSlot, usesBarCapsules]);\n\n return (\n <BarActionsSlotContext.Provider value={barActions}>\n <LayoutRoot ownSx={[{display: 'flex', flexDirection: 'column', minHeight: '100vh', bgcolor: 'background.default'}, isGlass && ambientBackgroundOf]}>\n <SkipToContentLink />\n <GlassLensFilter />\n\n {/* en cápsulas, lo que pasa por debajo de la barra se desenfoca en la orilla: debajo del menú lateral, que sigue nítido */}\n {isGlass && usesBarCapsules && (\n <LayoutScrollEdge aria-hidden=\"true\"\n ownSx={(theme) => ({\n ...scrollEdgeStylesOf(theme), display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'},\n position: 'fixed', top: 0, left: 0, right: 0, height: `calc(${APP_BAR_HEIGHT} + ${panelGap}px)`,\n zIndex: theme.zIndex.drawer - 1, pointerEvents: 'none',\n })} />\n )}\n {/* en cápsulas con vidrio la barra va transparente (color=\"transparent\": el tema no le pone vidrio) y cada grupo\n lleva el suyo; con el minimalista se queda del color del fondo, que tapa lo que pasa por debajo */}\n <LayoutBar ref={appBarRef} position=\"sticky\" color={isGlass && usesBarCapsules ? 'transparent' : undefined}\n ownSx={(theme) => ({\n pt: 'env(safe-area-inset-top)',\n // encima del menú lateral, como en el DashboardLayout de MUI\n zIndex: theme.zIndex.drawer + 1,\n // con vidrio flota: en escritorio arriba, separada de las orillas; en el teléfono ABAJO, en una cápsula,\n // como la barra de Safari en el iPhone (en el teléfono se veía cuadrada y pegada arriba; lo reportaron).\n // Abajo se suma la zona del gesto de inicio del iPhone (safe-area) a la separación\n ...(isGlass && {\n // en cápsulas no se recorta: la sombra de cada cápsula se saldría cortada\n overflow: usesBarCapsules ? 'visible' : 'hidden',\n [DESKTOP_MEDIA_QUERY]: {\n top: panelGap, mt: `${panelGap}px`, mx: `${panelGap}px`, width: 'auto',\n borderRadius: PANEL_RADIUS,\n // sin la raya de abajo de la barra fija: no hay franja que separar\n ...(usesBarCapsules && {borderBottom: 0, boxShadow: 'none'}),\n ...(isBarMerged && {display: 'none'}),\n },\n }),\n // el minimalista en cápsulas no flota (su barra es del color del fondo y tapa lo que pasa por debajo):\n // la separación de las orillas va por dentro, para que las cápsulas queden donde las del vidrio\n ...(!isGlass && usesBarCapsules && {\n [DESKTOP_MEDIA_QUERY]: {pt: `calc(env(safe-area-inset-top) + ${panelGap}px)`, px: `${panelGap}px`},\n }),\n // en el teléfono la barra de pestañas (MobileTabBar) la reemplaza: con ella van el menú y la cuenta\n ...(usesTabBar && {[PHONE_MEDIA_QUERY]: {display: 'none'}}),\n })}>\n {/* a la izquierda, la ☰ queda alineada con los íconos del menú lateral (los dos a 32 px de su orilla) */}\n <LayoutToolbar disableGutters ownSx={{minHeight: {xs: APP_BAR_HEIGHT_PIXELS, sm: APP_BAR_HEIGHT_PIXELS}, gap: 1, pl: {xs: 1.5, md: isSideMenu ? 1.5 : 3}, pr: {xs: 1.5, md: 3}}}>\n {/* la ☰ del teléfono va en la primera cápsula (en escritorio la de encoger el menú y el nombre). OJO: dentro de\n una cápsula, sin el margen negativo de «orilla de inicio» de MUI (-12 px): el botón quedaba pegado a la\n curva. Así su círculo queda concéntrico con la cápsula */}\n <BarGroup isCapsule={usesBarCapsules}>\n <LayoutMenuButton edge=\"start\" aria-label={t('drawer.open')} aria-expanded={isMobileMenuOpen} onClick={() => setIsMobileMenuOpen(true)}\n ownSx={{display: 'inline-flex', [DESKTOP_MEDIA_QUERY]: {display: 'none'}, color: 'inherit', ...((isGlass || usesBarCapsules) && {ml: 0})}}>\n <MenuIcon />\n </LayoutMenuButton>\n {isSideMenu && <CollapseMenuButton collapsed={collapsed} onToggle={toggleCollapsed} />}\n <BarBrand logo={logo} wordmark={wordmark} brandName={brandName} brandCaption={brandCaption} homePath={homePath}\n sx={isSideMenu ? {display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}} : {display: 'flex'}} />\n </BarGroup>\n {isSideMenu ? (\n <BarTitle brandName={brandName} />\n ) : (\n <>\n {/* el menú de arriba no sale en el teléfono: su cápsula tampoco (vacía se vería) */}\n <BarGroup isCapsule={usesBarCapsules} sx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}}}>\n <TopMenu menuItems={menuItems} activeItem={activeItem} />\n </BarGroup>\n <LayoutSpacer ownSx={{flexGrow: 1}} />\n </>\n )}\n {/* las acciones de la pantalla abierta (BarActions, y las de PageHeading con cápsulas): cada grupo cae aquí,\n antes de la cuenta. Solo en escritorio: en el teléfono flotan arriba (phoneBarActionsSlot, en el contenido) */}\n <LayoutTools ref={setBarActionsSlot} ownSx={{display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'flex'}, alignItems: 'center', gap: 1, minWidth: 0}} />\n <BarGroup isCapsule={usesBarCapsules}>\n <HeaderTools headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle} showLanguageToggle={showLanguageToggle}\n showColorSchemeToggle={showColorSchemeToggle} user={user} profilePath={profilePath} accountMenuItems={accountMenuItems}\n onSignOut={onSignOut} isSigningOut={isSigningOut} />\n </BarGroup>\n </LayoutToolbar>\n {ribbons}\n {/* con la barra de pestañas el aviso de versión nueva flota aparte, en su cápsula (en el teléfono esta barra no\n se ve); sin ella, franja dentro de la barra */}\n {!usesTabBar && <NewBuildNotice />}\n {isNavigating && (\n <LayoutProgress aria-label={t('navigating')} color=\"inherit\"\n ownSx={{position: 'absolute', bottom: 0, left: 0, right: 0, height: NAVIGATING_BAR_PIXELS}} />\n )}\n </LayoutBar>\n {usesTabBar && <NewBuildNotice variant={NEW_BUILD_NOTICE_VARIANT.floating} />}\n {usesTabBar && (\n <MobileTabBar menuItems={menuItems} activeItem={activeItem} menuBadges={menuBadges} moreFooter={sidebarFooter} minimizeOnScroll={minimizeTabBarOnScroll}\n labelOverflow={menuLabelOverflow} onPinnedTabsChange={onMobileTabsChange}\n account={(\n <HeaderTools headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} user={user}\n profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut} isSigningOut={isSigningOut} />\n )} />\n )}\n\n <LayoutBody ownSx={{display: 'flex', flexGrow: 1, minWidth: 0}}>\n {isSideMenu && (\n <LayoutDrawer variant=\"permanent\"\n ownSx={(theme) => ({\n display: 'none', [DESKTOP_MEDIA_QUERY]: {display: 'block'}, width: drawerWidth + panelGap, flexShrink: 0, whiteSpace: 'nowrap',\n transition: drawerTransitionOf(theme),\n '& .MuiDrawer-paper': {\n width: drawerWidth, left: panelGap, boxSizing: 'border-box', overflowX: 'hidden', transition: drawerTransitionOf(theme, ['width']),\n // justo debajo de la barra (su alto real, con avisos o sin ellos) y hasta abajo; si la barra se\n // fundió con el menú, de arriba a abajo\n ...(isBarMerged\n ? {top: panelGap, height: `calc(100% - ${panelGap * 2}px)`}\n : {top: `calc(${APP_BAR_HEIGHT} + ${panelGap}px)`, height: `calc(100% - ${APP_BAR_HEIGHT} - ${panelGap * 2}px)`}),\n // el panel flotante: separado de las orillas y sin la línea de la derecha (lo separa su brillo).\n // Encogido es una cápsula (radio de la mitad de su ancho), como las barras de pestañas de vidrio\n ...(isGlass && {\n border: 0,\n borderRadius: PANEL_RADIUS,\n }),\n },\n })}>\n <SideMenu {...sideMenuProps} showBrand={false} collapsed={collapsed}\n railTop={isBarMerged ? (\n <>\n <CollapseMenuButton collapsed={collapsed} onToggle={toggleCollapsed} />\n {logo && <RailBrand logo={logo} brandName={brandName} homePath={homePath} />}\n </>\n ) : undefined}\n railBottom={isBarMerged ? (\n <HeaderTools vertical headerActions={headerActions} accountSettings={accountSettings} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} user={user}\n profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut}\n isSigningOut={isSigningOut} />\n ) : undefined} />\n </LayoutDrawer>\n )}\n\n <LayoutMain component=\"main\" id={MAIN_CONTENT_ID} tabIndex={-1}\n ownSx={(theme) => ({\n flexGrow: 1, minWidth: 0, outline: 'none', px: {xs: 2, md: 3, lg: 4}, pt: 3, pb: 6,\n // con la barra de pestañas abajo (teléfono): arriba, lugar para la hora y la cámara del iPhone; abajo,\n // lugar para que la barra (que flota a GLASS_PANEL_GAP_PIXELS) no tape el final de la página\n ...(usesTabBar && {\n [PHONE_MEDIA_QUERY]: {\n pt: `calc(${theme.spacing(3)} + env(safe-area-inset-top))`,\n pb: `calc(${MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + GLASS_PANEL_GAP_PIXELS * 2}px + env(safe-area-inset-bottom) + ${theme.spacing(3)} + var(${FLOATING_BOTTOM_SPACE_CSS_VARIABLE}, 0px))`,\n },\n }),\n })}>\n {/* teléfono con cápsulas: el renglón de las acciones de la pantalla, arriba a la derecha. Va pegado (sticky)\n y no fijo: así ocupa su lugar arriba de la página (no tapa las migas ni el título) y al bajar se queda\n flotando. Vacío no se ve. Solo las cápsulas reciben clics: el hueco de la izquierda deja pasar el dedo\n a lo de abajo */}\n {usesPhoneFloatingActions && (\n <LayoutPhoneSlot ref={setPhoneBarActionsSlot} data-phone-bar-actions=\"\"\n ownSx={(theme) => ({\n display: 'none',\n [PHONE_MEDIA_QUERY]: {\n display: 'flex', '&:empty': {display: 'none'},\n position: 'sticky', top: `calc(env(safe-area-inset-top) + ${GLASS_PANEL_GAP_PIXELS}px)`, zIndex: theme.zIndex.appBar,\n justifyContent: 'flex-end', alignItems: 'center', gap: 1, mb: 2,\n pointerEvents: 'none', '& > *': {pointerEvents: 'auto'},\n },\n })} />\n )}\n <LayoutContent ownSx={{maxWidth: contentMaxWidth, mx: 'auto'}}>{children}</LayoutContent>\n </LayoutMain>\n </LayoutBody>\n\n {/* el cajón del teléfono (los dos estilos): con el logo arriba, se cierra al elegir una pantalla */}\n <LayoutPhoneDrawer variant=\"temporary\" open={isMobileMenuOpen} onClose={() => setIsMobileMenuOpen(false)} ModalProps={{keepMounted: true}}\n ownSx={{[DESKTOP_MEDIA_QUERY]: {display: 'none'}}}\n slotProps={{paper: {sx: {width: DRAWER_WIDTH_EXPANDED, boxSizing: 'border-box', pt: 'env(safe-area-inset-top)', pb: 'env(safe-area-inset-bottom)'}}}}>\n <SideMenu {...sideMenuProps} collapsed={false} onNavigate={() => setIsMobileMenuOpen(false)} />\n </LayoutPhoneDrawer>\n </LayoutRoot>\n </BarActionsSlotContext.Provider>\n );\n};\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), en la barra junto al nombre y en el cajón del teléfono\n * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo\n * ancho, la marca con su nombre en una sola imagen (como el de muchas empresas), o uno para cada modo ({light, dark}: el de\n * color en claro y el blanco en oscuro): va en lugar del logo y el nombre en la barra y arriba del menú del teléfono, a 24 px de alto. Con el menú\n * encogido se queda el logo chico (logo), si lo hay. brandName sigue siendo su nombre para el lector de pantalla\n * @param {string} [props.brandName] el nombre de la app; por omisión el appName de ToolkitProvider\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} [props.homePath] por omisión '/'\n * @param {object[]} [props.menuItems] lo que regresa composeMenu\n * @param {'side'|'top'} [props.menuPosition] por omisión 'side' (MENU_POSITION)\n * @param {{name: string, email?: string, subtitle?: string, avatarUrl?: string}|null} [props.user] quién está dentro\n * @param {string} [props.profilePath] la ruta de Mi perfil\n * @param {object[]} [props.accountMenuItems] renglones propios en el menú de la cuenta ({to | onClick, label | labelKey, Icon})\n * @param {() => void} [props.onSignOut] sin él no sale «Salir» (una app sin sesión)\n * @param {boolean} [props.isSigningOut]\n * @param {import('react').ReactNode} [props.headerActions] botones propios en la barra, antes de la cuenta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey de cada entrada del menú\n * (useMenuBadges): lo pendiente de cada pantalla, como numerito\n * @param {import('react').ReactNode} [props.accountSettings] ajustes propios del proyecto dentro del menú de la cuenta, debajo de\n * letra, idioma y claro u oscuro (con SettingSection, para que se vean igual)\n * @param {import('react').ReactNode} [props.sidebarFooter] lo de abajo del menú lateral (una tarjeta de ayuda…)\n * @param {boolean} [props.showFontSizeToggle] por omisión sí\n * @param {boolean} [props.showLanguageToggle] por omisión sí\n * @param {boolean} [props.showColorSchemeToggle] por omisión sí\n * @param {import('react').ReactNode} [props.ribbons] avisos propios dentro de la barra\n * @param {boolean} [props.isNavigating] la pantalla siguiente se está descargando (react-router: useIsNavigating)\n * @param {boolean} [props.defaultCollapsed] el menú lateral empieza encogido (la persona lo cambia y se recuerda)\n * @param {boolean} [props.minimizeTabBarOnScroll] en el teléfono con vidrio, la barra de pestañas se encoge al leer hacia\n * abajo y regresa al subir (como iOS 26); por omisión sí\n * @param {number|string} [props.contentMaxWidth] por omisión 1400\n * @param {string} [props.menuLabelOverflow] un nombre del menú lateral que no cabe en su renglón: MENU_LABEL_OVERFLOW.ellipsis\n * (por omisión, se corta con «…») o MENU_LABEL_OVERFLOW.wrap (baja a otro renglón y se lee completo). También desde el\n * tema: components.CalcitaAppLayout.defaultProps\n * @param {(collapsed: boolean) => void} [props.onMenuCollapse] la persona encogió (true) o abrió (false) el menú lateral\n * @param {(isOpen: boolean) => void} [props.onMobileMenuChange] se abrió o se cerró el menú del teléfono (el cajón)\n * @param {(keys: string[]|null) => void} [props.onMobileTabsChange] la persona eligió sus pestañas del teléfono (las llaves\n * de menuItemKeyOf; null: regresó a las de omisión). Se recuerdan en el navegador; con esto el proyecto las puede guardar\n * en su servidor\n * @param {object} [props.texts] los textos que cambian, con la forma de `layout` en los textos del paquete (para todo el marco)\n * @param {import('react').ReactNode} props.children el contenido\n */\nconst AppLayoutContent = (props) => {\n // lo que el proyecto puso en su tema (components.CalcitaAppLayout.defaultProps) y encima lo que llega aquí\n const {\n logo, wordmark, brandName, brandCaption, homePath = '/', menuItems = [], menuPosition = MENU_POSITION.side, user = null, profilePath, accountMenuItems = [], onSignOut,\n isSigningOut = false, headerActions, accountSettings, menuBadges, sidebarFooter, showFontSizeToggle = true, showLanguageToggle = true, showColorSchemeToggle = true, ribbons,\n isNavigating = false, defaultCollapsed = false, minimizeTabBarOnScroll = true, contentMaxWidth = 1400, menuLabelOverflow, onMenuCollapse, onMobileMenuChange,\n onMobileTabsChange, texts, children,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {appName} = useToolkitSettings();\n // un ícono ya pintado (icon) se vuelve componente aquí, una vez: las piezas del menú pintan siempre Icon\n const shownMenuItems = useMemo(() => menuItemsWithIconsOf(menuItems), [menuItems]);\n const appMenu = useMemo(() => ({menuItems: shownMenuItems}), [shownMenuItems]);\n // los textos del marco llegan a todas sus piezas (la cuenta, el menú, las pestañas del teléfono), cada una lee los suyos\n return (\n <ComponentTexts namespace=\"layout\" texts={texts}>\n <PageTitleProvider>\n <AppMenuContext.Provider value={appMenu}>\n <AppLayoutFrame logo={logo} wordmark={wordmark} brandName={brandName ?? appName} brandCaption={brandCaption} homePath={homePath} menuItems={shownMenuItems} menuPosition={menuPosition}\n user={user} profilePath={profilePath} accountMenuItems={accountMenuItems} onSignOut={onSignOut} isSigningOut={isSigningOut}\n headerActions={headerActions} accountSettings={accountSettings} menuBadges={menuBadges} sidebarFooter={sidebarFooter} showFontSizeToggle={showFontSizeToggle}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle} ribbons={ribbons}\n isNavigating={isNavigating} defaultCollapsed={defaultCollapsed} minimizeTabBarOnScroll={minimizeTabBarOnScroll}\n contentMaxWidth={contentMaxWidth} menuLabelOverflow={menuLabelOverflow} onMenuCollapse={onMenuCollapse} onMobileMenuChange={onMobileMenuChange}\n onMobileTabsChange={onMobileTabsChange}>\n {children}\n </AppLayoutFrame>\n </AppMenuContext.Provider>\n </PageTitleProvider>\n </ComponentTexts>\n );\n};\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nexport const AppLayout = withPieceSlots(AppLayoutContent, THEME_NAME);\n"],"names":["theme"],"mappings":"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"}
|
|
@@ -156,7 +156,17 @@ function useFittingTabCount(capsule, minTabPixels) {
|
|
|
156
156
|
return tabCount;
|
|
157
157
|
}
|
|
158
158
|
function MobileTabBarContent(props) {
|
|
159
|
-
const {
|
|
159
|
+
const {
|
|
160
|
+
menuItems,
|
|
161
|
+
activeItem,
|
|
162
|
+
menuBadges,
|
|
163
|
+
account,
|
|
164
|
+
moreFooter,
|
|
165
|
+
minimizeOnScroll = true,
|
|
166
|
+
labelOverflow,
|
|
167
|
+
onPinnedTabsChange,
|
|
168
|
+
...unknownProps
|
|
169
|
+
} = usePieceProps(props, THEME_NAME);
|
|
160
170
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
161
171
|
const { t } = useToolkitTranslation("layout");
|
|
162
172
|
const { Link: RouterLink } = useToolkitNavigation();
|
|
@@ -347,6 +357,7 @@ function MobileTabBarContent(props) {
|
|
|
347
357
|
collapsed: false,
|
|
348
358
|
showBrand: false,
|
|
349
359
|
footer: moreFooter,
|
|
360
|
+
labelOverflow,
|
|
350
361
|
onNavigate: closeMore
|
|
351
362
|
}
|
|
352
363
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileTabBar.js","sources":["../../src/appShell/MobileTabBar.jsx"],"sourcesContent":["// La barra de pestañas del teléfono con vidrio, como la de iOS 26: una cápsula de vidrio flotando abajo con las secciones\n// principales (mobileTabsOf: las que caben a lo ancho, y «Más» con el resto), y aparte, en su círculo de vidrio, la cuenta\n// (con todos los ajustes adentro). La reemplaza a la barra de abajo con ☰ y al cajón del menú (lo pidió el autor: «estilo\n// Apple pero con sus submenús de acciones»). También va con el minimalista cuando la barra va en cápsulas (usesMobileTabBar), sólida y\n// del gris de lo que flota (minimalFloatingStylesOf).\n//\n// - Una pestaña con submenú no navega: abre el menú de acciones de Apple arriba de ella (SubmenuActionsMenu).\n// - «Más» abre una hoja desde abajo con el resto del menú, por secciones (SideMenu, el mismo del escritorio). Arriba trae\n// «Editar la barra»: cada quien elige qué pantallas van abajo (TabBarEditor; se recuerda con usePinnedMobileTabs),\n// como pide la guía de Apple. Sin elegir, van las que marca el proyecto (mobileTab en menu.js).\n// - Cada pestaña lleva su badge como los de Apple (HIG «Tab bars»): ROJO con el número en blanco y solo para conteos (lo\n// pendiente, del servidor); un badge de texto («Nuevo») no va en la pestaña, se queda en el menú: Apple los reserva para\n// lo que pide atención.\n// - Cuántas pestañas van se mide (useFittingTabCount): las que caben con al menos MIN_TAB_PIXELS cada una; las demás van\n// a «Más». En una ventana angosta salen menos y en una ancha más, y los nombres no se cortan («Compone…»).\n// - Acostado (pantalla bajita), la barra va compacta: ícono y nombre lado a lado, para dejarle el alto al contenido.\n// - Al leer hacia abajo se encoge (como iOS 26): queda una cápsula chica con la pestaña donde está la persona, y la cuenta\n// más chica; al subir un poco, o al tocar la cápsula, regresa completa (cuándo: tabBarScroll.js). Se mueve solo con\n// opacidad y escala, lo que el navegador anima sin volver a acomodar la página: en un teléfono barato no se traba.\n// Solo se ve en el teléfono (PHONE_MEDIA_QUERY); en escritorio no ocupa nada.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaMobileTabBar con defaultProps y\n// styleOverrides de sus partes: tab, tabIconPlaceholder, tabLabel, root, bar, minimized, account, sheetHeader.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useLayoutEffect, useState} from 'react';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Drawer from '@mui/material/Drawer';\nimport Typography from '@mui/material/Typography';\nimport useMediaQuery from '@mui/material/useMediaQuery';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport {\n GLASS_PANEL_GAP_PIXELS, MOBILE_TAB_BAR_HEIGHT_PIXELS, PHONE_LANDSCAPE_MEDIA_QUERY, PHONE_MEDIA_QUERY,\n} from '@/appShell/shellSizes.js';\nimport SideMenu from '@/appShell/SideMenu.jsx';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport TabBarEditor from '@/appShell/TabBarEditor.jsx';\nimport {usePinnedMobileTabs} from '@/appShell/usePinnedMobileTabs.js';\nimport {useTabBarMinimized} from '@/appShell/useTabBarMinimized.js';\nimport {menuBadgeOf, menuItemKeyOf, mobileTabsOf} from '@/navigation/appMenu.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {accentTextStylesOf, glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaMobileTabBar)\nconst THEME_NAME = 'CalcitaMobileTabBar';\nconst TabBarTab = piecePart(THEME_NAME, 'Tab', ButtonBase);\nconst TabBarTabIconPlaceholder = piecePart(THEME_NAME, 'TabIconPlaceholder', Box);\nconst TabBarTabLabel = piecePart(THEME_NAME, 'TabLabel', Typography);\nconst TabBarRoot = piecePart(THEME_NAME, 'Root', Box);\nconst TabBarBar = piecePart(THEME_NAME, 'Bar', Box);\nconst TabBarMinimized = piecePart(THEME_NAME, 'Minimized', ButtonBase);\nconst TabBarAccount = piecePart(THEME_NAME, 'Account', Box);\nconst TabBarSheetHeader = piecePart(THEME_NAME, 'SheetHeader', Box);\n\n// el nombre de la pestaña parado y acostado (px), lo ancho máximo de la cápsula y lo alto de la hoja de «Más»\nconst TAB_LABEL_PIXELS = Object.freeze({portrait: 10, landscape: 12});\nconst TAB_ICON_SIZE = '1.5rem';\nconst TAB_BAR_MAX_WIDTH_PIXELS = 560;\n// lo menos que mide una pestaña (px) para que su nombre se lea entero: parada, un nombre de unas 11 letras («Formularios»\n// pide 61 px, medido en Chrome); acostada, ícono y nombre lado a lado. Con menos, la pestaña se va a «Más»\nconst MIN_TAB_PIXELS = Object.freeze({portrait: 60, landscape: 104});\n// antes de medir (o si la barra no se ve): las de siempre, 4 y «Más»\nconst DEFAULT_TAB_COUNT = 5;\nconst MORE_SHEET_MAX_WIDTH_PIXELS = 640;\nconst MORE_SHEET_MAX_HEIGHT = '75vh';\nconst BADGE_MAX = 99;\n// la barra flota sobre el contenido: vidrio más opaco que el de los controles, una línea fina en la orilla y una sombra\n// debajo. Con el vidrio de siempre, en claro se perdía sobre las tarjetas blancas y no parecía flotar (lo reportaron)\nconst FLOATING_GLASS_OPACITY = 0.8;\nconst FLOATING_EDGE = Object.freeze({\n light: {outline: 'rgba(15, 23, 42, 0.12)', shadow: '0 10px 30px rgba(15, 23, 42, 0.20), 0 2px 6px rgba(15, 23, 42, 0.10)'},\n dark: {outline: 'rgba(255, 255, 255, 0.16)', shadow: '0 10px 30px rgba(0, 0, 0, 0.65), 0 2px 6px rgba(0, 0, 0, 0.4)'},\n});\n// la pestaña elegida: un tinte del acento (en claro una gota blanca sobre la cápsula blanca no se distinguía)\nconst ACTIVE_TINT_PERCENT = Object.freeze({light: 14, dark: 24});\n// encogida: lo alto de la cápsula chica (px) y a qué tamaño se va lo que desaparece\nconst MINIMIZED_PIXELS = Object.freeze({portrait: 48, landscape: 40});\nconst HIDDEN_SCALE = 0.6;\n\n/**\n * Lo que aparece o desaparece al encoger la barra: solo opacidad y escala. visibility espera a que termine la animación\n * para esconderlo, y así lo que ya no se ve tampoco se puede tocar.\n *\n * @param {object} theme\n * @param {boolean} isShown\n * @param {string} transformOrigin hacia dónde se encoge ('left bottom')\n * @returns {object}\n */\nfunction appearStylesOf(theme, isShown, transformOrigin) {\n const duration = theme.transitions.duration.short;\n return {\n transformOrigin,\n opacity: isShown ? 1 : 0,\n transform: isShown ? 'none' : `scale(${HIDDEN_SCALE})`,\n visibility: isShown ? 'visible' : 'hidden',\n transition: `${theme.transitions.create(['opacity', 'transform'], {duration})}, visibility 0s linear ${isShown ? 0 : duration}ms`,\n };\n}\n\n/**\n * La superficie de la barra y del círculo de la cuenta: vidrio más opaco, su línea fina y su sombra (la sombra va en una\n * capa detrás, ::before, para no pisar el filo de luz del vidrio, que también es una sombra). Con el minimalista, sólida.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction floatingSurfaceOf(theme) {\n // con el minimalista (barra en cápsulas) la barra es sólida: su gris de lo que flota, sin línea ni sombra\n if (!isGlassTheme(theme)) {\n return {...minimalFloatingStylesOf(theme), position: 'relative', borderRadius: '999px', height: '100%'};\n }\n return {\n ...glassStylesOf(theme, {opacity: FLOATING_GLASS_OPACITY}),\n position: 'relative',\n borderRadius: '999px',\n height: '100%',\n outline: `1px solid ${FLOATING_EDGE.light.outline}`,\n '&::before': {\n content: '\"\"', position: 'absolute', inset: 0, borderRadius: 'inherit', zIndex: -1, pointerEvents: 'none',\n boxShadow: FLOATING_EDGE.light.shadow,\n },\n ...theme.applyStyles('dark', {\n outline: `1px solid ${FLOATING_EDGE.dark.outline}`,\n '&::before': {boxShadow: FLOATING_EDGE.dark.shadow},\n }),\n };\n}\n\n/**\n * La pestaña elegida: el tinte del acento y su letra legible.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction activeTabOf(theme) {\n const primary = (theme.vars ?? theme).palette.primary.main;\n return mergeStyles({\n backgroundColor: `color-mix(in srgb, ${primary} ${ACTIVE_TINT_PERCENT.light}%, transparent)`,\n ...theme.applyStyles('dark', {backgroundColor: `color-mix(in srgb, ${primary} ${ACTIVE_TINT_PERCENT.dark}%, transparent)`}),\n }, accentTextStylesOf(theme));\n}\n\n/**\n * Una pestaña: liga a su pantalla o, con submenú, botón que abre el menú de acciones.\n *\n * @param {object} props\n * @param {string} props.label ya traducido\n * @param {import('react').ElementType} [props.Icon]\n * @param {boolean} props.isActive\n * @param {{content: number|string, isCount: boolean}|null} props.badge\n * @param {object} [props.linkProps] {component, to} si navega\n * @param {(event: object) => void} [props.onClick]\n * @param {boolean} [props.hasPopup] abre un menú\n */\nfunction TabButton({label, Icon, isActive, badge, linkProps = {}, onClick, hasPopup = false}) {\n return (\n <TabBarTab {...linkProps} onClick={onClick} aria-haspopup={hasPopup ? 'menu' : undefined} aria-current={isActive ? 'page' : undefined}\n ownSx={(theme) => mergeStyles({\n flex: '1 1 0', minWidth: 0, height: '100%', borderRadius: '999px', px: 0,\n display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 0.25,\n color: 'text.primary',\n transition: theme.transitions.create(['background-color', 'color'], {duration: theme.transitions.duration.shortest}),\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {flexDirection: 'row', gap: 0.75},\n }, isActive ? activeTabOf(theme) : {})}>\n <Badge invisible={!badge?.isCount} color=\"error\" max={BADGE_MAX} overlap=\"circular\" badgeContent={badge?.isCount ? badge.content : undefined}>\n {Icon ? <Icon sx={{fontSize: TAB_ICON_SIZE}} /> : <TabBarTabIconPlaceholder ownSx={{width: TAB_ICON_SIZE, height: TAB_ICON_SIZE}} />}\n </Badge>\n <TabBarTabLabel component=\"span\" noWrap\n ownSx={{...textSizeOf(TAB_LABEL_PIXELS.portrait), fontWeight: 500, maxWidth: '100%',\n [PHONE_LANDSCAPE_MEDIA_QUERY]: textSizeOf(TAB_LABEL_PIXELS.landscape)}}>\n {label}\n </TabBarTabLabel>\n </TabBarTab>\n );\n}\n\n/**\n * Cuántas pestañas caben en la cápsula (contando «Más»). Se mide la cápsula, que no cambia de ancho con sus pestañas (se\n * estira hasta su tope), así que la cuenta no da vueltas. Se vuelve a medir al girar el teléfono o cambiar la ventana.\n *\n * @param {HTMLElement|null} capsule\n * @param {number} minTabPixels lo menos que mide una pestaña\n * @returns {number}\n */\nfunction useFittingTabCount(capsule, minTabPixels) {\n const [tabCount, setTabCount] = useState(DEFAULT_TAB_COUNT);\n // antes de pintar: así no se ve un momento con las pestañas apretadas\n useLayoutEffect(() => {\n if (!capsule) {\n return undefined;\n }\n const measure = () => {\n const style = window.getComputedStyle(capsule);\n const innerWidth = capsule.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n // en escritorio la barra no se ve (ancho 0): se quedan las de siempre\n if (innerWidth > 0) {\n setTabCount(Math.max(2, Math.floor(innerWidth / minTabPixels)));\n }\n };\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(capsule);\n return () => observer.disconnect();\n }, [capsule, minTabPixels]);\n return tabCount;\n}\n\n/**\n * @param {object} props\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.account] la cuenta (o el engrane), en su círculo junto a la barra\n * @param {import('react').ReactNode} [props.moreFooter] lo que va al final de la hoja de «Más» (la tarjeta del menú lateral)\n * @param {boolean} [props.minimizeOnScroll] se encoge al leer hacia abajo; por omisión sí\n * @param {(keys: string[]|null) => void} [props.onPinnedTabsChange] la persona eligió sus pestañas (null: regresó a las de\n * omisión)\n */\nfunction MobileTabBarContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaMobileTabBar.defaultProps) y encima lo que llega aquí\n const {menuItems, activeItem, menuBadges, account, moreFooter, minimizeOnScroll = true, onPinnedTabsChange, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const {pinnedKeys, setPinnedKeys} = usePinnedMobileTabs();\n const [tabsCapsule, setTabsCapsule] = useState(null);\n const isLandscape = useMediaQuery(PHONE_LANDSCAPE_MEDIA_QUERY);\n const tabCount = useFittingTabCount(tabsCapsule, isLandscape ? MIN_TAB_PIXELS.landscape : MIN_TAB_PIXELS.portrait);\n const {tabs, more} = mobileTabsOf(menuItems, pinnedKeys, tabCount);\n const [submenu, setSubmenu] = useState({anchorElement: null, menuItem: null});\n const [isMoreOpen, setIsMoreOpen] = useState(false);\n const [isEditingTabs, setIsEditingTabs] = useState(false);\n // al salir del editor su botón desaparece con el foco adentro: el foco regresa a «Editar la barra» (sin eso se perdía\n // y Escape ya no cerraba la hoja; también lo necesita quien usa lector de pantalla)\n const [returnsFromEditor, setReturnsFromEditor] = useState(false);\n const leaveEditor = () => {\n setIsEditingTabs(false);\n setReturnsFromEditor(true);\n };\n const closeMore = () => {\n setIsMoreOpen(false);\n setIsEditingTabs(false);\n setReturnsFromEditor(false);\n };\n const {isMinimized, expand} = useTabBarMinimized(minimizeOnScroll);\n const closeSubmenu = () => setSubmenu({anchorElement: null, menuItem: null});\n const isInside = (menuItem) => menuItem === activeItem || (menuItem.children ?? []).includes(activeItem);\n const moreHasActive = more.some(isInside);\n // la cápsula chica enseña dónde está la persona: su pestaña, «Más» si está en algo de ahí, o la primera\n const activeTab = tabs.find(isInside);\n const MinimizedIcon = (!activeTab && moreHasActive ? MoreHorizRoundedIcon : (activeTab ?? tabs[0])?.Icon) ?? MoreHorizRoundedIcon;\n // «Más» suma los conteos de lo que guarda (solo conteos: los de texto no van en la pestaña)\n const moreCount = more.reduce((total, menuItem) => {\n const badge = menuBadgeOf(menuItem, menuBadges);\n return total + (badge?.isCount ? badge.content : 0);\n }, 0);\n const moreBadge = moreCount > 0 ? {content: moreCount, isCount: true} : null;\n\n const glassSurfaceSx = floatingSurfaceOf;\n\n return (\n <>\n <TabBarRoot component=\"nav\" aria-label={t('drawer.ariaLabel')}\n ownSx={(theme) => ({\n display: 'none',\n [PHONE_MEDIA_QUERY]: {display: 'flex'},\n position: 'fixed', zIndex: theme.zIndex.appBar, gap: 0.75, justifyContent: 'center',\n left: GLASS_PANEL_GAP_PIXELS, right: GLASS_PANEL_GAP_PIXELS,\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS}px + env(safe-area-inset-bottom))`,\n height: MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait,\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {height: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape},\n })}>\n <TabBarBar ref={setTabsCapsule} inert={isMinimized}\n ownSx={(theme) => ({...glassSurfaceSx(theme), display: 'flex', flex: 1, maxWidth: TAB_BAR_MAX_WIDTH_PIXELS, p: 0.375,\n ...appearStylesOf(theme, !isMinimized, 'left bottom')})}>\n {tabs.map((menuItem) => {\n const hasChildren = (menuItem.children ?? []).length > 0;\n return (\n <TabButton key={menuItem.to ?? menuItem.href ?? menuItemLabelOf(menuItem, t)} label={menuItemLabelOf(menuItem, t)} Icon={menuItem.Icon}\n isActive={isInside(menuItem)} badge={menuBadgeOf(menuItem, menuBadges)} hasPopup={hasChildren}\n linkProps={hasChildren ? {} : menuLinkPropsOf(menuItem, RouterLink)}\n onClick={hasChildren ? (event) => setSubmenu({anchorElement: event.currentTarget, menuItem}) : undefined} />\n );\n })}\n {more.length > 0 && (\n <TabButton label={t('drawer.more')} Icon={MoreHorizRoundedIcon} isActive={moreHasActive} badge={moreBadge} hasPopup\n onClick={() => setIsMoreOpen(true)} />\n )}\n </TabBarBar>\n {/* encogida: la cápsula chica en la esquina, con la pestaña de ahorita; tocarla regresa la barra completa */}\n <TabBarMinimized inert={!isMinimized} onClick={expand} aria-label={t('drawer.showTabs')} aria-expanded={false}\n ownSx={(theme) => mergeStyles({\n ...glassSurfaceSx(theme), position: 'absolute', left: 0, bottom: 0,\n width: MINIMIZED_PIXELS.portrait, height: MINIMIZED_PIXELS.portrait,\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {width: MINIMIZED_PIXELS.landscape, height: MINIMIZED_PIXELS.landscape},\n ...appearStylesOf(theme, isMinimized, 'left bottom'),\n }, accentTextStylesOf(theme))}>\n <MinimizedIcon sx={{fontSize: TAB_ICON_SIZE}} />\n </TabBarMinimized>\n {account && (\n <TabBarAccount ownSx={(theme) => ({\n ...glassSurfaceSx(theme), flexShrink: 0, minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait, px: 0.5, display: 'flex', alignItems: 'center',\n justifyContent: 'center', [PHONE_LANDSCAPE_MEDIA_QUERY]: {minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape},\n '& .MuiButtonBase-root': {color: 'inherit'},\n // encogida, la cuenta se queda pero del alto de la cápsula chica, pegada a su esquina\n transformOrigin: 'right bottom',\n transform: isMinimized ? `scale(${MINIMIZED_PIXELS.portrait / MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait})` : 'none',\n transition: theme.transitions.create('transform', {duration: theme.transitions.duration.short}),\n ...(isMinimized && {[PHONE_LANDSCAPE_MEDIA_QUERY]: {\n minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape,\n transform: `scale(${MINIMIZED_PIXELS.landscape / MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape})`,\n }}),\n })}>\n {account}\n </TabBarAccount>\n )}\n </TabBarRoot>\n\n {submenu.menuItem && (\n <SubmenuActionsMenu anchorElement={submenu.anchorElement} onClose={closeSubmenu} menuItem={submenu.menuItem} activeItem={activeItem}\n menuBadges={menuBadges} placement={SUBMENU_PLACEMENT.above} />\n )}\n\n {/* «Más»: el resto del menú en una hoja desde abajo, separada de las orillas como las hojas de iOS */}\n <Drawer anchor=\"bottom\" open={isMoreOpen} onClose={closeMore}\n slotProps={{paper: {sx: {\n m: `${GLASS_PANEL_GAP_PIXELS}px auto calc(${GLASS_PANEL_GAP_PIXELS}px + env(safe-area-inset-bottom))`,\n width: `min(${MORE_SHEET_MAX_WIDTH_PIXELS}px, calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px))`,\n maxHeight: MORE_SHEET_MAX_HEIGHT, borderRadius: '20px', overflow: 'hidden',\n }}}}>\n {isEditingTabs ? (\n <TabBarEditor menuItems={menuItems} pinnedKeys={tabs.map(menuItemKeyOf)}\n onDone={(keys) => {\n setPinnedKeys(keys);\n onPinnedTabsChange?.(keys);\n leaveEditor();\n }}\n onReset={() => {\n setPinnedKeys(null);\n onPinnedTabsChange?.(null);\n leaveEditor();\n }} />\n ) : (\n <>\n <TabBarSheetHeader ownSx={{display: 'flex', justifyContent: 'flex-end', px: 1.5, pt: 1, flexShrink: 0}}>\n <Button size=\"small\" autoFocus={returnsFromEditor} onClick={() => setIsEditingTabs(true)}>{t('drawer.editTabs')}</Button>\n </TabBarSheetHeader>\n <SideMenu menuItems={more} activeItem={activeItem} menuBadges={menuBadges} collapsed={false} showBrand={false} footer={moreFooter}\n onNavigate={closeMore} />\n </>\n )}\n </Drawer>\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst MobileTabBar = withPieceSlots(MobileTabBarContent, THEME_NAME);\nexport default MobileTabBar;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MobileTabBar.js","sources":["../../src/appShell/MobileTabBar.jsx"],"sourcesContent":["// La barra de pestañas del teléfono con vidrio, como la de iOS 26: una cápsula de vidrio flotando abajo con las secciones\n// principales (mobileTabsOf: las que caben a lo ancho, y «Más» con el resto), y aparte, en su círculo de vidrio, la cuenta\n// (con todos los ajustes adentro). La reemplaza a la barra de abajo con ☰ y al cajón del menú (lo pidió el autor: «estilo\n// Apple pero con sus submenús de acciones»). También va con el minimalista cuando la barra va en cápsulas (usesMobileTabBar), sólida y\n// del gris de lo que flota (minimalFloatingStylesOf).\n//\n// - Una pestaña con submenú no navega: abre el menú de acciones de Apple arriba de ella (SubmenuActionsMenu).\n// - «Más» abre una hoja desde abajo con el resto del menú, por secciones (SideMenu, el mismo del escritorio). Arriba trae\n// «Editar la barra»: cada quien elige qué pantallas van abajo (TabBarEditor; se recuerda con usePinnedMobileTabs),\n// como pide la guía de Apple. Sin elegir, van las que marca el proyecto (mobileTab en menu.js).\n// - Cada pestaña lleva su badge como los de Apple (HIG «Tab bars»): ROJO con el número en blanco y solo para conteos (lo\n// pendiente, del servidor); un badge de texto («Nuevo») no va en la pestaña, se queda en el menú: Apple los reserva para\n// lo que pide atención.\n// - Cuántas pestañas van se mide (useFittingTabCount): las que caben con al menos MIN_TAB_PIXELS cada una; las demás van\n// a «Más». En una ventana angosta salen menos y en una ancha más, y los nombres no se cortan («Compone…»).\n// - Acostado (pantalla bajita), la barra va compacta: ícono y nombre lado a lado, para dejarle el alto al contenido.\n// - Al leer hacia abajo se encoge (como iOS 26): queda una cápsula chica con la pestaña donde está la persona, y la cuenta\n// más chica; al subir un poco, o al tocar la cápsula, regresa completa (cuándo: tabBarScroll.js). Se mueve solo con\n// opacidad y escala, lo que el navegador anima sin volver a acomodar la página: en un teléfono barato no se traba.\n// Solo se ve en el teléfono (PHONE_MEDIA_QUERY); en escritorio no ocupa nada.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaMobileTabBar con defaultProps y\n// styleOverrides de sus partes: tab, tabIconPlaceholder, tabLabel, root, bar, minimized, account, sheetHeader.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useLayoutEffect, useState} from 'react';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Drawer from '@mui/material/Drawer';\nimport Typography from '@mui/material/Typography';\nimport useMediaQuery from '@mui/material/useMediaQuery';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport {\n GLASS_PANEL_GAP_PIXELS, MOBILE_TAB_BAR_HEIGHT_PIXELS, PHONE_LANDSCAPE_MEDIA_QUERY, PHONE_MEDIA_QUERY,\n} from '@/appShell/shellSizes.js';\nimport SideMenu from '@/appShell/SideMenu.jsx';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport TabBarEditor from '@/appShell/TabBarEditor.jsx';\nimport {usePinnedMobileTabs} from '@/appShell/usePinnedMobileTabs.js';\nimport {useTabBarMinimized} from '@/appShell/useTabBarMinimized.js';\nimport {menuBadgeOf, menuItemKeyOf, mobileTabsOf} from '@/navigation/appMenu.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {accentTextStylesOf, glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaMobileTabBar)\nconst THEME_NAME = 'CalcitaMobileTabBar';\nconst TabBarTab = piecePart(THEME_NAME, 'Tab', ButtonBase);\nconst TabBarTabIconPlaceholder = piecePart(THEME_NAME, 'TabIconPlaceholder', Box);\nconst TabBarTabLabel = piecePart(THEME_NAME, 'TabLabel', Typography);\nconst TabBarRoot = piecePart(THEME_NAME, 'Root', Box);\nconst TabBarBar = piecePart(THEME_NAME, 'Bar', Box);\nconst TabBarMinimized = piecePart(THEME_NAME, 'Minimized', ButtonBase);\nconst TabBarAccount = piecePart(THEME_NAME, 'Account', Box);\nconst TabBarSheetHeader = piecePart(THEME_NAME, 'SheetHeader', Box);\n\n// el nombre de la pestaña parado y acostado (px), lo ancho máximo de la cápsula y lo alto de la hoja de «Más»\nconst TAB_LABEL_PIXELS = Object.freeze({portrait: 10, landscape: 12});\nconst TAB_ICON_SIZE = '1.5rem';\nconst TAB_BAR_MAX_WIDTH_PIXELS = 560;\n// lo menos que mide una pestaña (px) para que su nombre se lea entero: parada, un nombre de unas 11 letras («Formularios»\n// pide 61 px, medido en Chrome); acostada, ícono y nombre lado a lado. Con menos, la pestaña se va a «Más»\nconst MIN_TAB_PIXELS = Object.freeze({portrait: 60, landscape: 104});\n// antes de medir (o si la barra no se ve): las de siempre, 4 y «Más»\nconst DEFAULT_TAB_COUNT = 5;\nconst MORE_SHEET_MAX_WIDTH_PIXELS = 640;\nconst MORE_SHEET_MAX_HEIGHT = '75vh';\nconst BADGE_MAX = 99;\n// la barra flota sobre el contenido: vidrio más opaco que el de los controles, una línea fina en la orilla y una sombra\n// debajo. Con el vidrio de siempre, en claro se perdía sobre las tarjetas blancas y no parecía flotar (lo reportaron)\nconst FLOATING_GLASS_OPACITY = 0.8;\nconst FLOATING_EDGE = Object.freeze({\n light: {outline: 'rgba(15, 23, 42, 0.12)', shadow: '0 10px 30px rgba(15, 23, 42, 0.20), 0 2px 6px rgba(15, 23, 42, 0.10)'},\n dark: {outline: 'rgba(255, 255, 255, 0.16)', shadow: '0 10px 30px rgba(0, 0, 0, 0.65), 0 2px 6px rgba(0, 0, 0, 0.4)'},\n});\n// la pestaña elegida: un tinte del acento (en claro una gota blanca sobre la cápsula blanca no se distinguía)\nconst ACTIVE_TINT_PERCENT = Object.freeze({light: 14, dark: 24});\n// encogida: lo alto de la cápsula chica (px) y a qué tamaño se va lo que desaparece\nconst MINIMIZED_PIXELS = Object.freeze({portrait: 48, landscape: 40});\nconst HIDDEN_SCALE = 0.6;\n\n/**\n * Lo que aparece o desaparece al encoger la barra: solo opacidad y escala. visibility espera a que termine la animación\n * para esconderlo, y así lo que ya no se ve tampoco se puede tocar.\n *\n * @param {object} theme\n * @param {boolean} isShown\n * @param {string} transformOrigin hacia dónde se encoge ('left bottom')\n * @returns {object}\n */\nfunction appearStylesOf(theme, isShown, transformOrigin) {\n const duration = theme.transitions.duration.short;\n return {\n transformOrigin,\n opacity: isShown ? 1 : 0,\n transform: isShown ? 'none' : `scale(${HIDDEN_SCALE})`,\n visibility: isShown ? 'visible' : 'hidden',\n transition: `${theme.transitions.create(['opacity', 'transform'], {duration})}, visibility 0s linear ${isShown ? 0 : duration}ms`,\n };\n}\n\n/**\n * La superficie de la barra y del círculo de la cuenta: vidrio más opaco, su línea fina y su sombra (la sombra va en una\n * capa detrás, ::before, para no pisar el filo de luz del vidrio, que también es una sombra). Con el minimalista, sólida.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction floatingSurfaceOf(theme) {\n // con el minimalista (barra en cápsulas) la barra es sólida: su gris de lo que flota, sin línea ni sombra\n if (!isGlassTheme(theme)) {\n return {...minimalFloatingStylesOf(theme), position: 'relative', borderRadius: '999px', height: '100%'};\n }\n return {\n ...glassStylesOf(theme, {opacity: FLOATING_GLASS_OPACITY}),\n position: 'relative',\n borderRadius: '999px',\n height: '100%',\n outline: `1px solid ${FLOATING_EDGE.light.outline}`,\n '&::before': {\n content: '\"\"', position: 'absolute', inset: 0, borderRadius: 'inherit', zIndex: -1, pointerEvents: 'none',\n boxShadow: FLOATING_EDGE.light.shadow,\n },\n ...theme.applyStyles('dark', {\n outline: `1px solid ${FLOATING_EDGE.dark.outline}`,\n '&::before': {boxShadow: FLOATING_EDGE.dark.shadow},\n }),\n };\n}\n\n/**\n * La pestaña elegida: el tinte del acento y su letra legible.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction activeTabOf(theme) {\n const primary = (theme.vars ?? theme).palette.primary.main;\n return mergeStyles({\n backgroundColor: `color-mix(in srgb, ${primary} ${ACTIVE_TINT_PERCENT.light}%, transparent)`,\n ...theme.applyStyles('dark', {backgroundColor: `color-mix(in srgb, ${primary} ${ACTIVE_TINT_PERCENT.dark}%, transparent)`}),\n }, accentTextStylesOf(theme));\n}\n\n/**\n * Una pestaña: liga a su pantalla o, con submenú, botón que abre el menú de acciones.\n *\n * @param {object} props\n * @param {string} props.label ya traducido\n * @param {import('react').ElementType} [props.Icon]\n * @param {boolean} props.isActive\n * @param {{content: number|string, isCount: boolean}|null} props.badge\n * @param {object} [props.linkProps] {component, to} si navega\n * @param {(event: object) => void} [props.onClick]\n * @param {boolean} [props.hasPopup] abre un menú\n */\nfunction TabButton({label, Icon, isActive, badge, linkProps = {}, onClick, hasPopup = false}) {\n return (\n <TabBarTab {...linkProps} onClick={onClick} aria-haspopup={hasPopup ? 'menu' : undefined} aria-current={isActive ? 'page' : undefined}\n ownSx={(theme) => mergeStyles({\n flex: '1 1 0', minWidth: 0, height: '100%', borderRadius: '999px', px: 0,\n display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 0.25,\n color: 'text.primary',\n transition: theme.transitions.create(['background-color', 'color'], {duration: theme.transitions.duration.shortest}),\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {flexDirection: 'row', gap: 0.75},\n }, isActive ? activeTabOf(theme) : {})}>\n <Badge invisible={!badge?.isCount} color=\"error\" max={BADGE_MAX} overlap=\"circular\" badgeContent={badge?.isCount ? badge.content : undefined}>\n {Icon ? <Icon sx={{fontSize: TAB_ICON_SIZE}} /> : <TabBarTabIconPlaceholder ownSx={{width: TAB_ICON_SIZE, height: TAB_ICON_SIZE}} />}\n </Badge>\n <TabBarTabLabel component=\"span\" noWrap\n ownSx={{...textSizeOf(TAB_LABEL_PIXELS.portrait), fontWeight: 500, maxWidth: '100%',\n [PHONE_LANDSCAPE_MEDIA_QUERY]: textSizeOf(TAB_LABEL_PIXELS.landscape)}}>\n {label}\n </TabBarTabLabel>\n </TabBarTab>\n );\n}\n\n/**\n * Cuántas pestañas caben en la cápsula (contando «Más»). Se mide la cápsula, que no cambia de ancho con sus pestañas (se\n * estira hasta su tope), así que la cuenta no da vueltas. Se vuelve a medir al girar el teléfono o cambiar la ventana.\n *\n * @param {HTMLElement|null} capsule\n * @param {number} minTabPixels lo menos que mide una pestaña\n * @returns {number}\n */\nfunction useFittingTabCount(capsule, minTabPixels) {\n const [tabCount, setTabCount] = useState(DEFAULT_TAB_COUNT);\n // antes de pintar: así no se ve un momento con las pestañas apretadas\n useLayoutEffect(() => {\n if (!capsule) {\n return undefined;\n }\n const measure = () => {\n const style = window.getComputedStyle(capsule);\n const innerWidth = capsule.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n // en escritorio la barra no se ve (ancho 0): se quedan las de siempre\n if (innerWidth > 0) {\n setTabCount(Math.max(2, Math.floor(innerWidth / minTabPixels)));\n }\n };\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(capsule);\n return () => observer.disconnect();\n }, [capsule, minTabPixels]);\n return tabCount;\n}\n\n/**\n * @param {object} props\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.account] la cuenta (o el engrane), en su círculo junto a la barra\n * @param {import('react').ReactNode} [props.moreFooter] lo que va al final de la hoja de «Más» (la tarjeta del menú lateral)\n * @param {boolean} [props.minimizeOnScroll] se encoge al leer hacia abajo; por omisión sí\n * @param {string} [props.labelOverflow] un nombre que no cabe en su renglón de la hoja de «Más»: MENU_LABEL_OVERFLOW\n * @param {(keys: string[]|null) => void} [props.onPinnedTabsChange] la persona eligió sus pestañas (null: regresó a las de\n * omisión)\n */\nfunction MobileTabBarContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaMobileTabBar.defaultProps) y encima lo que llega aquí\n const {\n menuItems, activeItem, menuBadges, account, moreFooter, minimizeOnScroll = true, labelOverflow, onPinnedTabsChange, ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const {pinnedKeys, setPinnedKeys} = usePinnedMobileTabs();\n const [tabsCapsule, setTabsCapsule] = useState(null);\n const isLandscape = useMediaQuery(PHONE_LANDSCAPE_MEDIA_QUERY);\n const tabCount = useFittingTabCount(tabsCapsule, isLandscape ? MIN_TAB_PIXELS.landscape : MIN_TAB_PIXELS.portrait);\n const {tabs, more} = mobileTabsOf(menuItems, pinnedKeys, tabCount);\n const [submenu, setSubmenu] = useState({anchorElement: null, menuItem: null});\n const [isMoreOpen, setIsMoreOpen] = useState(false);\n const [isEditingTabs, setIsEditingTabs] = useState(false);\n // al salir del editor su botón desaparece con el foco adentro: el foco regresa a «Editar la barra» (sin eso se perdía\n // y Escape ya no cerraba la hoja; también lo necesita quien usa lector de pantalla)\n const [returnsFromEditor, setReturnsFromEditor] = useState(false);\n const leaveEditor = () => {\n setIsEditingTabs(false);\n setReturnsFromEditor(true);\n };\n const closeMore = () => {\n setIsMoreOpen(false);\n setIsEditingTabs(false);\n setReturnsFromEditor(false);\n };\n const {isMinimized, expand} = useTabBarMinimized(minimizeOnScroll);\n const closeSubmenu = () => setSubmenu({anchorElement: null, menuItem: null});\n const isInside = (menuItem) => menuItem === activeItem || (menuItem.children ?? []).includes(activeItem);\n const moreHasActive = more.some(isInside);\n // la cápsula chica enseña dónde está la persona: su pestaña, «Más» si está en algo de ahí, o la primera\n const activeTab = tabs.find(isInside);\n const MinimizedIcon = (!activeTab && moreHasActive ? MoreHorizRoundedIcon : (activeTab ?? tabs[0])?.Icon) ?? MoreHorizRoundedIcon;\n // «Más» suma los conteos de lo que guarda (solo conteos: los de texto no van en la pestaña)\n const moreCount = more.reduce((total, menuItem) => {\n const badge = menuBadgeOf(menuItem, menuBadges);\n return total + (badge?.isCount ? badge.content : 0);\n }, 0);\n const moreBadge = moreCount > 0 ? {content: moreCount, isCount: true} : null;\n\n const glassSurfaceSx = floatingSurfaceOf;\n\n return (\n <>\n <TabBarRoot component=\"nav\" aria-label={t('drawer.ariaLabel')}\n ownSx={(theme) => ({\n display: 'none',\n [PHONE_MEDIA_QUERY]: {display: 'flex'},\n position: 'fixed', zIndex: theme.zIndex.appBar, gap: 0.75, justifyContent: 'center',\n left: GLASS_PANEL_GAP_PIXELS, right: GLASS_PANEL_GAP_PIXELS,\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS}px + env(safe-area-inset-bottom))`,\n height: MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait,\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {height: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape},\n })}>\n <TabBarBar ref={setTabsCapsule} inert={isMinimized}\n ownSx={(theme) => ({...glassSurfaceSx(theme), display: 'flex', flex: 1, maxWidth: TAB_BAR_MAX_WIDTH_PIXELS, p: 0.375,\n ...appearStylesOf(theme, !isMinimized, 'left bottom')})}>\n {tabs.map((menuItem) => {\n const hasChildren = (menuItem.children ?? []).length > 0;\n return (\n <TabButton key={menuItem.to ?? menuItem.href ?? menuItemLabelOf(menuItem, t)} label={menuItemLabelOf(menuItem, t)} Icon={menuItem.Icon}\n isActive={isInside(menuItem)} badge={menuBadgeOf(menuItem, menuBadges)} hasPopup={hasChildren}\n linkProps={hasChildren ? {} : menuLinkPropsOf(menuItem, RouterLink)}\n onClick={hasChildren ? (event) => setSubmenu({anchorElement: event.currentTarget, menuItem}) : undefined} />\n );\n })}\n {more.length > 0 && (\n <TabButton label={t('drawer.more')} Icon={MoreHorizRoundedIcon} isActive={moreHasActive} badge={moreBadge} hasPopup\n onClick={() => setIsMoreOpen(true)} />\n )}\n </TabBarBar>\n {/* encogida: la cápsula chica en la esquina, con la pestaña de ahorita; tocarla regresa la barra completa */}\n <TabBarMinimized inert={!isMinimized} onClick={expand} aria-label={t('drawer.showTabs')} aria-expanded={false}\n ownSx={(theme) => mergeStyles({\n ...glassSurfaceSx(theme), position: 'absolute', left: 0, bottom: 0,\n width: MINIMIZED_PIXELS.portrait, height: MINIMIZED_PIXELS.portrait,\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {width: MINIMIZED_PIXELS.landscape, height: MINIMIZED_PIXELS.landscape},\n ...appearStylesOf(theme, isMinimized, 'left bottom'),\n }, accentTextStylesOf(theme))}>\n <MinimizedIcon sx={{fontSize: TAB_ICON_SIZE}} />\n </TabBarMinimized>\n {account && (\n <TabBarAccount ownSx={(theme) => ({\n ...glassSurfaceSx(theme), flexShrink: 0, minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait, px: 0.5, display: 'flex', alignItems: 'center',\n justifyContent: 'center', [PHONE_LANDSCAPE_MEDIA_QUERY]: {minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape},\n '& .MuiButtonBase-root': {color: 'inherit'},\n // encogida, la cuenta se queda pero del alto de la cápsula chica, pegada a su esquina\n transformOrigin: 'right bottom',\n transform: isMinimized ? `scale(${MINIMIZED_PIXELS.portrait / MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait})` : 'none',\n transition: theme.transitions.create('transform', {duration: theme.transitions.duration.short}),\n ...(isMinimized && {[PHONE_LANDSCAPE_MEDIA_QUERY]: {\n minWidth: MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape,\n transform: `scale(${MINIMIZED_PIXELS.landscape / MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape})`,\n }}),\n })}>\n {account}\n </TabBarAccount>\n )}\n </TabBarRoot>\n\n {submenu.menuItem && (\n <SubmenuActionsMenu anchorElement={submenu.anchorElement} onClose={closeSubmenu} menuItem={submenu.menuItem} activeItem={activeItem}\n menuBadges={menuBadges} placement={SUBMENU_PLACEMENT.above} />\n )}\n\n {/* «Más»: el resto del menú en una hoja desde abajo, separada de las orillas como las hojas de iOS */}\n <Drawer anchor=\"bottom\" open={isMoreOpen} onClose={closeMore}\n slotProps={{paper: {sx: {\n m: `${GLASS_PANEL_GAP_PIXELS}px auto calc(${GLASS_PANEL_GAP_PIXELS}px + env(safe-area-inset-bottom))`,\n width: `min(${MORE_SHEET_MAX_WIDTH_PIXELS}px, calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px))`,\n maxHeight: MORE_SHEET_MAX_HEIGHT, borderRadius: '20px', overflow: 'hidden',\n }}}}>\n {isEditingTabs ? (\n <TabBarEditor menuItems={menuItems} pinnedKeys={tabs.map(menuItemKeyOf)}\n onDone={(keys) => {\n setPinnedKeys(keys);\n onPinnedTabsChange?.(keys);\n leaveEditor();\n }}\n onReset={() => {\n setPinnedKeys(null);\n onPinnedTabsChange?.(null);\n leaveEditor();\n }} />\n ) : (\n <>\n <TabBarSheetHeader ownSx={{display: 'flex', justifyContent: 'flex-end', px: 1.5, pt: 1, flexShrink: 0}}>\n <Button size=\"small\" autoFocus={returnsFromEditor} onClick={() => setIsEditingTabs(true)}>{t('drawer.editTabs')}</Button>\n </TabBarSheetHeader>\n <SideMenu menuItems={more} activeItem={activeItem} menuBadges={menuBadges} collapsed={false} showBrand={false} footer={moreFooter}\n labelOverflow={labelOverflow} onNavigate={closeMore} />\n </>\n )}\n </Drawer>\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst MobileTabBar = withPieceSlots(MobileTabBarContent, THEME_NAME);\nexport default MobileTabBar;\n"],"names":[],"mappings":"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"}
|
|
@@ -80,6 +80,7 @@ function SideMenuContent(props) {
|
|
|
80
80
|
railTop,
|
|
81
81
|
railBottom,
|
|
82
82
|
menuBadges,
|
|
83
|
+
labelOverflow,
|
|
83
84
|
...unknownProps
|
|
84
85
|
} = usePieceProps(props, THEME_NAME);
|
|
85
86
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
@@ -217,7 +218,8 @@ function SideMenuContent(props) {
|
|
|
217
218
|
activeItem,
|
|
218
219
|
collapsed,
|
|
219
220
|
onNavigate,
|
|
220
|
-
menuBadges
|
|
221
|
+
menuBadges,
|
|
222
|
+
labelOverflow
|
|
221
223
|
},
|
|
222
224
|
menuItem.to ?? menuItemLabelOf(menuItem, t)
|
|
223
225
|
))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideMenu.js","sources":["../../src/appShell/SideMenu.jsx"],"sourcesContent":["// Lo de adentro del menú lateral (el fijo de escritorio y el cajón del teléfono): arriba el logo y el nombre de la app\n// (solo en el cajón del teléfono: en escritorio van en la barra), en medio las secciones del menú (composeMenu, con el\n// título de cada sección) y abajo lo que el proyecto quiera (sidebarFooter: una tarjeta de ayuda, el plan contratado…).\n//\n// Es una columna de toda la altura: el menú crece y se desliza solo, y el pie se queda abajo.\n// Encogido (solo íconos): el título de cada sección se vuelve una rayita del mismo alto (los huecos entre secciones se\n// quedan parejos y nada brinca al abrirlo), el pie se quita (escondido seguía ocupando su lugar y sacaba la barra de\n// desplazamiento). La barra de desplazamiento no se pinta nunca: se comía el ancho de los nombres (la rueda sigue).\n// Con el vidrio del tema, los títulos de sección van como se escriben («Operación») y no en mayúsculas: la guía de Liquid\n// Glass de Apple los dejó de poner en mayúsculas porque se leen mejor.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSideMenu con defaultProps y\n// styleOverrides de sus partes: scrollArrow, root, brand, brandLogo, brandText, brandName, brandCaption, railTop,\n// listArea, list, section, sectionTitle, sectionTitleText, footer, railBottom.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';\nimport KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';\n\nimport {menuItemLabelOf, menuSectionsOf} from '@/appShell/menuItemText.js';\nimport Wordmark from '@/appShell/Wordmark.jsx';\nimport {APP_BAR_HEIGHT_PIXELS} from '@/appShell/shellSizes.js';\nimport SideMenuEntry from '@/appShell/SideMenuEntry.jsx';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSideMenu)\nconst THEME_NAME = 'CalcitaSideMenu';\nconst MenuScrollArrow = piecePart(THEME_NAME, 'ScrollArrow', IconButton);\nconst MenuRoot = piecePart(THEME_NAME, 'Root', Box);\nconst MenuBrand = piecePart(THEME_NAME, 'Brand', ButtonBase);\nconst MenuBrandLogo = piecePart(THEME_NAME, 'BrandLogo', Box);\nconst MenuBrandText = piecePart(THEME_NAME, 'BrandText', Box);\nconst MenuBrandName = piecePart(THEME_NAME, 'BrandName', Typography);\nconst MenuBrandCaption = piecePart(THEME_NAME, 'BrandCaption', Typography);\nconst MenuRailTop = piecePart(THEME_NAME, 'RailTop', Box);\nconst MenuListArea = piecePart(THEME_NAME, 'ListArea', Box);\nconst MenuList = piecePart(THEME_NAME, 'List', List);\nconst MenuSection = piecePart(THEME_NAME, 'Section', Box);\nconst MenuSectionTitle = piecePart(THEME_NAME, 'SectionTitle', ListSubheader);\nconst MenuSectionTitleText = piecePart(THEME_NAME, 'SectionTitleText', Box);\nconst MenuFooter = piecePart(THEME_NAME, 'Footer', Box);\nconst MenuRailBottom = piecePart(THEME_NAME, 'RailBottom', Box);\n\n// lo alto del desvanecido en la orilla del menú que tiene más (px)\nconst SCROLL_FADE_PIXELS = 28;\n// el estilo minimalista del tema (THEME_LOOK.minimal; se repite el texto para no importar el tema desde el menú)\nconst MINIMAL_LOOK = 'minimal';\n// la flecha: a cuánto de la orilla va y cuánto se repite al deslizar un tramo (px)\nconst SCROLL_ARROW_INSET_PIXELS = 4;\nconst SCROLL_STEP_OVERLAP_PIXELS = 44;\n\n/** La flecha de una orilla del menú: un botoncito de vidrio centrado que lo desliza un tramo. */\nfunction ScrollArrow({label, onClick, sx, children}) {\n return (\n <Tooltip title={label} placement=\"right\">\n <MenuScrollArrow size=\"small\" aria-label={label} onClick={onClick}\n ownSx={[(theme) => ({\n position: 'absolute', left: '50%', transform: 'translateX(-50%)', zIndex: 1, width: 32, height: 24, borderRadius: '999px',\n ...(isGlassTheme(theme) ? glassStylesOf(theme, {isControl: true}) : {bgcolor: 'background.paper', boxShadow: 2}),\n }), sx]}>\n {children}\n </MenuScrollArrow>\n </Tooltip>\n );\n}\n\n// el logo de 28 px queda centrado en el menú encogido de 64 px: (64 - 28) / 2\nconst BRAND_LEFT_PADDING = '18px';\n// la letra del título de sección (rem): en mayúsculas, más chica; con vidrio, como se escribe, un poco más grande. Con\n// alto de renglón 2, la mitad del renglón mide lo mismo que la letra: ahí va la rayita del encogido\nconst SECTION_TITLE_FONT_REM = Object.freeze({solid: 0.6875, glass: 0.75});\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), el que se queda con el menú encogido\n * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo\n * ancho (en lugar del logo y el nombre arriba), o uno para cada modo ({light, dark})\n * @param {string} props.brandName el nombre de la app\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} props.homePath\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem\n * @param {boolean} props.collapsed\n * @param {import('react').ReactNode} [props.footer]\n * @param {boolean} [props.showBrand] el logo y el nombre arriba; por omisión sí (en escritorio van en la barra)\n * @param {() => void} [props.onNavigate]\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.railTop] arriba del menú encogido que hace de barra: la ☰ y el logo\n * @param {import('react').ReactNode} [props.railBottom] abajo de ese menú: los ajustes y la cuenta\n */\nfunction SideMenuContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSideMenu.defaultProps) y encima lo que llega aquí\n const {\n logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, collapsed, footer, showBrand = true, onNavigate, railTop, railBottom, menuBadges,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n // con vidrio y con el minimalista (los dos siguen a Apple) los títulos de sección van como se escriben, no en mayúsculas\n const isAppleStyle = isGlassTheme(theme) || theme.toolkit?.look === MINIMAL_LOOK;\n const sectionTitleFontRem = isAppleStyle ? SECTION_TITLE_FONT_REM.glass : SECTION_TITLE_FONT_REM.solid;\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n // el menú no lleva barra de desplazamiento (ver abajo): cuando no cabe, se desvanece en la orilla de donde hay más, para\n // que se entienda que se desliza (lo reportaron: «no se entiende que scrollea»)\n const listRef = useRef(null);\n const [hiddenEdges, setHiddenEdges] = useState({top: false, bottom: false});\n useEffect(() => {\n const list = listRef.current;\n if (!list) {\n return undefined;\n }\n const measure = () => setHiddenEdges({\n top: list.scrollTop > 1,\n bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,\n });\n measure();\n list.addEventListener('scroll', measure, {passive: true});\n const resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n resizeObserver?.observe(list);\n return () => {\n list.removeEventListener('scroll', measure);\n resizeObserver?.disconnect();\n };\n }, [menuItems, collapsed]);\n // la flecha desliza el menú lo que mide su parte visible, menos un renglón para no perder de vista dónde se iba\n const scrollMenu = (direction) => listRef.current?.scrollBy({top: direction * (listRef.current.clientHeight - SCROLL_STEP_OVERLAP_PIXELS), behavior: 'smooth'});\n const fadeMask = `linear-gradient(to bottom, ${hiddenEdges.top ? 'transparent' : 'black'}, black ${SCROLL_FADE_PIXELS}px, black calc(100% - ${SCROLL_FADE_PIXELS}px), ${hiddenEdges.bottom ? 'transparent' : 'black'})`;\n\n return (\n // OJO: flex y no height 100%: dentro de la hoja de «Más» (que solo tiene alto máximo) un 100 % no se resuelve, el menú\n // crecía sin límite y la hoja no se deslizaba (lo reportaron con el teléfono acostado)\n <MenuRoot ownSx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n {showBrand && (\n <>\n <MenuBrand component={RouterLink} to={homePath} onClick={onNavigate} aria-label={brandName}\n ownSx={{display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1.25, minHeight: APP_BAR_HEIGHT_PIXELS,\n pl: BRAND_LEFT_PADDING, pr: 2, flexShrink: 0}}>\n {wordmark ? <Wordmark>{wordmark}</Wordmark> : (\n <>\n <MenuBrandLogo ownSx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</MenuBrandLogo>\n <MenuBrandText ownSx={(theme) => ({minWidth: 0, textAlign: 'left', ...fadeWhenCollapsed(theme)})}>\n <MenuBrandName variant=\"subtitle2\" component=\"span\" noWrap ownSx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</MenuBrandName>\n {brandCaption && <MenuBrandCaption variant=\"caption\" component=\"span\" color=\"text.secondary\" noWrap ownSx={{display: 'block', lineHeight: 1.3}}>{brandCaption}</MenuBrandCaption>}\n </MenuBrandText>\n </>\n )}\n </MenuBrand>\n <Divider />\n </>\n )}\n\n {railTop && <MenuRailTop ownSx={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, pt: 1.25, pb: 0.5, flexShrink: 0}}>{railTop}</MenuRailTop>}\n\n {/* la lista y sus flechas: cuando no cabe, una flecha en la orilla de donde hay más (como los menús largos de la Mac)\n que la desliza un tramo; el desvanecido la refuerza. Sin barra de desplazamiento no se entendía que había más */}\n <MenuListArea ownSx={{position: 'relative', flexGrow: 1, minHeight: 0, display: 'flex', flexDirection: 'column'}}>\n <MenuList ref={listRef} component=\"nav\" aria-label={t('drawer.ariaLabel')} dense disablePadding\n ownSx={{\n px: 1, pt: 1, pb: 2, flexGrow: 1, minHeight: 0, height: '100%', overflowY: 'auto', overflowX: 'hidden',\n // sin barra de desplazamiento, abierto o encogido (se desliza igual con la rueda, el trackpad o el\n // teclado, como en la Mac, donde la barra solo sale al deslizar): a la vista se veía una raya junto a\n // los nombres, y aunque fuera transparente se comía su ancho y cortaba «Alta con react-hook-form»\n scrollbarWidth: 'none',\n maskImage: fadeMask,\n WebkitMaskImage: fadeMask,\n '&::-webkit-scrollbar': {display: 'none'},\n }}>\n {menuSectionsOf(menuItems, t).map((section, sectionIndex) => (\n <MenuSection component=\"li\" key={section.title ?? `section-${sectionIndex}`} ownSx={{listStyle: 'none'}}>\n <List dense disablePadding\n subheader={section.title ? (\n <MenuSectionTitle disableSticky component=\"div\"\n ownSx={(theme) => ({position: 'relative', bgcolor: 'transparent', px: 1.5, pt: 1.5, lineHeight: 2, fontSize: `${sectionTitleFontRem}rem`,\n fontWeight: 600, letterSpacing: isAppleStyle ? 0 : '0.08em', textTransform: isAppleStyle ? 'none' : 'uppercase',\n color: 'text.secondary', whiteSpace: 'nowrap',\n // encogido: en lugar del título, una rayita a media altura del renglón\n '&::after': {\n content: '\"\"', position: 'absolute', left: '25%', right: '25%', height: '1px',\n top: `calc(${theme.spacing(1.5)} + ${sectionTitleFontRem}rem)`, bgcolor: 'divider',\n opacity: collapsed ? 1 : 0, transition: theme.transitions.create('opacity'),\n }})}>\n <MenuSectionTitleText component=\"span\" ownSx={fadeWhenCollapsed}>{section.title}</MenuSectionTitleText>\n </MenuSectionTitle>\n ) : null}>\n {section.menuItems.map((menuItem) => (\n <SideMenuEntry key={menuItem.to ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} />\n ))}\n </List>\n </MenuSection>\n ))}\n {/* la tarjeta de abajo va al final de lo que se desliza, no fija: en una pantalla baja (el teléfono acostado) se\n comía el menú y solo se veía «Inicio» (lo reportaron) */}\n {footer && (\n <MenuFooter component=\"li\" ownSx={{listStyle: 'none', pt: 2, px: 0.5, display: collapsed ? 'none' : 'block'}}>{footer}</MenuFooter>\n )}\n </MenuList>\n {hiddenEdges.top && (\n <ScrollArrow label={t('drawer.scrollUp')} onClick={() => scrollMenu(-1)} sx={{top: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowUpRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n {hiddenEdges.bottom && (\n <ScrollArrow label={t('drawer.scrollDown')} onClick={() => scrollMenu(1)} sx={{bottom: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowDownRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n </MenuListArea>\n\n {railBottom && <MenuRailBottom ownSx={{display: 'flex', justifyContent: 'center', pt: 1, pb: 1.25, flexShrink: 0, borderTop: 1, borderColor: 'divider'}}>{railBottom}</MenuRailBottom>}\n </MenuRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SideMenu = withPieceSlots(SideMenuContent, THEME_NAME);\nexport default SideMenu;\n"],"names":["theme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SideMenu.js","sources":["../../src/appShell/SideMenu.jsx"],"sourcesContent":["// Lo de adentro del menú lateral (el fijo de escritorio y el cajón del teléfono): arriba el logo y el nombre de la app\n// (solo en el cajón del teléfono: en escritorio van en la barra), en medio las secciones del menú (composeMenu, con el\n// título de cada sección) y abajo lo que el proyecto quiera (sidebarFooter: una tarjeta de ayuda, el plan contratado…).\n//\n// Es una columna de toda la altura: el menú crece y se desliza solo, y el pie se queda abajo.\n// Encogido (solo íconos): el título de cada sección se vuelve una rayita del mismo alto (los huecos entre secciones se\n// quedan parejos y nada brinca al abrirlo), el pie se quita (escondido seguía ocupando su lugar y sacaba la barra de\n// desplazamiento). La barra de desplazamiento no se pinta nunca: se comía el ancho de los nombres (la rueda sigue).\n// Con el vidrio del tema, los títulos de sección van como se escriben («Operación») y no en mayúsculas: la guía de Liquid\n// Glass de Apple los dejó de poner en mayúsculas porque se leen mejor.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSideMenu con defaultProps y\n// styleOverrides de sus partes: scrollArrow, root, brand, brandLogo, brandText, brandName, brandCaption, railTop,\n// listArea, list, section, sectionTitle, sectionTitleText, footer, railBottom.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';\nimport KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';\n\nimport {menuItemLabelOf, menuSectionsOf} from '@/appShell/menuItemText.js';\nimport Wordmark from '@/appShell/Wordmark.jsx';\nimport {APP_BAR_HEIGHT_PIXELS} from '@/appShell/shellSizes.js';\nimport SideMenuEntry from '@/appShell/SideMenuEntry.jsx';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSideMenu)\nconst THEME_NAME = 'CalcitaSideMenu';\nconst MenuScrollArrow = piecePart(THEME_NAME, 'ScrollArrow', IconButton);\nconst MenuRoot = piecePart(THEME_NAME, 'Root', Box);\nconst MenuBrand = piecePart(THEME_NAME, 'Brand', ButtonBase);\nconst MenuBrandLogo = piecePart(THEME_NAME, 'BrandLogo', Box);\nconst MenuBrandText = piecePart(THEME_NAME, 'BrandText', Box);\nconst MenuBrandName = piecePart(THEME_NAME, 'BrandName', Typography);\nconst MenuBrandCaption = piecePart(THEME_NAME, 'BrandCaption', Typography);\nconst MenuRailTop = piecePart(THEME_NAME, 'RailTop', Box);\nconst MenuListArea = piecePart(THEME_NAME, 'ListArea', Box);\nconst MenuList = piecePart(THEME_NAME, 'List', List);\nconst MenuSection = piecePart(THEME_NAME, 'Section', Box);\nconst MenuSectionTitle = piecePart(THEME_NAME, 'SectionTitle', ListSubheader);\nconst MenuSectionTitleText = piecePart(THEME_NAME, 'SectionTitleText', Box);\nconst MenuFooter = piecePart(THEME_NAME, 'Footer', Box);\nconst MenuRailBottom = piecePart(THEME_NAME, 'RailBottom', Box);\n\n// lo alto del desvanecido en la orilla del menú que tiene más (px)\nconst SCROLL_FADE_PIXELS = 28;\n// el estilo minimalista del tema (THEME_LOOK.minimal; se repite el texto para no importar el tema desde el menú)\nconst MINIMAL_LOOK = 'minimal';\n// la flecha: a cuánto de la orilla va y cuánto se repite al deslizar un tramo (px)\nconst SCROLL_ARROW_INSET_PIXELS = 4;\nconst SCROLL_STEP_OVERLAP_PIXELS = 44;\n\n/** La flecha de una orilla del menú: un botoncito de vidrio centrado que lo desliza un tramo. */\nfunction ScrollArrow({label, onClick, sx, children}) {\n return (\n <Tooltip title={label} placement=\"right\">\n <MenuScrollArrow size=\"small\" aria-label={label} onClick={onClick}\n ownSx={[(theme) => ({\n position: 'absolute', left: '50%', transform: 'translateX(-50%)', zIndex: 1, width: 32, height: 24, borderRadius: '999px',\n ...(isGlassTheme(theme) ? glassStylesOf(theme, {isControl: true}) : {bgcolor: 'background.paper', boxShadow: 2}),\n }), sx]}>\n {children}\n </MenuScrollArrow>\n </Tooltip>\n );\n}\n\n// el logo de 28 px queda centrado en el menú encogido de 64 px: (64 - 28) / 2\nconst BRAND_LEFT_PADDING = '18px';\n// la letra del título de sección (rem): en mayúsculas, más chica; con vidrio, como se escribe, un poco más grande. Con\n// alto de renglón 2, la mitad del renglón mide lo mismo que la letra: ahí va la rayita del encogido\nconst SECTION_TITLE_FONT_REM = Object.freeze({solid: 0.6875, glass: 0.75});\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), el que se queda con el menú encogido\n * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo\n * ancho (en lugar del logo y el nombre arriba), o uno para cada modo ({light, dark})\n * @param {string} props.brandName el nombre de la app\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} props.homePath\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem\n * @param {boolean} props.collapsed\n * @param {import('react').ReactNode} [props.footer]\n * @param {boolean} [props.showBrand] el logo y el nombre arriba; por omisión sí (en escritorio van en la barra)\n * @param {() => void} [props.onNavigate]\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.railTop] arriba del menú encogido que hace de barra: la ☰ y el logo\n * @param {import('react').ReactNode} [props.railBottom] abajo de ese menú: los ajustes y la cuenta\n * @param {string} [props.labelOverflow] un nombre que no cabe en su renglón: MENU_LABEL_OVERFLOW (SideMenuEntry)\n */\nfunction SideMenuContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSideMenu.defaultProps) y encima lo que llega aquí\n const {\n logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, collapsed, footer, showBrand = true, onNavigate, railTop, railBottom, menuBadges,\n labelOverflow, ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n // con vidrio y con el minimalista (los dos siguen a Apple) los títulos de sección van como se escriben, no en mayúsculas\n const isAppleStyle = isGlassTheme(theme) || theme.toolkit?.look === MINIMAL_LOOK;\n const sectionTitleFontRem = isAppleStyle ? SECTION_TITLE_FONT_REM.glass : SECTION_TITLE_FONT_REM.solid;\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n // el menú no lleva barra de desplazamiento (ver abajo): cuando no cabe, se desvanece en la orilla de donde hay más, para\n // que se entienda que se desliza (lo reportaron: «no se entiende que scrollea»)\n const listRef = useRef(null);\n const [hiddenEdges, setHiddenEdges] = useState({top: false, bottom: false});\n useEffect(() => {\n const list = listRef.current;\n if (!list) {\n return undefined;\n }\n const measure = () => setHiddenEdges({\n top: list.scrollTop > 1,\n bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,\n });\n measure();\n list.addEventListener('scroll', measure, {passive: true});\n const resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n resizeObserver?.observe(list);\n return () => {\n list.removeEventListener('scroll', measure);\n resizeObserver?.disconnect();\n };\n }, [menuItems, collapsed]);\n // la flecha desliza el menú lo que mide su parte visible, menos un renglón para no perder de vista dónde se iba\n const scrollMenu = (direction) => listRef.current?.scrollBy({top: direction * (listRef.current.clientHeight - SCROLL_STEP_OVERLAP_PIXELS), behavior: 'smooth'});\n const fadeMask = `linear-gradient(to bottom, ${hiddenEdges.top ? 'transparent' : 'black'}, black ${SCROLL_FADE_PIXELS}px, black calc(100% - ${SCROLL_FADE_PIXELS}px), ${hiddenEdges.bottom ? 'transparent' : 'black'})`;\n\n return (\n // OJO: flex y no height 100%: dentro de la hoja de «Más» (que solo tiene alto máximo) un 100 % no se resuelve, el menú\n // crecía sin límite y la hoja no se deslizaba (lo reportaron con el teléfono acostado)\n <MenuRoot ownSx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n {showBrand && (\n <>\n <MenuBrand component={RouterLink} to={homePath} onClick={onNavigate} aria-label={brandName}\n ownSx={{display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1.25, minHeight: APP_BAR_HEIGHT_PIXELS,\n pl: BRAND_LEFT_PADDING, pr: 2, flexShrink: 0}}>\n {wordmark ? <Wordmark>{wordmark}</Wordmark> : (\n <>\n <MenuBrandLogo ownSx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</MenuBrandLogo>\n <MenuBrandText ownSx={(theme) => ({minWidth: 0, textAlign: 'left', ...fadeWhenCollapsed(theme)})}>\n <MenuBrandName variant=\"subtitle2\" component=\"span\" noWrap ownSx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</MenuBrandName>\n {brandCaption && <MenuBrandCaption variant=\"caption\" component=\"span\" color=\"text.secondary\" noWrap ownSx={{display: 'block', lineHeight: 1.3}}>{brandCaption}</MenuBrandCaption>}\n </MenuBrandText>\n </>\n )}\n </MenuBrand>\n <Divider />\n </>\n )}\n\n {railTop && <MenuRailTop ownSx={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, pt: 1.25, pb: 0.5, flexShrink: 0}}>{railTop}</MenuRailTop>}\n\n {/* la lista y sus flechas: cuando no cabe, una flecha en la orilla de donde hay más (como los menús largos de la Mac)\n que la desliza un tramo; el desvanecido la refuerza. Sin barra de desplazamiento no se entendía que había más */}\n <MenuListArea ownSx={{position: 'relative', flexGrow: 1, minHeight: 0, display: 'flex', flexDirection: 'column'}}>\n <MenuList ref={listRef} component=\"nav\" aria-label={t('drawer.ariaLabel')} dense disablePadding\n ownSx={{\n px: 1, pt: 1, pb: 2, flexGrow: 1, minHeight: 0, height: '100%', overflowY: 'auto', overflowX: 'hidden',\n // sin barra de desplazamiento, abierto o encogido (se desliza igual con la rueda, el trackpad o el\n // teclado, como en la Mac, donde la barra solo sale al deslizar): a la vista se veía una raya junto a\n // los nombres, y aunque fuera transparente se comía su ancho y cortaba «Alta con react-hook-form»\n scrollbarWidth: 'none',\n maskImage: fadeMask,\n WebkitMaskImage: fadeMask,\n '&::-webkit-scrollbar': {display: 'none'},\n }}>\n {menuSectionsOf(menuItems, t).map((section, sectionIndex) => (\n <MenuSection component=\"li\" key={section.title ?? `section-${sectionIndex}`} ownSx={{listStyle: 'none'}}>\n <List dense disablePadding\n subheader={section.title ? (\n <MenuSectionTitle disableSticky component=\"div\"\n ownSx={(theme) => ({position: 'relative', bgcolor: 'transparent', px: 1.5, pt: 1.5, lineHeight: 2, fontSize: `${sectionTitleFontRem}rem`,\n fontWeight: 600, letterSpacing: isAppleStyle ? 0 : '0.08em', textTransform: isAppleStyle ? 'none' : 'uppercase',\n color: 'text.secondary', whiteSpace: 'nowrap',\n // encogido: en lugar del título, una rayita a media altura del renglón\n '&::after': {\n content: '\"\"', position: 'absolute', left: '25%', right: '25%', height: '1px',\n top: `calc(${theme.spacing(1.5)} + ${sectionTitleFontRem}rem)`, bgcolor: 'divider',\n opacity: collapsed ? 1 : 0, transition: theme.transitions.create('opacity'),\n }})}>\n <MenuSectionTitleText component=\"span\" ownSx={fadeWhenCollapsed}>{section.title}</MenuSectionTitleText>\n </MenuSectionTitle>\n ) : null}>\n {section.menuItems.map((menuItem) => (\n <SideMenuEntry key={menuItem.to ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} labelOverflow={labelOverflow} />\n ))}\n </List>\n </MenuSection>\n ))}\n {/* la tarjeta de abajo va al final de lo que se desliza, no fija: en una pantalla baja (el teléfono acostado) se\n comía el menú y solo se veía «Inicio» (lo reportaron) */}\n {footer && (\n <MenuFooter component=\"li\" ownSx={{listStyle: 'none', pt: 2, px: 0.5, display: collapsed ? 'none' : 'block'}}>{footer}</MenuFooter>\n )}\n </MenuList>\n {hiddenEdges.top && (\n <ScrollArrow label={t('drawer.scrollUp')} onClick={() => scrollMenu(-1)} sx={{top: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowUpRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n {hiddenEdges.bottom && (\n <ScrollArrow label={t('drawer.scrollDown')} onClick={() => scrollMenu(1)} sx={{bottom: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowDownRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n </MenuListArea>\n\n {railBottom && <MenuRailBottom ownSx={{display: 'flex', justifyContent: 'center', pt: 1, pb: 1.25, flexShrink: 0, borderTop: 1, borderColor: 'divider'}}>{railBottom}</MenuRailBottom>}\n </MenuRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SideMenu = withPieceSlots(SideMenuContent, THEME_NAME);\nexport default SideMenu;\n"],"names":["theme"],"mappings":"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"}
|
|
@@ -17,12 +17,19 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
|
17
17
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
18
18
|
import { menuItemLabelOf, menuLinkPropsOf } from "./menuItemText.js";
|
|
19
19
|
import SubmenuActionsMenu, { SUBMENU_PLACEMENT } from "./SubmenuActionsMenu.js";
|
|
20
|
+
import { DRAWER_WIDTH_EXPANDED } from "./shellSizes.js";
|
|
20
21
|
import { menuBadgeOf } from "../navigation/appMenu.js";
|
|
21
22
|
import { isGlassTheme, accentTextStylesOf, glassDropOf } from "../theme/glassSurface.js";
|
|
22
23
|
import { mergeStyles } from "../theme/mergeComponents.js";
|
|
23
24
|
import { textSizeOf } from "../theme/appleTracking.js";
|
|
24
25
|
import { piecePart, withPieceSlots, usePieceProps } from "../theme/themePieces.js";
|
|
25
26
|
import { warnUnknownProps } from "../toolkit/unknownProps.js";
|
|
27
|
+
const MENU_LABEL_OVERFLOW = Object.freeze({
|
|
28
|
+
// se corta con «…» (lo de siempre): todos los renglones miden lo mismo
|
|
29
|
+
ellipsis: "ellipsis",
|
|
30
|
+
// baja a otro renglón: se lee completo
|
|
31
|
+
wrap: "wrap"
|
|
32
|
+
});
|
|
26
33
|
const THEME_NAME = "CalcitaSideMenuEntry";
|
|
27
34
|
const EntryButton = piecePart(THEME_NAME, "Button", ListItemButton);
|
|
28
35
|
const EntryIcon = piecePart(THEME_NAME, "Icon", ListItemIcon);
|
|
@@ -45,8 +52,19 @@ const MENU_TEXT_PIXELS = 13;
|
|
|
45
52
|
const MENU_BADGE_PIXELS = 11;
|
|
46
53
|
const BADGE_MAX = 99;
|
|
47
54
|
const RAIL_BUTTON_RADIUS_PIXELS = 10;
|
|
55
|
+
const MENU_LIST_SIDE_PADDING = 1;
|
|
56
|
+
const DRAWER_LINE_PIXELS = 1;
|
|
48
57
|
function SideMenuEntryContent(props) {
|
|
49
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
menuItem,
|
|
60
|
+
activeItem,
|
|
61
|
+
collapsed,
|
|
62
|
+
onNavigate,
|
|
63
|
+
menuBadges,
|
|
64
|
+
labelOverflow = MENU_LABEL_OVERFLOW.ellipsis,
|
|
65
|
+
depth = 0,
|
|
66
|
+
...unknownProps
|
|
67
|
+
} = usePieceProps(props, THEME_NAME);
|
|
50
68
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
51
69
|
const { t } = useToolkitTranslation("layout");
|
|
52
70
|
const { Link: RouterLink } = useToolkitNavigation();
|
|
@@ -62,6 +80,7 @@ function SideMenuEntryContent(props) {
|
|
|
62
80
|
const label = menuItemLabelOf(menuItem, t);
|
|
63
81
|
const badge = menuBadgeOf(menuItem, menuBadges);
|
|
64
82
|
const Icon = menuItem.Icon;
|
|
83
|
+
const wrapsLabel = labelOverflow === MENU_LABEL_OVERFLOW.wrap && !collapsed;
|
|
65
84
|
const toggleChildren = (event) => {
|
|
66
85
|
if (collapsed) {
|
|
67
86
|
setFlyoutAnchor(event.currentTarget);
|
|
@@ -87,6 +106,7 @@ function SideMenuEntryContent(props) {
|
|
|
87
106
|
pl: leftPadding,
|
|
88
107
|
pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,
|
|
89
108
|
minHeight: isRailButton ? RAIL_BUTTON_PIXELS : isChild ? CHILD_ROW_MIN_HEIGHT_PIXELS : ROW_MIN_HEIGHT_PIXELS,
|
|
109
|
+
...wrapsLabel && { minWidth: `calc(${DRAWER_WIDTH_EXPANDED}px - ${theme.spacing(2 * MENU_LIST_SIDE_PADDING)} - ${DRAWER_LINE_PIXELS}px)` },
|
|
90
110
|
transition: theme.transitions.create(["background-color", "border-radius", "min-height"]),
|
|
91
111
|
...isActive ? activeText : { color: "text.primary" },
|
|
92
112
|
"&.Mui-selected, &.Mui-selected:hover": selectedStyle
|
|
@@ -109,7 +129,10 @@ function SideMenuEntryContent(props) {
|
|
|
109
129
|
{
|
|
110
130
|
primary: label,
|
|
111
131
|
ownSx: fadeWhenCollapsed,
|
|
112
|
-
slotProps: { primary: {
|
|
132
|
+
slotProps: { primary: {
|
|
133
|
+
noWrap: !wrapsLabel,
|
|
134
|
+
sx: { ...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400, ...wrapsLabel && { whiteSpace: "normal", overflowWrap: "anywhere" } }
|
|
135
|
+
} }
|
|
113
136
|
}
|
|
114
137
|
),
|
|
115
138
|
badge && /* @__PURE__ */ jsx(EntryBadge, { label: badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content, size: "small", color: "primary", ownSx: (theme2) => ({ height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme2) }) }),
|
|
@@ -139,6 +162,7 @@ function SideMenuEntryContent(props) {
|
|
|
139
162
|
collapsed,
|
|
140
163
|
onNavigate,
|
|
141
164
|
menuBadges,
|
|
165
|
+
labelOverflow,
|
|
142
166
|
depth: depth + 1
|
|
143
167
|
},
|
|
144
168
|
child.to ?? child.href ?? menuItemLabelOf(child, t)
|
|
@@ -147,6 +171,7 @@ function SideMenuEntryContent(props) {
|
|
|
147
171
|
}
|
|
148
172
|
const SideMenuEntry = withPieceSlots(SideMenuEntryContent, THEME_NAME);
|
|
149
173
|
export {
|
|
174
|
+
MENU_LABEL_OVERFLOW,
|
|
150
175
|
SideMenuEntry as default
|
|
151
176
|
};
|
|
152
177
|
//# sourceMappingURL=SideMenuEntry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideMenuEntry.js","sources":["../../src/appShell/SideMenuEntry.jsx"],"sourcesContent":["// Un renglón del menú lateral: ícono, nombre y (si trae) su numerito; si tiene submenú, se abre y se cierra en su lugar.\n// Con el menú encogido, el submenú sale como un menú flotante a la derecha del ícono (el de acciones de Apple: ícono y\n// nombre, la pantalla abierta marcada) en lugar de abrir todo el menú. Muestra un nivel: las hijas directas.\n//\n// Reglas que vienen del menú lateral de walk-ot, ya pagadas:\n// - La estructura es FIJA: al encoger el menú, el nombre y la flecha NO se desmontan, solo se vuelven transparentes. Si\n// se desmontaran, React los quita en el mismo cuadro en que el menú anima su ancho y el ojo lo lee como un salto.\n// - Encogido, el nombre sale en un tooltip a la derecha.\n// - Los tamaños van en rem, no en px: así crecen con el tamaño de letra que elige la persona (FontSizeToggle).\n// - El renglón es una liga de verdad (clic con la rueda, «abrir en otra pestaña»), no un onClick que navega.\n// - Una hija del submenú con ícono se mete un escalón; una sin ícono no deja hueco vacío: su nombre queda justo debajo\n// del de su madre (con el hueco, el nombre se cortaba: «Alta con react-hook-fo…»).\n//\n// Con el vidrio del tema, lo que pide la guía de Liquid Glass de Apple:\n// - lo elegido va en la «gota» (la misma de las pestañas), no en un fondo plano;\n// - encogido, el menú es un panel con las esquinas del expandido (20) y cada renglón un botón de 44 px (el mínimo para el\n// dedo) con esquina concéntrica: el radio del panel (20) menos el margen entre botón y orilla (10) da 10. Antes era una\n// cápsula con botones redondos y se veía demasiado redondo (lo reportaron).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSideMenuEntry con defaultProps y\n// styleOverrides de sus partes: button, icon, iconPlaceholder, text, badge, expand.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Chip from '@mui/material/Chip';\nimport Collapse from '@mui/material/Collapse';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Tooltip from '@mui/material/Tooltip';\nimport {useTheme} from '@mui/material/styles';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {accentTextStylesOf, glassDropOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSideMenuEntry)\nconst THEME_NAME = 'CalcitaSideMenuEntry';\nconst EntryButton = piecePart(THEME_NAME, 'Button', ListItemButton);\nconst EntryIcon = piecePart(THEME_NAME, 'Icon', ListItemIcon);\nconst EntryIconPlaceholder = piecePart(THEME_NAME, 'IconPlaceholder', Box);\nconst EntryText = piecePart(THEME_NAME, 'Text', ListItemText);\nconst EntryBadge = piecePart(THEME_NAME, 'Badge', Chip);\nconst EntryExpand = piecePart(THEME_NAME, 'Expand', Box);\n\n// las medidas del renglón, en unidades de spacing: los márgenes, el escalón de cada nivel de submenú y el espacio entre el\n// ícono y el nombre. Las hijas van más apretadas (ícono más chico, menos espacio, renglón más bajo): medido en Chrome, con\n// las medidas de las madres, «Alta con react-hook-form» en negritas pedía 160 px, tenía 154 y se cortaba.\nconst ROW_LEFT_PADDING = 1.5;\nconst ROW_RIGHT_PADDING = 1.5;\nconst CHILD_RIGHT_PADDING = 1;\nconst INDENT_PER_LEVEL = 1.5;\nconst ICON_GAP = 1.75;\nconst CHILD_ICON_GAP = 1;\nconst ICON_SIZE = '1.375rem';\nconst CHILD_ICON_SIZE = '1.25rem';\nconst ROW_MIN_HEIGHT_PIXELS = 40;\nconst CHILD_ROW_MIN_HEIGHT_PIXELS = 36;\n// encogido con vidrio: el botón de 44 px (lo mínimo para tocar con el dedo, WCAG 2.5.5) con su esquina concéntrica\nconst RAIL_BUTTON_PIXELS = 44;\n// el nombre del renglón y su numerito (px), con su espaciado de Apple (textSizeOf)\nconst MENU_TEXT_PIXELS = 13;\nconst MENU_BADGE_PIXELS = 11;\n// de ahí para arriba se lee «99+»\nconst BADGE_MAX = 99;\nconst RAIL_BUTTON_RADIUS_PIXELS = 10;\n\n/**\n * @param {object} props\n * @param {object} props.menuItem una entrada de composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {boolean} props.collapsed el menú está encogido (solo íconos)\n * @param {() => void} [props.onNavigate] al elegir una pantalla (el cajón del teléfono se cierra)\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey (menuBadgeOf)\n * @param {number} [props.depth]\n */\nfunction SideMenuEntryContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSideMenuEntry.defaultProps) y encima lo que llega aquí\n const {menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n const isRailButton = isGlass && collapsed;\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const containsActive = hasChildren && menuItem.children.includes(activeItem);\n const isActive = menuItem === activeItem || containsActive;\n // abierto si la persona lo abrió, o solo si ahí está la pantalla abierta\n const [openedByPerson, setOpenedByPerson] = useState(null);\n // encogido: el botón del que cuelga el menú flotante del submenú (null = cerrado)\n const [flyoutAnchor, setFlyoutAnchor] = useState(null);\n const isOpen = openedByPerson ?? containsActive;\n const label = menuItemLabelOf(menuItem, t);\n // el badge: el conteo del servidor, la suma de las hijas o el fijo del menu.js; encogido va sobre el ícono\n const badge = menuBadgeOf(menuItem, menuBadges);\n const Icon = menuItem.Icon;\n\n const toggleChildren = (event) => {\n if (collapsed) {\n setFlyoutAnchor(event.currentTarget);\n return;\n }\n setOpenedByPerson(!isOpen);\n };\n const closeFlyout = () => setFlyoutAnchor(null);\n\n const linkProps = hasChildren\n ? {onClick: toggleChildren, ...(collapsed && {'aria-haspopup': 'menu'})}\n : {...menuLinkPropsOf(menuItem, RouterLink), onClick: onNavigate};\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n\n const isChild = depth > 0;\n // una hija sin ícono empieza donde empieza el nombre de su madre: margen + ícono + espacio, más los escalones de en medio\n const leftPadding = !isChild || Icon\n ? theme.spacing(ROW_LEFT_PADDING + depth * INDENT_PER_LEVEL)\n : `calc(${theme.spacing(ROW_LEFT_PADDING + ICON_GAP + (depth - 1) * INDENT_PER_LEVEL)} + ${ICON_SIZE})`;\n // el renglón de la pantalla abierta va del color del tema, pero legible (accentTextStylesOf): el naranja tal cual sobre\n // blanco daba 2.78 : 1\n const activeText = accentTextStylesOf(theme);\n // OJO: se SUMAN con mergeStyles, no con {...a, ...b}: los dos traen su parte de modo oscuro y con la suma simple la segunda\n // borraba la primera (el elegido salía en oscuro con el fondo de claro, casi negro y con línea blanca; error mío)\n const selectedStyle = mergeStyles(isGlass ? glassDropOf(theme) : {bgcolor: 'action.selected'}, activeText);\n\n const entryButton = (\n <EntryButton {...linkProps} selected={!hasChildren && isActive} aria-expanded={hasChildren ? (collapsed ? Boolean(flyoutAnchor) : isOpen) : undefined}\n ownSx={{\n borderRadius: isRailButton ? `${RAIL_BUTTON_RADIUS_PIXELS}px` : 1.5, pl: leftPadding, pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,\n minHeight: isRailButton ? RAIL_BUTTON_PIXELS : (isChild ? CHILD_ROW_MIN_HEIGHT_PIXELS : ROW_MIN_HEIGHT_PIXELS),\n transition: theme.transitions.create(['background-color', 'border-radius', 'min-height']),\n ...(isActive ? activeText : {color: 'text.primary'}),\n '&.Mui-selected, &.Mui-selected:hover': selectedStyle,\n }}>\n {(!isChild || Icon) && (\n <EntryIcon ownSx={{minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: 'inherit'}}>\n <Badge invisible={!collapsed || !badge} color=\"primary\" max={BADGE_MAX} badgeContent={badge?.isCount ? badge.content : undefined}\n variant={badge?.isCount ? 'standard' : 'dot'} overlap=\"circular\">\n {Icon ? <Icon sx={{fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE}} /> : <EntryIconPlaceholder ownSx={{width: ICON_SIZE, height: ICON_SIZE}} />}\n </Badge>\n </EntryIcon>\n )}\n <EntryText primary={label} ownSx={fadeWhenCollapsed}\n slotProps={{primary: {noWrap: true, sx: {...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400}}}} />\n {badge && (\n <EntryBadge label={badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content} size=\"small\" color=\"primary\" ownSx={(theme) => ({height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme)})} />\n )}\n {hasChildren && (\n <EntryExpand ownSx={(theme) => ({display: 'flex', alignItems: 'center', ...fadeWhenCollapsed(theme)})}>\n {isOpen ? <ExpandMoreIcon fontSize=\"small\" /> : <ChevronRightIcon fontSize=\"small\" />}\n </EntryExpand>\n )}\n </EntryButton>\n );\n\n return (\n <>\n {collapsed ? <Tooltip title={label} placement=\"right\">{entryButton}</Tooltip> : entryButton}\n {hasChildren && collapsed && (\n <SubmenuActionsMenu anchorElement={flyoutAnchor} onClose={closeFlyout} menuItem={menuItem} activeItem={activeItem} menuBadges={menuBadges}\n onNavigate={onNavigate} placement={SUBMENU_PLACEMENT.side} />\n )}\n {hasChildren && (\n <Collapse in={isOpen && !collapsed} unmountOnExit>\n <List dense disablePadding>\n {menuItem.children.map((child) => (\n <SideMenuEntry key={child.to ?? child.href ?? menuItemLabelOf(child, t)} menuItem={child} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} depth={depth + 1} />\n ))}\n </List>\n </Collapse>\n )}\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SideMenuEntry = withPieceSlots(SideMenuEntryContent, THEME_NAME);\nexport default SideMenuEntry;\n"],"names":["theme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SideMenuEntry.js","sources":["../../src/appShell/SideMenuEntry.jsx"],"sourcesContent":["// Un renglón del menú lateral: ícono, nombre y (si trae) su numerito; si tiene submenú, se abre y se cierra en su lugar.\n// Con el menú encogido, el submenú sale como un menú flotante a la derecha del ícono (el de acciones de Apple: ícono y\n// nombre, la pantalla abierta marcada) en lugar de abrir todo el menú. Muestra un nivel: las hijas directas.\n//\n// Reglas que vienen del menú lateral de walk-ot, ya pagadas:\n// - La estructura es FIJA: al encoger el menú, el nombre y la flecha NO se desmontan, solo se vuelven transparentes. Si\n// se desmontaran, React los quita en el mismo cuadro en que el menú anima su ancho y el ojo lo lee como un salto.\n// - Encogido, el nombre sale en un tooltip a la derecha.\n// - Los tamaños van en rem, no en px: así crecen con el tamaño de letra que elige la persona (FontSizeToggle).\n// - El renglón es una liga de verdad (clic con la rueda, «abrir en otra pestaña»), no un onClick que navega.\n// - Una hija del submenú con ícono se mete un escalón; una sin ícono no deja hueco vacío: su nombre queda justo debajo\n// del de su madre (con el hueco, el nombre se cortaba: «Alta con react-hook-fo…»).\n//\n// Con el vidrio del tema, lo que pide la guía de Liquid Glass de Apple:\n// - lo elegido va en la «gota» (la misma de las pestañas), no en un fondo plano;\n// - encogido, el menú es un panel con las esquinas del expandido (20) y cada renglón un botón de 44 px (el mínimo para el\n// dedo) con esquina concéntrica: el radio del panel (20) menos el margen entre botón y orilla (10) da 10. Antes era una\n// cápsula con botones redondos y se veía demasiado redondo (lo reportaron).\n//\n// Un nombre largo se corta con «…» por omisión (el renglón mide siempre lo mismo). Con labelOverflow wrap baja a un\n// segundo renglón: el menú tiene ancho fijo y, con un vocabulario largo («Administración de clientes»), cortado ya no se\n// sabe a dónde lleva la entrada. Solo baja con el menú abierto (encogido el nombre ni se ve) y el renglón tiene desde el\n// principio el ancho del menú abierto: si creciera junto con el menú al abrirse, el nombre bajaría letra por letra (medido\n// en Chrome: un renglón llegó a 703 px de alto a media animación).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSideMenuEntry con defaultProps y\n// styleOverrides de sus partes: button, icon, iconPlaceholder, text, badge, expand.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Chip from '@mui/material/Chip';\nimport Collapse from '@mui/material/Collapse';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Tooltip from '@mui/material/Tooltip';\nimport {useTheme} from '@mui/material/styles';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport {DRAWER_WIDTH_EXPANDED} from '@/appShell/shellSizes.js';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {accentTextStylesOf, glassDropOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n/** Qué hace el menú lateral con un nombre que no cabe en su renglón. */\nexport const MENU_LABEL_OVERFLOW = Object.freeze({\n // se corta con «…» (lo de siempre): todos los renglones miden lo mismo\n ellipsis: 'ellipsis',\n // baja a otro renglón: se lee completo\n wrap: 'wrap',\n});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSideMenuEntry)\nconst THEME_NAME = 'CalcitaSideMenuEntry';\nconst EntryButton = piecePart(THEME_NAME, 'Button', ListItemButton);\nconst EntryIcon = piecePart(THEME_NAME, 'Icon', ListItemIcon);\nconst EntryIconPlaceholder = piecePart(THEME_NAME, 'IconPlaceholder', Box);\nconst EntryText = piecePart(THEME_NAME, 'Text', ListItemText);\nconst EntryBadge = piecePart(THEME_NAME, 'Badge', Chip);\nconst EntryExpand = piecePart(THEME_NAME, 'Expand', Box);\n\n// las medidas del renglón, en unidades de spacing: los márgenes, el escalón de cada nivel de submenú y el espacio entre el\n// ícono y el nombre. Las hijas van más apretadas (ícono más chico, menos espacio, renglón más bajo): medido en Chrome, con\n// las medidas de las madres, «Alta con react-hook-form» en negritas pedía 160 px, tenía 154 y se cortaba.\nconst ROW_LEFT_PADDING = 1.5;\nconst ROW_RIGHT_PADDING = 1.5;\nconst CHILD_RIGHT_PADDING = 1;\nconst INDENT_PER_LEVEL = 1.5;\nconst ICON_GAP = 1.75;\nconst CHILD_ICON_GAP = 1;\nconst ICON_SIZE = '1.375rem';\nconst CHILD_ICON_SIZE = '1.25rem';\nconst ROW_MIN_HEIGHT_PIXELS = 40;\nconst CHILD_ROW_MIN_HEIGHT_PIXELS = 36;\n// encogido con vidrio: el botón de 44 px (lo mínimo para tocar con el dedo, WCAG 2.5.5) con su esquina concéntrica\nconst RAIL_BUTTON_PIXELS = 44;\n// el nombre del renglón y su numerito (px), con su espaciado de Apple (textSizeOf)\nconst MENU_TEXT_PIXELS = 13;\nconst MENU_BADGE_PIXELS = 11;\n// de ahí para arriba se lee «99+»\nconst BADGE_MAX = 99;\nconst RAIL_BUTTON_RADIUS_PIXELS = 10;\n// con los nombres que bajan de renglón, lo menos que mide el renglón con el menú abierto: el ancho del cajón menos el margen\n// de la lista de SideMenu (px 1 de cada lado, en unidades de spacing) y la línea de la derecha del cajón sin vidrio. El\n// cajón recorta lo que sobra mientras se abre. OJO: si el proyecto angosta el cajón desde su tema, también tiene que bajar\n// este mínimo (styleOverrides de button) o se recortan el numerito y la flecha\nconst MENU_LIST_SIDE_PADDING = 1;\nconst DRAWER_LINE_PIXELS = 1;\n\n/**\n * @param {object} props\n * @param {object} props.menuItem una entrada de composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {boolean} props.collapsed el menú está encogido (solo íconos)\n * @param {() => void} [props.onNavigate] al elegir una pantalla (el cajón del teléfono se cierra)\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey (menuBadgeOf)\n * @param {string} [props.labelOverflow] un nombre que no cabe: MENU_LABEL_OVERFLOW.ellipsis (por omisión, se corta con\n * «…») o MENU_LABEL_OVERFLOW.wrap (baja a otro renglón)\n * @param {number} [props.depth]\n */\nfunction SideMenuEntryContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSideMenuEntry.defaultProps) y encima lo que llega aquí\n const {\n menuItem, activeItem, collapsed, onNavigate, menuBadges, labelOverflow = MENU_LABEL_OVERFLOW.ellipsis, depth = 0, ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n const isRailButton = isGlass && collapsed;\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const containsActive = hasChildren && menuItem.children.includes(activeItem);\n const isActive = menuItem === activeItem || containsActive;\n // abierto si la persona lo abrió, o solo si ahí está la pantalla abierta\n const [openedByPerson, setOpenedByPerson] = useState(null);\n // encogido: el botón del que cuelga el menú flotante del submenú (null = cerrado)\n const [flyoutAnchor, setFlyoutAnchor] = useState(null);\n const isOpen = openedByPerson ?? containsActive;\n const label = menuItemLabelOf(menuItem, t);\n // el badge: el conteo del servidor, la suma de las hijas o el fijo del menu.js; encogido va sobre el ícono\n const badge = menuBadgeOf(menuItem, menuBadges);\n const Icon = menuItem.Icon;\n // baja de renglón solo con el menú abierto (ver el encabezado). whiteSpace normal: el cajón del escritorio le pide a todo\n // su contenido un solo renglón (para que nada se reacomode mientras anima) y el nombre lo hereda. overflowWrap anywhere:\n // una palabra más larga que el renglón («conciliacion_de_pagos_del_mes») también baja en lugar de salirse\n const wrapsLabel = labelOverflow === MENU_LABEL_OVERFLOW.wrap && !collapsed;\n\n const toggleChildren = (event) => {\n if (collapsed) {\n setFlyoutAnchor(event.currentTarget);\n return;\n }\n setOpenedByPerson(!isOpen);\n };\n const closeFlyout = () => setFlyoutAnchor(null);\n\n const linkProps = hasChildren\n ? {onClick: toggleChildren, ...(collapsed && {'aria-haspopup': 'menu'})}\n : {...menuLinkPropsOf(menuItem, RouterLink), onClick: onNavigate};\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n\n const isChild = depth > 0;\n // una hija sin ícono empieza donde empieza el nombre de su madre: margen + ícono + espacio, más los escalones de en medio\n const leftPadding = !isChild || Icon\n ? theme.spacing(ROW_LEFT_PADDING + depth * INDENT_PER_LEVEL)\n : `calc(${theme.spacing(ROW_LEFT_PADDING + ICON_GAP + (depth - 1) * INDENT_PER_LEVEL)} + ${ICON_SIZE})`;\n // el renglón de la pantalla abierta va del color del tema, pero legible (accentTextStylesOf): el naranja tal cual sobre\n // blanco daba 2.78 : 1\n const activeText = accentTextStylesOf(theme);\n // OJO: se SUMAN con mergeStyles, no con {...a, ...b}: los dos traen su parte de modo oscuro y con la suma simple la segunda\n // borraba la primera (el elegido salía en oscuro con el fondo de claro, casi negro y con línea blanca; error mío)\n const selectedStyle = mergeStyles(isGlass ? glassDropOf(theme) : {bgcolor: 'action.selected'}, activeText);\n\n const entryButton = (\n <EntryButton {...linkProps} selected={!hasChildren && isActive} aria-expanded={hasChildren ? (collapsed ? Boolean(flyoutAnchor) : isOpen) : undefined}\n ownSx={{\n borderRadius: isRailButton ? `${RAIL_BUTTON_RADIUS_PIXELS}px` : 1.5, pl: leftPadding, pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,\n minHeight: isRailButton ? RAIL_BUTTON_PIXELS : (isChild ? CHILD_ROW_MIN_HEIGHT_PIXELS : ROW_MIN_HEIGHT_PIXELS),\n ...(wrapsLabel && {minWidth: `calc(${DRAWER_WIDTH_EXPANDED}px - ${theme.spacing(2 * MENU_LIST_SIDE_PADDING)} - ${DRAWER_LINE_PIXELS}px)`}),\n transition: theme.transitions.create(['background-color', 'border-radius', 'min-height']),\n ...(isActive ? activeText : {color: 'text.primary'}),\n '&.Mui-selected, &.Mui-selected:hover': selectedStyle,\n }}>\n {(!isChild || Icon) && (\n <EntryIcon ownSx={{minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: 'inherit'}}>\n <Badge invisible={!collapsed || !badge} color=\"primary\" max={BADGE_MAX} badgeContent={badge?.isCount ? badge.content : undefined}\n variant={badge?.isCount ? 'standard' : 'dot'} overlap=\"circular\">\n {Icon ? <Icon sx={{fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE}} /> : <EntryIconPlaceholder ownSx={{width: ICON_SIZE, height: ICON_SIZE}} />}\n </Badge>\n </EntryIcon>\n )}\n <EntryText primary={label} ownSx={fadeWhenCollapsed}\n slotProps={{primary: {\n noWrap: !wrapsLabel,\n sx: {...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400, ...(wrapsLabel && {whiteSpace: 'normal', overflowWrap: 'anywhere'})},\n }}} />\n {badge && (\n <EntryBadge label={badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content} size=\"small\" color=\"primary\" ownSx={(theme) => ({height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme)})} />\n )}\n {hasChildren && (\n <EntryExpand ownSx={(theme) => ({display: 'flex', alignItems: 'center', ...fadeWhenCollapsed(theme)})}>\n {isOpen ? <ExpandMoreIcon fontSize=\"small\" /> : <ChevronRightIcon fontSize=\"small\" />}\n </EntryExpand>\n )}\n </EntryButton>\n );\n\n return (\n <>\n {collapsed ? <Tooltip title={label} placement=\"right\">{entryButton}</Tooltip> : entryButton}\n {hasChildren && collapsed && (\n <SubmenuActionsMenu anchorElement={flyoutAnchor} onClose={closeFlyout} menuItem={menuItem} activeItem={activeItem} menuBadges={menuBadges}\n onNavigate={onNavigate} placement={SUBMENU_PLACEMENT.side} />\n )}\n {hasChildren && (\n <Collapse in={isOpen && !collapsed} unmountOnExit>\n <List dense disablePadding>\n {menuItem.children.map((child) => (\n <SideMenuEntry key={child.to ?? child.href ?? menuItemLabelOf(child, t)} menuItem={child} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} labelOverflow={labelOverflow} depth={depth + 1} />\n ))}\n </List>\n </Collapse>\n )}\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SideMenuEntry = withPieceSlots(SideMenuEntryContent, THEME_NAME);\nexport default SideMenuEntry;\n"],"names":["theme"],"mappings":"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"}
|
|
@@ -19,7 +19,9 @@ async function startDownload({ label, format, request, onCompleted, onFailed, on
|
|
|
19
19
|
id: downloadId,
|
|
20
20
|
loadedBytes: progressEvent.loaded,
|
|
21
21
|
totalBytes: progressEvent.total
|
|
22
|
-
})
|
|
22
|
+
}),
|
|
23
|
+
// un lote de varios archivos cuenta como UNA descarga: así su renglón dice en cuál va, en lugar de agregar otro
|
|
24
|
+
onLabelChange: (newLabel) => downloadsActor.send({ type: "RELABELED", id: downloadId, label: newLabel })
|
|
23
25
|
});
|
|
24
26
|
saveBlob(blob, fileName);
|
|
25
27
|
downloadsActor.send({ type: "COMPLETED", id: downloadId, fileName, sizeBytes: blob.size });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"downloadQueue.js","sources":["../../src/downloads/downloadQueue.js"],"sourcesContent":["// Arranca descargas y las anota en la lista que pinta DownloadManager (el administrador de descargas).\n//\n// Uso (desde un módulo, que es quien conoce el API):\n// startDownload({\n// label: t('detail.downloadLog'),\n// request: (transferOptions) => ingestionApi.downloadProcessingLog(batchId, transferOptions),\n// onCompleted: () => refreshPeriods(),\n// });\n//\n// startDownload no espera a que termine quien la llama: el diálogo de confirmación se cierra en cuanto la descarga entra\n// a la lista y la persona sigue trabajando mientras baja (el log más grande tarda ~30 s). El panel (tipo Google Drive) es\n// el ÚNICO lugar que avisa: avance, lista y error con su texto del API (RFC 9457). Sin aviso aparte, para no avisar dos\n// veces lo mismo.\n//\n// Una sola lista para toda la app (un actor de módulo, como el cliente de createApiClient): la llenan varias pantallas y\n// la lee un solo panel montado en la raíz de la aplicación.\n//\n// Funciones puras del paquete, no renderizan: function (regla de la casa). No conoce el API: el archivo lo pide la\n// `request` que le pasan.\nimport {createActor} from 'xstate';\n\nimport {DOWNLOAD_STATUS, downloadsMachine} from '@/downloads/downloadsMachine.js';\nimport {saveBlob} from '@/files.js';\nimport {newIdentifier} from '@/identifiers.js';\n\nexport const downloadsActor = createActor(downloadsMachine).start();\n\n// cómo cancelar cada descarga en curso; vive fuera del contexto de la máquina porque un AbortController no es un dato\nconst abortControllerByDownloadId = new Map();\n\n/**\n * Descarga un archivo con avance en el panel, lo guarda en disco y avisa al terminar.\n *\n * @param {object} options\n * @param {string} options.label ya traducido; lo que se ve en el panel mientras todavía no se sabe el nombre del\n * archivo\n * @param {string} [options.format] el tipo de archivo, para su ícono en el panel: xlsx, csv, pdf o uno del proyecto\n * (ToolkitProvider fileTypes)\n * @param {(transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} options.request\n * @param {(fileName: string) => void} [options.onCompleted] p. ej. volver a pedir la tabla (si la descarga cambia algo\n * en el servidor)\n * @param {(error: unknown) => void} [options.onFailed] cuando no se pudo (el panel ya lo dice; para avisar o reportarlo)\n * @param {() => void} [options.onCanceled] cuando la persona la canceló\n * @returns {Promise<string>} id de la descarga; la promesa se cumple cuando termina (bien, mal o cancelada), nunca falla\n */\nexport async function startDownload({label, format, request, onCompleted, onFailed, onCanceled}) {\n const downloadId = newIdentifier();\n const abortController = new AbortController();\n abortControllerByDownloadId.set(downloadId, abortController);\n downloadsActor.send({type: 'STARTED', id: downloadId, label, format});\n\n try {\n const {blob, fileName} = await request({\n signal: abortController.signal,\n // la petición avisa cada pedazo que llega; total viene en null cuando el servidor arma el archivo al vuelo (sin\n // Content-Length) y el panel enseña lo que lleva en vez del porcentaje\n onDownloadProgress: (progressEvent) => downloadsActor.send({\n type: 'PROGRESSED', id: downloadId, loadedBytes: progressEvent.loaded, totalBytes: progressEvent.total,\n }),\n });\n saveBlob(blob, fileName);\n downloadsActor.send({type: 'COMPLETED', id: downloadId, fileName, sizeBytes: blob.size});\n onCompleted?.(fileName);\n } catch (error) {\n // OJO: cancelar también llega aquí como error (el AbortError del navegador); se distingue por la señal, no por el\n // tipo de error, porque cada `request` del proyecto puede tronar con el suyo\n if (abortController.signal.aborted) {\n downloadsActor.send({type: 'CANCELED', id: downloadId});\n onCanceled?.();\n } else {\n // el mensaje ya viene traducido del API (ApiError con el detail de RFC 9457)\n downloadsActor.send({type: 'FAILED', id: downloadId, errorMessage: error?.message ?? null});\n onFailed?.(error);\n }\n } finally {\n abortControllerByDownloadId.delete(downloadId);\n }\n\n return downloadId;\n}\n\n/**\n * Cancela una descarga en curso (el panel la marca como cancelada).\n *\n * @param {string} downloadId\n */\nexport function cancelDownload(downloadId) {\n abortControllerByDownloadId.get(downloadId)?.abort();\n}\n\n/** Quita del panel todas las descargas terminadas (completadas, fallidas o canceladas). */\nexport function clearFinishedDownloads() {\n downloadsActor.send({type: 'CLEAR_FINISHED'});\n}\n\n/**\n * ¿Cuántas siguen descargando? Decide el resumen del panel y si se puede cerrar.\n *\n * @param {Array<object>} downloads\n * @returns {number}\n */\nexport function countActiveDownloads(downloads) {\n return downloads.filter((download) => download.status === DOWNLOAD_STATUS.downloading).length;\n}\n"],"names":[],"mappings":";;;;AAyBY,MAAC,iBAAiB,YAAY,gBAAgB,EAAE,MAAK;AAGjE,MAAM,8BAA8B,oBAAI,IAAG;
|
|
1
|
+
{"version":3,"file":"downloadQueue.js","sources":["../../src/downloads/downloadQueue.js"],"sourcesContent":["// Arranca descargas y las anota en la lista que pinta DownloadManager (el administrador de descargas).\n//\n// Uso (desde un módulo, que es quien conoce el API):\n// startDownload({\n// label: t('detail.downloadLog'),\n// request: (transferOptions) => ingestionApi.downloadProcessingLog(batchId, transferOptions),\n// onCompleted: () => refreshPeriods(),\n// });\n//\n// startDownload no espera a que termine quien la llama: el diálogo de confirmación se cierra en cuanto la descarga entra\n// a la lista y la persona sigue trabajando mientras baja (el log más grande tarda ~30 s). El panel (tipo Google Drive) es\n// el ÚNICO lugar que avisa: avance, lista y error con su texto del API (RFC 9457). Sin aviso aparte, para no avisar dos\n// veces lo mismo.\n//\n// Una sola lista para toda la app (un actor de módulo, como el cliente de createApiClient): la llenan varias pantallas y\n// la lee un solo panel montado en la raíz de la aplicación.\n//\n// Funciones puras del paquete, no renderizan: function (regla de la casa). No conoce el API: el archivo lo pide la\n// `request` que le pasan.\nimport {createActor} from 'xstate';\n\nimport {DOWNLOAD_STATUS, downloadsMachine} from '@/downloads/downloadsMachine.js';\nimport {saveBlob} from '@/files.js';\nimport {newIdentifier} from '@/identifiers.js';\n\nexport const downloadsActor = createActor(downloadsMachine).start();\n\n// cómo cancelar cada descarga en curso; vive fuera del contexto de la máquina porque un AbortController no es un dato\nconst abortControllerByDownloadId = new Map();\n\n/**\n * Descarga un archivo con avance en el panel, lo guarda en disco y avisa al terminar.\n *\n * @param {object} options\n * @param {string} options.label ya traducido; lo que se ve en el panel mientras todavía no se sabe el nombre del\n * archivo\n * @param {string} [options.format] el tipo de archivo, para su ícono en el panel: xlsx, csv, pdf o uno del proyecto\n * (ToolkitProvider fileTypes)\n * @param {(transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void, onLabelChange: (label: string) => void}) => Promise<{blob: Blob, fileName: string}>} options.request\n * baja el archivo y regresa {blob, fileName}. onLabelChange es para un lote de varios archivos que cuenta como una\n * sola descarga: cambia el rótulo del panel mientras baja («3 de 11: contrato.pdf»); quien no lo llame se queda con\n * `label`\n * @param {(fileName: string) => void} [options.onCompleted] p. ej. volver a pedir la tabla (si la descarga cambia algo\n * en el servidor)\n * @param {(error: unknown) => void} [options.onFailed] cuando no se pudo (el panel ya lo dice; para avisar o reportarlo)\n * @param {() => void} [options.onCanceled] cuando la persona la canceló\n * @returns {Promise<string>} id de la descarga; la promesa se cumple cuando termina (bien, mal o cancelada), nunca falla\n */\nexport async function startDownload({label, format, request, onCompleted, onFailed, onCanceled}) {\n const downloadId = newIdentifier();\n const abortController = new AbortController();\n abortControllerByDownloadId.set(downloadId, abortController);\n downloadsActor.send({type: 'STARTED', id: downloadId, label, format});\n\n try {\n const {blob, fileName} = await request({\n signal: abortController.signal,\n // la petición avisa cada pedazo que llega; total viene en null cuando el servidor arma el archivo al vuelo (sin\n // Content-Length) y el panel enseña lo que lleva en vez del porcentaje\n onDownloadProgress: (progressEvent) => downloadsActor.send({\n type: 'PROGRESSED', id: downloadId, loadedBytes: progressEvent.loaded, totalBytes: progressEvent.total,\n }),\n // un lote de varios archivos cuenta como UNA descarga: así su renglón dice en cuál va, en lugar de agregar otro\n onLabelChange: (newLabel) => downloadsActor.send({type: 'RELABELED', id: downloadId, label: newLabel}),\n });\n saveBlob(blob, fileName);\n downloadsActor.send({type: 'COMPLETED', id: downloadId, fileName, sizeBytes: blob.size});\n onCompleted?.(fileName);\n } catch (error) {\n // OJO: cancelar también llega aquí como error (el AbortError del navegador); se distingue por la señal, no por el\n // tipo de error, porque cada `request` del proyecto puede tronar con el suyo\n if (abortController.signal.aborted) {\n downloadsActor.send({type: 'CANCELED', id: downloadId});\n onCanceled?.();\n } else {\n // el mensaje ya viene traducido del API (ApiError con el detail de RFC 9457)\n downloadsActor.send({type: 'FAILED', id: downloadId, errorMessage: error?.message ?? null});\n onFailed?.(error);\n }\n } finally {\n abortControllerByDownloadId.delete(downloadId);\n }\n\n return downloadId;\n}\n\n/**\n * Cancela una descarga en curso (el panel la marca como cancelada).\n *\n * @param {string} downloadId\n */\nexport function cancelDownload(downloadId) {\n abortControllerByDownloadId.get(downloadId)?.abort();\n}\n\n/** Quita del panel todas las descargas terminadas (completadas, fallidas o canceladas). */\nexport function clearFinishedDownloads() {\n downloadsActor.send({type: 'CLEAR_FINISHED'});\n}\n\n/**\n * ¿Cuántas siguen descargando? Decide el resumen del panel y si se puede cerrar.\n *\n * @param {Array<object>} downloads\n * @returns {number}\n */\nexport function countActiveDownloads(downloads) {\n return downloads.filter((download) => download.status === DOWNLOAD_STATUS.downloading).length;\n}\n"],"names":[],"mappings":";;;;AAyBY,MAAC,iBAAiB,YAAY,gBAAgB,EAAE,MAAK;AAGjE,MAAM,8BAA8B,oBAAI,IAAG;AAoBpC,eAAe,cAAc,EAAC,OAAO,QAAQ,SAAS,aAAa,UAAU,WAAU,GAAG;AAC7F,QAAM,aAAa,cAAa;AAChC,QAAM,kBAAkB,IAAI,gBAAe;AAC3C,8BAA4B,IAAI,YAAY,eAAe;AAC3D,iBAAe,KAAK,EAAC,MAAM,WAAW,IAAI,YAAY,OAAO,OAAM,CAAC;AAEpE,MAAI;AACA,UAAM,EAAC,MAAM,SAAQ,IAAI,MAAM,QAAQ;AAAA,MACnC,QAAQ,gBAAgB;AAAA;AAAA;AAAA,MAGxB,oBAAoB,CAAC,kBAAkB,eAAe,KAAK;AAAA,QACvD,MAAM;AAAA,QAAc,IAAI;AAAA,QAAY,aAAa,cAAc;AAAA,QAAQ,YAAY,cAAc;AAAA,MACjH,CAAa;AAAA;AAAA,MAED,eAAe,CAAC,aAAa,eAAe,KAAK,EAAC,MAAM,aAAa,IAAI,YAAY,OAAO,SAAQ,CAAC;AAAA,IACjH,CAAS;AACD,aAAS,MAAM,QAAQ;AACvB,mBAAe,KAAK,EAAC,MAAM,aAAa,IAAI,YAAY,UAAU,WAAW,KAAK,KAAI,CAAC;AACvF,kBAAc,QAAQ;AAAA,EAC1B,SAAS,OAAO;AAGZ,QAAI,gBAAgB,OAAO,SAAS;AAChC,qBAAe,KAAK,EAAC,MAAM,YAAY,IAAI,WAAU,CAAC;AACtD,mBAAU;AAAA,IACd,OAAO;AAEH,qBAAe,KAAK,EAAC,MAAM,UAAU,IAAI,YAAY,cAAc,OAAO,WAAW,KAAI,CAAC;AAC1F,iBAAW,KAAK;AAAA,IACpB;AAAA,EACJ,UAAC;AACG,gCAA4B,OAAO,UAAU;AAAA,EACjD;AAEA,SAAO;AACX;AAOO,SAAS,eAAe,YAAY;AACvC,8BAA4B,IAAI,UAAU,GAAG,MAAK;AACtD;AAGO,SAAS,yBAAyB;AACrC,iBAAe,KAAK,EAAC,MAAM,iBAAgB,CAAC;AAChD;AAQO,SAAS,qBAAqB,WAAW;AAC5C,SAAO,UAAU,OAAO,CAAC,aAAa,SAAS,WAAW,gBAAgB,WAAW,EAAE;AAC3F;"}
|
|
@@ -42,6 +42,14 @@ const downloadsMachine = setup({}).createMachine({
|
|
|
42
42
|
})
|
|
43
43
|
})
|
|
44
44
|
},
|
|
45
|
+
// el rótulo cambia mientras baja: un lote de varios archivos que cuenta como UNA descarga dice en cuál va
|
|
46
|
+
// («3 de 11: contrato.pdf»). Igual que el avance, solo a la que sigue descargando: un aviso que llega tarde no le
|
|
47
|
+
// cambia el nombre a una que ya terminó
|
|
48
|
+
RELABELED: {
|
|
49
|
+
actions: assign({
|
|
50
|
+
downloads: ({ context, event }) => updateActiveDownload(context.downloads, event.id, { label: event.label })
|
|
51
|
+
})
|
|
52
|
+
},
|
|
45
53
|
COMPLETED: {
|
|
46
54
|
actions: assign({
|
|
47
55
|
// con el tamaño del archivo ya guardado: un archivo armado en el navegador (CSV, JSON) no avisa avance y el
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"downloadsMachine.js","sources":["../../src/downloads/downloadsMachine.js"],"sourcesContent":["// Lista de descargas de la sesión (el «administrador de descargas», sin Redux).\n//\n// Cada descarga vive así:\n//\n// STARTED ──▶ downloading ──PROGRESSED──▶ downloading\n// │\n// ├── COMPLETED ──▶ completed ─┐\n// ├── FAILED ─────▶ failed ────┼── CLEAR_FINISHED ──▶ (sale de la lista)\n// └── CANCELED ───▶ canceled ──┘\n//\n// Por qué una máquina y no un useState: la lista la alimentan varias pantallas a la vez (quien descarga) y la lee un\n// solo panel (DownloadManager); una descarga que ya terminó no puede volver a «descargando» por un aviso de progreso\n// que llegó tarde. Lo prueba downloadsMachine.test.js.\n//\n// Pura: solo importa xstate. Quien descarga de verdad es startDownload (downloadQueue.js); el paquete no conoce el API.\nimport {assign, setup} from 'xstate';\n\n/** Estatus de una descarga; son los que pinta el panel (con su texto en common:downloads.statuses). */\nexport const DOWNLOAD_STATUS = Object.freeze({\n downloading: 'downloading',\n completed: 'completed',\n failed: 'failed',\n canceled: 'canceled',\n});\n\n/**\n * Cambia una sola descarga de la lista, y solo si sigue descargando (las terminadas ya no cambian).\n *\n * @param {Array<object>} downloads\n * @param {string} downloadId\n * @param {object} changes\n * @returns {Array<object>}\n */\nfunction updateActiveDownload(downloads, downloadId, changes) {\n return downloads.map((download) => (\n download.id === downloadId && download.status === DOWNLOAD_STATUS.downloading ? {...download, ...changes} : download\n ));\n}\n\nexport const downloadsMachine = setup({}).createMachine({\n id: 'downloads',\n context: {\n // la más nueva primero\n downloads: [],\n },\n on: {\n STARTED: {\n actions: assign({\n downloads: ({context, event}) => [\n {\n id: event.id,\n label: event.label,\n // xlsx, csv o pdf: el panel pinta el ícono del tipo de archivo (null si quien descarga no lo dice)\n format: event.format ?? null,\n fileName: null,\n status: DOWNLOAD_STATUS.downloading,\n loadedBytes: 0,\n totalBytes: null,\n errorMessage: null,\n },\n ...context.downloads,\n ],\n }),\n },\n PROGRESSED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n loadedBytes: event.loadedBytes,\n // un archivo que se arma al vuelo (el log) no dice su tamaño: se queda en null y la barra no tiene fin\n totalBytes: event.totalBytes ?? null,\n }),\n }),\n },\n COMPLETED: {\n actions: assign({\n // con el tamaño del archivo ya guardado: un archivo armado en el navegador (CSV, JSON) no avisa avance y el\n // panel decía «0 bytes» de un archivo con datos\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n status: DOWNLOAD_STATUS.completed,\n fileName: event.fileName,\n ...(typeof event.sizeBytes === 'number' && {loadedBytes: event.sizeBytes, totalBytes: event.sizeBytes}),\n }),\n }),\n },\n FAILED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n status: DOWNLOAD_STATUS.failed,\n errorMessage: event.errorMessage ?? null,\n }),\n }),\n },\n CANCELED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {status: DOWNLOAD_STATUS.canceled}),\n }),\n },\n CLEAR_FINISHED: {\n actions: assign({\n downloads: ({context}) => context.downloads.filter((download) => download.status === DOWNLOAD_STATUS.downloading),\n }),\n },\n },\n});\n"],"names":[],"mappings":";AAkBY,MAAC,kBAAkB,OAAO,OAAO;AAAA,EACzC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU;AACd,CAAC;AAUD,SAAS,qBAAqB,WAAW,YAAY,SAAS;AAC1D,SAAO,UAAU,IAAI,CAAC,aAClB,SAAS,OAAO,cAAc,SAAS,WAAW,gBAAgB,cAAc,EAAC,GAAG,UAAU,GAAG,QAAO,IAAI,QAC/G;AACL;AAEY,MAAC,mBAAmB,MAAM,EAAE,EAAE,cAAc;AAAA,EACpD,IAAI;AAAA,EACJ,SAAS;AAAA;AAAA,IAEL,WAAW,CAAA;AAAA,EACnB;AAAA,EACI,IAAI;AAAA,IACA,SAAS;AAAA,MACL,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM;AAAA,UAC7B;AAAA,YACI,IAAI,MAAM;AAAA,YACV,OAAO,MAAM;AAAA;AAAA,YAEb,QAAQ,MAAM,UAAU;AAAA,YACxB,UAAU;AAAA,YACV,QAAQ,gBAAgB;AAAA,YACxB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,cAAc;AAAA,UACtC;AAAA,UACoB,GAAG,QAAQ;AAAA,QAC/B;AAAA,MACA,CAAa;AAAA,IACb;AAAA,IACQ,YAAY;AAAA,MACR,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,aAAa,MAAM;AAAA;AAAA,UAEnB,YAAY,MAAM,cAAc;AAAA,QACpD,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA,IACQ,WAAW;AAAA,MACP,SAAS,OAAO;AAAA;AAAA;AAAA,QAGZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,QAAQ,gBAAgB;AAAA,UACxB,UAAU,MAAM;AAAA,UAChB,GAAI,OAAO,MAAM,cAAc,YAAY,EAAC,aAAa,MAAM,WAAW,YAAY,MAAM,UAAS;AAAA,QACzH,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA,IACQ,QAAQ;AAAA,MACJ,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,QAAQ,gBAAgB;AAAA,UACxB,cAAc,MAAM,gBAAgB;AAAA,QACxD,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA,IACQ,UAAU;AAAA,MACN,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI,EAAC,QAAQ,gBAAgB,SAAQ,CAAC;AAAA,MACrI,CAAa;AAAA,IACb;AAAA,IACQ,gBAAgB;AAAA,MACZ,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,QAAO,MAAM,QAAQ,UAAU,OAAO,CAAC,aAAa,SAAS,WAAW,gBAAgB,WAAW;AAAA,MAChI,CAAa;AAAA,IACb;AAAA,EACA;AACA,CAAC;"}
|
|
1
|
+
{"version":3,"file":"downloadsMachine.js","sources":["../../src/downloads/downloadsMachine.js"],"sourcesContent":["// Lista de descargas de la sesión (el «administrador de descargas», sin Redux).\n//\n// Cada descarga vive así:\n//\n// STARTED ──▶ downloading ──PROGRESSED / RELABELED──▶ downloading\n// │\n// ├── COMPLETED ──▶ completed ─┐\n// ├── FAILED ─────▶ failed ────┼── CLEAR_FINISHED ──▶ (sale de la lista)\n// └── CANCELED ───▶ canceled ──┘\n//\n// Por qué una máquina y no un useState: la lista la alimentan varias pantallas a la vez (quien descarga) y la lee un\n// solo panel (DownloadManager); una descarga que ya terminó no puede volver a «descargando» por un aviso de progreso\n// que llegó tarde. Lo prueba downloadsMachine.test.js.\n//\n// Pura: solo importa xstate. Quien descarga de verdad es startDownload (downloadQueue.js); el paquete no conoce el API.\nimport {assign, setup} from 'xstate';\n\n/** Estatus de una descarga; son los que pinta el panel (con su texto en common:downloads.statuses). */\nexport const DOWNLOAD_STATUS = Object.freeze({\n downloading: 'downloading',\n completed: 'completed',\n failed: 'failed',\n canceled: 'canceled',\n});\n\n/**\n * Cambia una sola descarga de la lista, y solo si sigue descargando (las terminadas ya no cambian).\n *\n * @param {Array<object>} downloads\n * @param {string} downloadId\n * @param {object} changes\n * @returns {Array<object>}\n */\nfunction updateActiveDownload(downloads, downloadId, changes) {\n return downloads.map((download) => (\n download.id === downloadId && download.status === DOWNLOAD_STATUS.downloading ? {...download, ...changes} : download\n ));\n}\n\nexport const downloadsMachine = setup({}).createMachine({\n id: 'downloads',\n context: {\n // la más nueva primero\n downloads: [],\n },\n on: {\n STARTED: {\n actions: assign({\n downloads: ({context, event}) => [\n {\n id: event.id,\n label: event.label,\n // xlsx, csv o pdf: el panel pinta el ícono del tipo de archivo (null si quien descarga no lo dice)\n format: event.format ?? null,\n fileName: null,\n status: DOWNLOAD_STATUS.downloading,\n loadedBytes: 0,\n totalBytes: null,\n errorMessage: null,\n },\n ...context.downloads,\n ],\n }),\n },\n PROGRESSED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n loadedBytes: event.loadedBytes,\n // un archivo que se arma al vuelo (el log) no dice su tamaño: se queda en null y la barra no tiene fin\n totalBytes: event.totalBytes ?? null,\n }),\n }),\n },\n // el rótulo cambia mientras baja: un lote de varios archivos que cuenta como UNA descarga dice en cuál va\n // («3 de 11: contrato.pdf»). Igual que el avance, solo a la que sigue descargando: un aviso que llega tarde no le\n // cambia el nombre a una que ya terminó\n RELABELED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {label: event.label}),\n }),\n },\n COMPLETED: {\n actions: assign({\n // con el tamaño del archivo ya guardado: un archivo armado en el navegador (CSV, JSON) no avisa avance y el\n // panel decía «0 bytes» de un archivo con datos\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n status: DOWNLOAD_STATUS.completed,\n fileName: event.fileName,\n ...(typeof event.sizeBytes === 'number' && {loadedBytes: event.sizeBytes, totalBytes: event.sizeBytes}),\n }),\n }),\n },\n FAILED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {\n status: DOWNLOAD_STATUS.failed,\n errorMessage: event.errorMessage ?? null,\n }),\n }),\n },\n CANCELED: {\n actions: assign({\n downloads: ({context, event}) => updateActiveDownload(context.downloads, event.id, {status: DOWNLOAD_STATUS.canceled}),\n }),\n },\n CLEAR_FINISHED: {\n actions: assign({\n downloads: ({context}) => context.downloads.filter((download) => download.status === DOWNLOAD_STATUS.downloading),\n }),\n },\n },\n});\n"],"names":[],"mappings":";AAkBY,MAAC,kBAAkB,OAAO,OAAO;AAAA,EACzC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU;AACd,CAAC;AAUD,SAAS,qBAAqB,WAAW,YAAY,SAAS;AAC1D,SAAO,UAAU,IAAI,CAAC,aAClB,SAAS,OAAO,cAAc,SAAS,WAAW,gBAAgB,cAAc,EAAC,GAAG,UAAU,GAAG,QAAO,IAAI,QAC/G;AACL;AAEY,MAAC,mBAAmB,MAAM,EAAE,EAAE,cAAc;AAAA,EACpD,IAAI;AAAA,EACJ,SAAS;AAAA;AAAA,IAEL,WAAW,CAAA;AAAA,EACnB;AAAA,EACI,IAAI;AAAA,IACA,SAAS;AAAA,MACL,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM;AAAA,UAC7B;AAAA,YACI,IAAI,MAAM;AAAA,YACV,OAAO,MAAM;AAAA;AAAA,YAEb,QAAQ,MAAM,UAAU;AAAA,YACxB,UAAU;AAAA,YACV,QAAQ,gBAAgB;AAAA,YACxB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,cAAc;AAAA,UACtC;AAAA,UACoB,GAAG,QAAQ;AAAA,QAC/B;AAAA,MACA,CAAa;AAAA,IACb;AAAA,IACQ,YAAY;AAAA,MACR,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,aAAa,MAAM;AAAA;AAAA,UAEnB,YAAY,MAAM,cAAc;AAAA,QACpD,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA;AAAA;AAAA;AAAA,IAIQ,WAAW;AAAA,MACP,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI,EAAC,OAAO,MAAM,MAAK,CAAC;AAAA,MACvH,CAAa;AAAA,IACb;AAAA,IACQ,WAAW;AAAA,MACP,SAAS,OAAO;AAAA;AAAA;AAAA,QAGZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,QAAQ,gBAAgB;AAAA,UACxB,UAAU,MAAM;AAAA,UAChB,GAAI,OAAO,MAAM,cAAc,YAAY,EAAC,aAAa,MAAM,WAAW,YAAY,MAAM,UAAS;AAAA,QACzH,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA,IACQ,QAAQ;AAAA,MACJ,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI;AAAA,UAC/E,QAAQ,gBAAgB;AAAA,UACxB,cAAc,MAAM,gBAAgB;AAAA,QACxD,CAAiB;AAAA,MACjB,CAAa;AAAA,IACb;AAAA,IACQ,UAAU;AAAA,MACN,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,SAAS,MAAK,MAAM,qBAAqB,QAAQ,WAAW,MAAM,IAAI,EAAC,QAAQ,gBAAgB,SAAQ,CAAC;AAAA,MACrI,CAAa;AAAA,IACb;AAAA,IACQ,gBAAgB;AAAA,MACZ,SAAS,OAAO;AAAA,QACZ,WAAW,CAAC,EAAC,QAAO,MAAM,QAAQ,UAAU,OAAO,CAAC,aAAa,SAAS,WAAW,gBAAgB,WAAW;AAAA,MAChI,CAAa;AAAA,IACb;AAAA,EACA;AACA,CAAC;"}
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import { parseMarkedText } from "./ui/labels/markedTextPieces.js";
|
|
|
34
34
|
import { CSS_TOKENS, cssTokenDeclarationsOf, cssTokenOf } from "./theme/cssTokens.js";
|
|
35
35
|
import { FIELD_TONES, GLASS_INSET_PIXELS, GLASS_MENU_RADIUS_PIXELS, GLASS_OPACITY, GLASS_PANEL_RADIUS_PIXELS, accentTextStylesOf, ambientBackgroundOf, canvasFieldStylesOf, fieldPaletteOf, fieldWellStylesOf, fixedBackgroundLayerOf, glassDropOf, glassStylesOf, glassSurfaceOf, isGlassTheme, materialBackgroundOf, materialStylesOf, materialSurfaceOf, scrollEdgeStylesOf } from "./theme/glassSurface.js";
|
|
36
36
|
import { AppLayout, MENU_POSITION } from "./appShell/AppLayout.js";
|
|
37
|
+
import { MENU_LABEL_OVERFLOW } from "./appShell/SideMenuEntry.js";
|
|
37
38
|
import { AuthLayout } from "./appShell/AuthLayout.js";
|
|
38
39
|
import { default as default12 } from "./appShell/BarActions.js";
|
|
39
40
|
import { default as default13 } from "./appShell/BarActionButton.js";
|
|
@@ -274,6 +275,7 @@ export {
|
|
|
274
275
|
LANGUAGES,
|
|
275
276
|
default45 as LanguageToggle,
|
|
276
277
|
MAIN_CONTENT_ID,
|
|
278
|
+
MENU_LABEL_OVERFLOW,
|
|
277
279
|
MENU_POSITION,
|
|
278
280
|
MINIMAL_BACKGROUND,
|
|
279
281
|
MINIMAL_TONES,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -36,6 +36,7 @@ export declare const AppLayout: (props: {
|
|
|
36
36
|
defaultCollapsed?: boolean;
|
|
37
37
|
minimizeTabBarOnScroll?: boolean;
|
|
38
38
|
contentMaxWidth?: number | string;
|
|
39
|
+
menuLabelOverflow?: string;
|
|
39
40
|
onMenuCollapse?: (collapsed: boolean) => void;
|
|
40
41
|
onMobileMenuChange?: (isOpen: boolean) => void;
|
|
41
42
|
onMobileTabsChange?: (keys: string[] | null) => void;
|
|
@@ -5,6 +5,7 @@ declare const MobileTabBar: (props: {
|
|
|
5
5
|
account?: import('react').ReactNode;
|
|
6
6
|
moreFooter?: import('react').ReactNode;
|
|
7
7
|
minimizeOnScroll?: boolean;
|
|
8
|
+
labelOverflow?: string;
|
|
8
9
|
onPinnedTabsChange?: (keys: string[] | null) => void;
|
|
9
10
|
} & {
|
|
10
11
|
slots?: Record<string, import('react').ElementType>;
|
|
@@ -16,6 +16,7 @@ declare const SideMenu: (props: {
|
|
|
16
16
|
menuBadges?: Record<string, number>;
|
|
17
17
|
railTop?: import('react').ReactNode;
|
|
18
18
|
railBottom?: import('react').ReactNode;
|
|
19
|
+
labelOverflow?: string;
|
|
19
20
|
} & {
|
|
20
21
|
slots?: Record<string, import('react').ElementType>;
|
|
21
22
|
slotProps?: Record<string, object | ((partProps: object) => object)>;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
/** Qué hace el menú lateral con un nombre que no cabe en su renglón. */
|
|
2
|
+
export declare const MENU_LABEL_OVERFLOW: Readonly<{
|
|
3
|
+
ellipsis: "ellipsis";
|
|
4
|
+
wrap: "wrap";
|
|
5
|
+
}>;
|
|
1
6
|
declare const SideMenuEntry: (props: {
|
|
2
7
|
menuItem: object;
|
|
3
8
|
activeItem: object | null;
|
|
4
9
|
collapsed: boolean;
|
|
5
10
|
onNavigate?: () => void;
|
|
6
11
|
menuBadges?: Record<string, number>;
|
|
12
|
+
labelOverflow?: string;
|
|
7
13
|
depth?: number;
|
|
8
14
|
} & {
|
|
9
15
|
slots?: Record<string, import('react').ElementType>;
|
|
@@ -9,7 +9,10 @@ export declare const downloadsActor: import("xstate").Actor<import("xstate").Sta
|
|
|
9
9
|
* archivo
|
|
10
10
|
* @param {string} [options.format] el tipo de archivo, para su ícono en el panel: xlsx, csv, pdf o uno del proyecto
|
|
11
11
|
* (ToolkitProvider fileTypes)
|
|
12
|
-
* @param {(transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} options.request
|
|
12
|
+
* @param {(transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void, onLabelChange: (label: string) => void}) => Promise<{blob: Blob, fileName: string}>} options.request
|
|
13
|
+
* baja el archivo y regresa {blob, fileName}. onLabelChange es para un lote de varios archivos que cuenta como una
|
|
14
|
+
* sola descarga: cambia el rótulo del panel mientras baja («3 de 11: contrato.pdf»); quien no lo llame se queda con
|
|
15
|
+
* `label`
|
|
13
16
|
* @param {(fileName: string) => void} [options.onCompleted] p. ej. volver a pedir la tabla (si la descarga cambia algo
|
|
14
17
|
* en el servidor)
|
|
15
18
|
* @param {(error: unknown) => void} [options.onFailed] cuando no se pudo (el panel ya lo dice; para avisar o reportarlo)
|
|
@@ -25,6 +28,7 @@ export declare function startDownload({ label, format, request, onCompleted, onF
|
|
|
25
28
|
loaded: number;
|
|
26
29
|
total?: number | null;
|
|
27
30
|
}) => void;
|
|
31
|
+
onLabelChange: (label: string) => void;
|
|
28
32
|
}) => Promise<{
|
|
29
33
|
blob: Blob;
|
|
30
34
|
fileName: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { parseMarkedText } from './ui/labels/markedTextPieces.js';
|
|
|
24
24
|
export { CSS_TOKENS, cssTokenDeclarationsOf, cssTokenOf } from './theme/cssTokens.js';
|
|
25
25
|
export { GLASS_INSET_PIXELS, GLASS_MENU_RADIUS_PIXELS, GLASS_OPACITY, GLASS_PANEL_RADIUS_PIXELS, ambientBackgroundOf, glassDropOf, glassStylesOf, glassSurfaceOf, FIELD_TONES, accentTextStylesOf, canvasFieldStylesOf, fieldPaletteOf, fieldWellStylesOf, fixedBackgroundLayerOf, isGlassTheme, materialBackgroundOf, materialStylesOf, materialSurfaceOf, scrollEdgeStylesOf, } from './theme/glassSurface.js';
|
|
26
26
|
export { AppLayout, MENU_POSITION } from './appShell/AppLayout.js';
|
|
27
|
+
export { MENU_LABEL_OVERFLOW } from './appShell/SideMenuEntry.js';
|
|
27
28
|
export { AuthLayout } from './appShell/AuthLayout.js';
|
|
28
29
|
export { default as BarActions } from './appShell/BarActions.js';
|
|
29
30
|
export { default as BarActionButton } from './appShell/BarActionButton.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackcraftmx/calcita",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Componentes y utilidades de React con MUI para arrancar proyectos rápido: tabla de servidor, diálogos, avisos, descargas, formularios, formatos y reportes en Excel.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"author": "calcifux",
|