@stackcraftmx/calcita 0.30.0 → 0.30.1

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 CHANGED
@@ -404,7 +404,7 @@ valor de siempre de respaldo: sin ella se ve igual.
404
404
  | --- | --- | --- |
405
405
  | `--calcita-panel-radius` | las esquinas de lo que flota grande y las tarjetas grandes (menú lateral, diálogos, la sección de un formulario) | 20px (16px en el minimalista) |
406
406
  | `--calcita-menu-radius` | las esquinas de menús, listas desplegables, globos y avisos | 12px |
407
- | `--calcita-field-radius` | las esquinas de los campos de captura | 12px (10px en el minimalista) |
407
+ | `--calcita-field-radius` | las esquinas de los campos de captura | 12px (10px en el minimalista; cápsula dentro de `FormSection`) |
408
408
  | `--calcita-glass-blur` | el desenfoque del vidrio | 20px |
409
409
  | `--calcita-glass-saturate` | cuánto aviva el vidrio los colores de atrás | 180% |
410
410
 
@@ -528,12 +528,18 @@ un `<div>` de la página vieja, con su propio `ToolkitProvider`.
528
528
  salen también sin sesión, detrás del engrane («Regresar al sistema»).
529
529
  - **Su `sx` gana:** lo que el proyecto pinte con `sx` les gana a los ajustes de Calcita en claro y en oscuro.
530
530
  - **Su registro:** `addLogSink` manda cada anotación también a su reporte (Sentry, Datadog, su endpoint).
531
- - **Si parte el código en archivos** (`manualChunks`): Calcita en el suyo. Junto con lo demás de `node_modules` arma un
532
- círculo con MUI y la app truena al abrir («reading 'isValidElementType'»). Su FilePond se queda fuera para seguir bajando
533
- solo donde se usa:
531
+ - **Mejor no parta el código a mano** (`manualChunks`): con las pantallas cargadas al abrirse (rutas perezosas), Vite arma
532
+ solo los archivos que se comparten y lo pesado de una pantalla (la tabla de MUI X, FilePond, formik y yup) baja solo ahí.
533
+ Medido en un sistema real: con el reparto a mano bajaban 1,171 kB en cada pantalla; sin él, 971 kB, ya con su tabla y su
534
+ formulario nuevos. Asignar un paquete a un archivo fijo le gana a la carga perezosa y lo baja en todas las pantallas.
535
+ Si aun así reparte a mano, Calcita va en su propio archivo (junto con lo demás de `node_modules` arma un círculo con MUI y
536
+ la app truena al abrir: «reading 'isValidElementType'») y lo que solo usan algunas pantallas se queda sin asignar:
534
537
 
535
538
  ```js
536
- if (id.includes('@stackcraftmx/calcita') && !id.includes('/filepond')) return 'calcita';
539
+ const LAZY_ONLY = ['filepond', '/@mui/x-', '/nuqs/', '/formik/', '/yup/', '/@stackcraftmx/calcita/dist/DataTable/',
540
+ '/@stackcraftmx/calcita/dist/table.js', '/@stackcraftmx/calcita/dist/forms'];
541
+ if (LAZY_ONLY.some((part) => id.includes(part))) return undefined; // que siga a la pantalla que lo usa
542
+ if (id.includes('@stackcraftmx/calcita')) return 'calcita';
537
543
  ```
538
544
 
539
545
  ```jsx
@@ -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} from '@/theme/themePieces.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} [props.wordmark] el logo ancho, la marca con su nombre en una sola imagen (como el de muchas\n * empresas): 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 */\nexport const AppLayout = (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} = usePieceProps(props, THEME_NAME);\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"],"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} from '@/theme/themePieces.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 */\nexport const AppLayout = (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} = usePieceProps(props, THEME_NAME);\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"],"names":["theme"],"mappings":"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"}
@@ -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} from '@/theme/themePieces.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} [props.wordmark] el logo ancho (en lugar del logo y el nombre arriba)\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 */\nexport default function SideMenu(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} = usePieceProps(props, THEME_NAME);\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"],"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} from '@/theme/themePieces.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 */\nexport default function SideMenu(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} = usePieceProps(props, THEME_NAME);\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"],"names":["theme"],"mappings":"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"}
@@ -1,15 +1,30 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import { isValidElement } from "react";
3
4
  import Box from "@mui/material/Box";
4
5
  import { piecePart, usePieceProps } from "../theme/themePieces.js";
5
6
  const THEME_NAME = "CalcitaWordmark";
6
7
  const WordmarkRoot = piecePart(THEME_NAME, "Root", Box);
8
+ const WordmarkMode = piecePart(THEME_NAME, "Mode", "span");
7
9
  const WORDMARK_HEIGHT_PIXELS = 24;
10
+ function isWordmarkPair(wordmark) {
11
+ return wordmark !== null && typeof wordmark === "object" && !isValidElement(wordmark) && !Array.isArray(wordmark) && ("light" in wordmark || "dark" in wordmark);
12
+ }
8
13
  function Wordmark(props) {
9
14
  const { children } = usePieceProps(props, THEME_NAME);
10
- return /* @__PURE__ */ jsx(WordmarkRoot, { ownSx: { height: WORDMARK_HEIGHT_PIXELS, display: "flex", alignItems: "center", flex: "0 0 auto", "& > img, & > svg": { height: "100%", width: "auto", display: "block" } }, children });
15
+ return /* @__PURE__ */ jsx(WordmarkRoot, { ownSx: {
16
+ height: WORDMARK_HEIGHT_PIXELS,
17
+ display: "flex",
18
+ alignItems: "center",
19
+ flex: "0 0 auto",
20
+ "& :where(img, svg)": { height: "100%", width: "auto", display: "block" }
21
+ }, children: isWordmarkPair(children) ? /* @__PURE__ */ jsxs(Fragment, { children: [
22
+ /* @__PURE__ */ jsx(WordmarkMode, { ownSx: (theme) => ({ display: "contents", ...theme.applyStyles("dark", { display: "none" }) }), children: children.light ?? children.dark }),
23
+ /* @__PURE__ */ jsx(WordmarkMode, { ownSx: (theme) => ({ display: "none", ...theme.applyStyles("dark", { display: "contents" }) }), children: children.dark ?? children.light })
24
+ ] }) : children });
11
25
  }
12
26
  export {
13
- Wordmark as default
27
+ Wordmark as default,
28
+ isWordmarkPair
14
29
  };
15
30
  //# sourceMappingURL=Wordmark.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Wordmark.js","sources":["../../src/appShell/Wordmark.jsx"],"sourcesContent":["// El logo ancho de la app (la marca con su nombre en una sola imagen, como el de muchas empresas) en la barra y arriba del\n// menú del teléfono: a 24 px de alto y lo ancho que pida, sin deformarse. Una imagen o un svg sueltos toman ese alto solos.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaWordmark con defaultProps y\n// styleOverrides de sus partes: root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Box from '@mui/material/Box';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaWordmark)\nconst THEME_NAME = 'CalcitaWordmark';\nconst WordmarkRoot = piecePart(THEME_NAME, 'Root', Box);\n\n// el alto del logo ancho: el de la letra del nombre con su renglón, para que la barra no crezca\nconst WORDMARK_HEIGHT_PIXELS = 24;\n\n/**\n * @param {{children: import('react').ReactNode}} props el logo (una <img>, un <svg> o lo que pinte el proyecto)\n */\nexport default function Wordmark(props) {\n // lo que el proyecto puso en su tema (components.CalcitaWordmark.defaultProps) y encima lo que llega aquí\n const {children} = usePieceProps(props, THEME_NAME);\n return (\n <WordmarkRoot ownSx={{height: WORDMARK_HEIGHT_PIXELS, display: 'flex', alignItems: 'center', flex: '0 0 auto', '& > img, & > svg': {height: '100%', width: 'auto', display: 'block'}}}>\n {children}\n </WordmarkRoot>\n );\n}\n"],"names":[],"mappings":";;;;AAWA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,GAAG;AAGtD,MAAM,yBAAyB;AAK/B,SAAwB,SAAS,OAAO;AAEpC,QAAM,EAAC,SAAA,IAAY,cAAc,OAAO,UAAU;AAClD,SACI,oBAAC,gBAAa,OAAO,EAAC,QAAQ,wBAAwB,SAAS,QAAQ,YAAY,UAAU,MAAM,YAAY,oBAAoB,EAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,QAAA,KACvK,UACL;AAER;"}
1
+ {"version":3,"file":"Wordmark.js","sources":["../../src/appShell/Wordmark.jsx"],"sourcesContent":["// El logo ancho de la app (la marca con su nombre en una sola imagen, como el de muchas empresas) en la barra y arriba del\n// menú del teléfono: a 24 px de alto y lo ancho que pida, sin deformarse. Una imagen o un svg toman ese alto solos.\n//\n// Con una imagen para cada modo ({light: <img …/>, dark: <img …/>}: el de color en claro y el blanco en oscuro) se pintan\n// las dos y el modo decide cuál se ve, sin parpadeo al cambiar ni al cargar.\n//\n// OJO, trampa pagada (la prueba en un sistema que ya existía): la regla de la imagen («height 100 %, display block») iba\n// como «& > img» y le ganaba al sx de la imagen del proyecto; quien escondía una de sus dos imágenes con CSS no podía. Ahora\n// va con :where(), que no suma peso: lo que el proyecto le ponga a su imagen gana.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaWordmark con defaultProps y\n// styleOverrides de sus partes: root y mode (la imagen de cada modo).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {isValidElement} from 'react';\nimport Box from '@mui/material/Box';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaWordmark)\nconst THEME_NAME = 'CalcitaWordmark';\nconst WordmarkRoot = piecePart(THEME_NAME, 'Root', Box);\nconst WordmarkMode = piecePart(THEME_NAME, 'Mode', 'span');\n\n// el alto del logo ancho: el de la letra del nombre con su renglón, para que la barra no crezca\nconst WORDMARK_HEIGHT_PIXELS = 24;\n\n/**\n * ¿Llegó una imagen para cada modo ({light, dark}) y no un logo solo?\n *\n * @param {unknown} wordmark\n * @returns {boolean}\n */\nexport function isWordmarkPair(wordmark) {\n return wordmark !== null && typeof wordmark === 'object' && !isValidElement(wordmark) && !Array.isArray(wordmark)\n && ('light' in wordmark || 'dark' in wordmark);\n}\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} props.children el\n * logo (una <img>, un <svg> o lo que pinte el proyecto) o uno para cada modo: {light, dark}\n */\nexport default function Wordmark(props) {\n // lo que el proyecto puso en su tema (components.CalcitaWordmark.defaultProps) y encima lo que llega aquí\n const {children} = usePieceProps(props, THEME_NAME);\n return (\n <WordmarkRoot ownSx={{\n height: WORDMARK_HEIGHT_PIXELS, display: 'flex', alignItems: 'center', flex: '0 0 auto',\n '& :where(img, svg)': {height: '100%', width: 'auto', display: 'block'},\n }}>\n {isWordmarkPair(children) ? (\n <>\n {/* las dos se pintan y el modo decide cuál se ve: display contents deja a la imagen tomar el alto */}\n <WordmarkMode ownSx={(theme) => ({display: 'contents', ...theme.applyStyles('dark', {display: 'none'})})}>{children.light ?? children.dark}</WordmarkMode>\n <WordmarkMode ownSx={(theme) => ({display: 'none', ...theme.applyStyles('dark', {display: 'contents'})})}>{children.dark ?? children.light}</WordmarkMode>\n </>\n ) : children}\n </WordmarkRoot>\n );\n}\n"],"names":[],"mappings":";;;;;AAmBA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,GAAG;AACtD,MAAM,eAAe,UAAU,YAAY,QAAQ,MAAM;AAGzD,MAAM,yBAAyB;AAQxB,SAAS,eAAe,UAAU;AACrC,SAAO,aAAa,QAAQ,OAAO,aAAa,YAAY,CAAC,eAAe,QAAQ,KAAK,CAAC,MAAM,QAAQ,QAAQ,MACxG,WAAW,YAAY,UAAU;AAC7C;AAOA,SAAwB,SAAS,OAAO;AAEpC,QAAM,EAAC,SAAA,IAAY,cAAc,OAAO,UAAU;AAClD,SACI,oBAAC,gBAAa,OAAO;AAAA,IACjB,QAAQ;AAAA,IAAwB,SAAS;AAAA,IAAQ,YAAY;AAAA,IAAU,MAAM;AAAA,IAC7E,sBAAsB,EAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,QAAA;AAAA,EAAO,GAErE,UAAA,eAAe,QAAQ,IACpB,qBAAA,UAAA,EAEI,UAAA;AAAA,IAAA,oBAAC,cAAA,EAAa,OAAO,CAAC,WAAW,EAAC,SAAS,YAAY,GAAG,MAAM,YAAY,QAAQ,EAAC,SAAS,QAAO,MAAM,UAAA,SAAS,SAAS,SAAS,MAAK;AAAA,IAC3I,oBAAC,gBAAa,OAAO,CAAC,WAAW,EAAC,SAAS,QAAQ,GAAG,MAAM,YAAY,QAAQ,EAAC,SAAS,WAAA,CAAW,MAAM,UAAA,SAAS,QAAQ,SAAS,MAAA,CAAM;AAAA,EAAA,EAAA,CAC/I,IACA,UACR;AAER;"}
@@ -212,8 +212,13 @@ function fieldPaletteOf(theme) {
212
212
  function fieldEdgeOf(color) {
213
213
  return `inset 0 0 0 1px ${color}`;
214
214
  }
215
+ const FORM_ROW_FIELD_RADIUS = "999px";
216
+ function pixelsOf(radius) {
217
+ return typeof radius === "number" ? `${radius}px` : radius;
218
+ }
215
219
  function fieldRadiusOf(theme, lookRadius) {
216
- return theme.toolkit?.fields?.radius ?? cssTokenOf("fieldRadius", `${lookRadius}px`);
220
+ const projectRadius = theme.toolkit?.fields?.radius;
221
+ return projectRadius !== void 0 && projectRadius !== null ? pixelsOf(projectRadius) : cssTokenOf("fieldRadius", pixelsOf(lookRadius));
217
222
  }
218
223
  function fieldWellStylesOf(theme, { radius = FIELD_WELL_RADIUS_PIXELS } = {}) {
219
224
  const base = { backgroundImage: "none", backdropFilter: "none", WebkitBackdropFilter: "none", borderRadius: fieldRadiusOf(theme, radius) };
@@ -254,7 +259,7 @@ function fieldWellFromVariablesOf(theme) {
254
259
  backgroundImage: "none",
255
260
  backdropFilter: "none",
256
261
  WebkitBackdropFilter: "none",
257
- borderRadius: fieldRadiusOf(theme, FIELD_WELL_RADIUS_PIXELS),
262
+ borderRadius: fieldRadiusOf(theme, FORM_ROW_FIELD_RADIUS),
258
263
  backgroundColor: "var(--calcita-field-fill)",
259
264
  boxShadow: "var(--calcita-field-edge)",
260
265
  "&:hover": { backgroundColor: "var(--calcita-field-hover-fill)", boxShadow: "var(--calcita-field-hover-edge)" }
@@ -1 +1 @@
1
- {"version":3,"file":"glassSurface.js","sources":["../../src/theme/glassSurface.js"],"sourcesContent":["// El «vidrio líquido» (Liquid Glass de Apple, 2025) en su versión de CSS: una superficie poco transparente que deja ver,\n// desenfocado, lo que pasa por detrás, con un brillo en el borde de arriba. La guía de Apple (Human Interface Guidelines,\n// «Materials»: https://developer.apple.com/design/human-interface-guidelines/materials) separa dos capas, y aquí igual:\n// - VIDRIO (glassStylesOf): «a distinct functional layer for controls and navigation elements… that floats above the\n// content layer». En el framework: la barra de arriba, el menú lateral, los menús y ventanitas, los diálogos, los\n// tooltips, los avisos, el panel de descargas, el botón flotante y, en los controles, la «gota» que marca lo elegido.\n// - MATERIAL (materialStylesOf): «Don't use Liquid Glass in the content layer»; el contenido usa los materiales estándar\n// (vidrio esmerilado, sin el brillo del borde) y los gruesos, más opacos, porque «provide better contrast for text».\n// Es lo que llevan las tarjetas con createToolkitTheme({glass: {surfaces: true}}).\n//\n// Decisiones, con su porqué:\n// - 60 % opaco por omisión. Con 80 %, en claro el vidrio era casi blanco sobre un lienzo casi blanco y se leía como\n// «cápsulas con sombra» (lo dijo el autor), no como vidrio: Apple dice que el vidrio «has no inherent color, and\n// instead takes on colors from the content directly behind it», y para eso tiene que dejar pasar el color. El material\n// del contenido es «delgado» (MATERIAL_OPACITY_OFFSET): con 74 % las tarjetas se veían planas, de un solo color (lo dijo\n// el autor); con el desenfoque de 20 px el texto se sigue leyendo. El proyecto lo cambia con\n// createToolkitTheme({glass: {opacity: 0.8}}), que mueve las dos.\n// - Lo que dibuja el vidrio es su filo de luz, como la orilla de una lente: brillante arriba a la izquierda (de donde\n// viene la luz), más suave abajo a la derecha, y un resplandor por dentro; la sombra de afuera es corta.\n// - Se pinta sólido si la persona pidió menos transparencia o más contraste en su equipo, o si el navegador no sabe\n// desenfocar: la misma guía pide respetar «Reducir transparencia», y Apple tuvo que agregar el control en iOS 26.1\n// porque el texto sobre vidrio no se leía.\n// - Con vidrio, el lienzo lleva unas manchas de color (ambientBackgroundOf): sobre un lienzo liso, el vidrio no\n// se distingue de una superficie sólida, porque no hay nada que desenfocar.\n// - Esquinas concéntricas: la de afuera es la de adentro más el margen (los renglones del menú lateral son de 12 px a 8 px\n// del borde, así que el panel es de 20 px). Apple lo pide así; si no, las esquinas «no casan» y se nota.\n// - Sin refracción (la lupa que deforma lo de atrás): en la web solo sale con filtros SVG que hoy funcionan nada más en\n// Chrome, o con WebGL. Esto es la versión que funciona en todos.\n// - color-mix y no alpha(): sirve igual con las variables CSS del tema (var(--mui-palette-…)) que con un color normal.\n//\n// OJO: backdrop-filter vuelve a la superficie el «contenedor» de lo que lleve adentro con position: fixed. Lo que deba\n// quedar fijo a la ventana va fuera de ella (los menús de MUI ya van en un portal).\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {paletteOf} from '@/theme/components/paletteOf.js';\nimport {lightDarkStylesOf} from '@/theme/lightDarkStyles.js';\nimport {cssTokenOf} from '@/theme/cssTokens.js';\n\n/** Qué tan opaca es la superficie (0 a 1). */\nexport const GLASS_OPACITY = 0.6;\n\n/** Las esquinas de lo que flota grande (menú lateral, barra, diálogos): renglones de 12 px a 8 px del borde. */\nexport const GLASS_PANEL_RADIUS_PIXELS = 20;\n\n/** Las esquinas de los menús y ventanitas: renglones de 8 px a 4 px del borde. */\nexport const GLASS_MENU_RADIUS_PIXELS = 12;\n\n// las esquinas y el desenfoque como variables CSS que el proyecto cambia de un jalón (cssTokens.js), con el valor de siempre\n// como respaldo\nexport const PANEL_RADIUS = cssTokenOf('panelRadius', `${GLASS_PANEL_RADIUS_PIXELS}px`);\nexport const MENU_RADIUS = cssTokenOf('menuRadius', `${GLASS_MENU_RADIUS_PIXELS}px`);\n\n/**\n * Lo que se separa de la orilla de la ventana lo que flota (la barra, el menú lateral, los cajones y la hoja de abajo):\n * Apple lo pide para que el contenido se asome por detrás («half sheets are inset from the edge of the display to allow\n * content to peek through from beneath them», Adopting Liquid Glass).\n */\nexport const GLASS_INSET_PIXELS = 8;\n\nexport const GLASS_BLUR = `blur(${cssTokenOf('glassBlur')}) saturate(${cssTokenOf('glassSaturate')})`;\n\n/**\n * El lente de cada tamaño (ver lensRulesOf), medido en capturas de Chrome: el control chico (botón, chip) más claro y con\n * la orilla más marcada; el panel (barra, menú, diálogo) más grueso, porque lleva texto («Liquid Glass appears more opaque\n * in larger elements like sidebars to preserve legibility», HIG «Color»). edgeFraction y scale van en fracción del tamaño\n * del vidrio; clearerBy es cuánto menos tinte lleva que el esmerilado.\n */\nexport const GLASS_LENS = Object.freeze({\n control: {filterId: 'toolkit-glass-lens-control', edgeFraction: 0.25, scale: 0.2, blurPixels: 3, clearerBy: 0.25},\n panel: {filterId: 'toolkit-glass-lens-panel', edgeFraction: 0.12, scale: 0.05, blurPixels: 10, clearerBy: 0.1},\n});\n\n// el gris de la gota en oscuro (por ciento de blanco): lo elegido se tiene que notar (glassDropOf)\nconst DARK_DROP_WHITE_PERCENT = 18;\n// cuánto negro (en claro) o blanco (en oscuro) lleva un texto del color del tema para leerse (accentTextStylesOf)\nconst ACCENT_TEXT_SHADE_PERCENT = 30;\n// el pozo de los campos (fieldWellStylesOf): el gris de relleno de Apple en claro y un oscurecido en oscuro\nconst FIELD_WELL_RADIUS_PIXELS = 12;\nconst FIELD_WELL_FILL = Object.freeze({\n light: {rest: 'rgba(118, 118, 128, 0.12)', hover: 'rgba(118, 118, 128, 0.18)'},\n // en oscuro el gris de Apple, MÁS CLARO que lo de atrás (su «tertiary fill»): con negro al 32 % el campo se perdía\n dark: {rest: 'rgba(118, 118, 128, 0.24)', hover: 'rgba(118, 118, 128, 0.30)'},\n});\n// el campo suelto sobre el lienzo (canvasFieldStylesOf): lámina clara en claro, oscura en oscuro, con su filo\nconst CANVAS_FIELD = Object.freeze({\n light: {rest: 'rgba(255, 255, 255, 0.62)', hover: 'rgba(255, 255, 255, 0.78)', edge: 'rgba(15, 23, 42, 0.10)'},\n // en oscuro, el gris de relleno de Apple (más claro que el fondo). Con negro al 34 % el campo contra el fondo casi negro\n // medía 1.01 : 1 y no se veía (lo reportaron: el buscador «se pierde»); con este, 1.26 : 1, y su filo aparte\n dark: {rest: 'rgba(118, 118, 128, 0.24)', hover: 'rgba(118, 118, 128, 0.30)', edge: 'rgba(255, 255, 255, 0.10)'},\n});\n// el contraste mínimo de WCAG para letra normal, y cómo se oscurece un relleno hasta lograrlo (whiteLabelFillOf)\nconst MIN_TEXT_CONTRAST = 4.5;\nconst FILL_SHARE_STEP_PERCENT = 5;\nconst MIN_FILL_SHARE_PERCENT = 30;\n\n/** La marca que GlassLensFilter pone en <body> cuando el navegador sí pinta el lente. */\nexport const GLASS_LENS_FLAG_SELECTOR = 'body[data-glass-lens=\"on\"]';\n\n/**\n * El selector de «este control está dentro de una tarjeta» (cualquier Paper: tarjeta, diálogo, menú), donde el control ya\n * no es vidrio. Va en dos versiones, sin y con la marca del lente: la regla del lente (una etiqueta y dos clases) le ganaba\n * a la de la tarjeta (dos clases) y los botones dentro de una tarjeta volvían a ser vidrio. Medido en Chrome.\n *\n * @param {string} [suffix] lo que va pegado al control (p. ej. ':not(.MuiButtonGroup-grouped)')\n * @returns {string}\n */\nexport function insideContentSelectorOf(suffix = '') {\n return `.MuiPaper-root &${suffix}, ${GLASS_LENS_FLAG_SELECTOR} .MuiPaper-root &${suffix}`;\n}\nconst REDUCED_TRANSPARENCY_QUERY = '@media (prefers-reduced-transparency: reduce), (prefers-contrast: more)';\nconst NO_BACKDROP_FILTER_QUERY = '@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))';\n\n// qué tan fuertes son las manchas del lienzo (por ciento del color). En oscuro van MÁS fuertes que en claro: sobre un lienzo\n// casi negro, unas manchas tenues no dan nada que desenfocar y el vidrio se ve como un gris sólido.\n// La de en medio (center) va detrás del contenido: sin ella, en claro las tarjetas de vidrio quedaban sobre blanco y se\n// veían sólidas (medido en las capturas de Chrome).\nconst AMBIENT_STRENGTH_LIGHT = Object.freeze({primary: 40, info: 30, accent: 30, center: 16});\n// en oscuro: manchas de los tonos oscuros sobre un casi negro con un toque azul (como los fondos oscuros de Apple). Antes\n// eran los tonos vivos al 36 % sobre #0A0A0A: aclaraban el fondo hasta el gris de las tarjetas y los elementos se perdían\n// (lo reportaron; medido: tarjeta contra fondo 1.12 : 1). Ahora 1.28 : 1 en el peor punto\nconst AMBIENT_STRENGTH_DARK = Object.freeze({primary: 18, info: 18, accent: 12, center: 10});\nconst DARK_CANVAS = Object.freeze({base: '#05070B'});\n\n// el filo de la lente: la luz de arriba a la izquierda (rimLight), la de abajo a la derecha más suave (rimShade), un\n// resplandor por dentro (innerGlow), una línea fina de afuera que lo separa del lienzo (outline) y la sombra que lo\n// despega (shadow). En oscuro el filo es más tenue, pero la sombra más fuerte: una negra suave no se ve sobre casi negro.\n// Un control chico (botón, chip) lleva una sombra corta (controlShadow): la de un panel se veía como neblina alrededor y,\n// dentro de una tira que se desliza (los filtros rápidos), la tira la cortaba en un rectángulo.\nconst GLASS_EDGE_LIGHT = Object.freeze({\n rimLight: 'rgba(255, 255, 255, 0.95)', rimShade: 'rgba(255, 255, 255, 0.5)', innerGlow: 'rgba(255, 255, 255, 0.45)',\n outline: 'rgba(15, 23, 42, 0.07)', shadow: '0 6px 20px rgba(15, 23, 42, 0.10)',\n controlShadow: '0 1px 2px rgba(15, 23, 42, 0.10), 0 2px 6px rgba(15, 23, 42, 0.06)',\n});\nconst GLASS_EDGE_DARK = Object.freeze({\n rimLight: 'rgba(255, 255, 255, 0.32)', rimShade: 'rgba(255, 255, 255, 0.12)', innerGlow: 'rgba(255, 255, 255, 0.07)',\n outline: 'rgba(0, 0, 0, 0.35)', shadow: '0 8px 28px rgba(0, 0, 0, 0.45)',\n controlShadow: '0 1px 2px rgba(0, 0, 0, 0.40), 0 2px 6px rgba(0, 0, 0, 0.25)',\n});\n// lo ancho del resplandor de adentro (px): lo que se ve como el grosor del vidrio en la orilla\nconst INNER_GLOW_PIXELS = 12;\n// en oscuro el vidrio se aclara un poco sobre su color: un gris igual de oscuro que el lienzo no se distingue de él aunque\n// desenfoque. Es lo que Apple hace en oscuro: lo que va al frente usa los colores «elevated», más claros, «making\n// foreground interfaces appear to advance» (HIG, Dark Mode). Con 8 % las tarjetas y los controles se perdían en el fondo\n// oscuro (lo reportaron); con 16 % y el lienzo nuevo (DARK_CANVAS) la tarjeta se separa 1.28 : 1 del fondo y el control\n// 1.32 : 1 (medido; Apple, su tarjeta #1C1C1E sobre negro: 1.23 : 1)\nconst DARK_GLASS_LIFT_PERCENT = 16;\n\n// el material del contenido: sin brillo de borde (eso es del vidrio de los controles), con una línea fina de afuera que lo\n// separa del lienzo y una sombra suave. En oscuro la línea es clara: una oscura no se ve sobre casi negro\nconst MATERIAL_EDGE_LIGHT = Object.freeze({outline: 'rgba(15, 23, 42, 0.08)', shadow: '0 1px 2px rgba(15, 23, 42, 0.06), 0 8px 24px rgba(15, 23, 42, 0.06)'});\nconst MATERIAL_EDGE_DARK = Object.freeze({outline: 'rgba(255, 255, 255, 0.08)', shadow: '0 8px 24px rgba(0, 0, 0, 0.35)'});\n// el material del contenido es «delgado» (HIG «Materials»: «Thinner materials… help people retain their context by\n// providing a visible reminder of the content that's in the background»): menos opaco que el vidrio de los controles. Con\n// el «grueso» (+0.14, 74 %) las tarjetas se veían planas; el texto se lee por el desenfoque de 20 px, y con «Reducir\n// transparencia» se pinta sólido. En oscuro resta menos: con 45 % la tarjeta casi no se separaba del fondo (medido 1.12 : 1)\nconst MATERIAL_OPACITY_OFFSET = Object.freeze({light: -0.15, dark: -0.05});\n// el material «grueso» (por ciento): tapa lo que pasa por debajo, como la columna fija de una tabla\nconst THICK_MATERIAL_PERCENT = 92;\n\n/**\n * Si el tema trae el vidrio prendido (createToolkitTheme({glass: …})). Lo usan los componentes del framework que flotan\n * (el marco, el panel de descargas, los avisos) y lo puede usar el proyecto para lo suyo.\n *\n * @param {object} theme el tema de MUI\n * @returns {boolean}\n */\nexport function isGlassTheme(theme) {\n return Boolean(theme?.toolkit?.glass);\n}\n\n/**\n * La opacidad del vidrio del tema: la que pidió el proyecto o GLASS_OPACITY.\n *\n * @param {object} theme\n * @returns {number}\n */\nexport function glassOpacityOf(theme) {\n return theme?.toolkit?.glass?.opacity ?? GLASS_OPACITY;\n}\n\n/**\n * El estilo de una superficie de vidrio sobre un color.\n *\n * @param {string} solidColor el color sólido de la superficie (p. ej. palette.background.paper o primary.main)\n * @param {object} [options]\n * @param {number} [options.opacity] por omisión GLASS_OPACITY\n * @param {boolean} [options.isDark] en oscuro el vidrio se aclara un poco y su filo brilla más\n * @param {boolean} [options.isControl] un control chico (botón, chip): sombra corta en vez de la de un panel\n * @returns {object} estilos para styleOverrides o sx\n */\nexport function glassSurfaceOf(solidColor, {opacity = GLASS_OPACITY, isDark = false, isControl = false} = {}) {\n const edge = isDark ? GLASS_EDGE_DARK : GLASS_EDGE_LIGHT;\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(opacity * 100)}%, transparent)`,\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n backgroundImage: 'linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0) 40%)',\n boxShadow: [\n `inset 1px 1px 0 ${edge.rimLight}`,\n `inset -1px -1px 0 ${edge.rimShade}`,\n `inset 0 0 ${INNER_GLOW_PIXELS}px ${edge.innerGlow}`,\n `0 0 0 1px ${edge.outline}`,\n isControl ? edge.controlShadow : edge.shadow,\n ].join(', '),\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * Lo que se pinta en lugar del vidrio si la persona pidió menos transparencia o más contraste, o si el navegador no sabe\n * desenfocar. Para lo transparente que no es una superficie (el fondo oscuro detrás de un diálogo, la perilla del\n * deslizador al arrastrarla).\n *\n * @param {object} solidStyle los estilos sólidos\n * @returns {object}\n */\nexport function solidFallbackOf(solidStyle) {\n return {[REDUCED_TRANSPARENCY_QUERY]: solidStyle, [NO_BACKDROP_FILTER_QUERY]: solidStyle};\n}\n\n/**\n * El vidrio sobre un color del tema en claro y en oscuro, con la opacidad del tema. Es lo que usan los ajustes de\n * componentes, y lo que usa el proyecto para algo suyo que flote:\n * sx={(theme) => glassStylesOf(theme)} // sobre el color de las tarjetas\n * sx={(theme) => glassStylesOf(theme, {color: paletteOf(theme).primary.main})} // vidrio del color principal\n *\n * @param {object} theme el tema de MUI\n * @param {object} [options]\n * @param {string} [options.color] el color de la superficie; por omisión el de las tarjetas (background.paper)\n * @param {string} [options.darkColor] el de modo oscuro, si es otro; por omisión el mismo\n * @param {number} [options.opacity] por omisión la del tema\n * @param {boolean} [options.isControl] un control chico (botón, chip): sombra corta en vez de la de un panel\n * @returns {object}\n */\nexport function glassStylesOf(theme, {color, darkColor, opacity, isControl = false} = {}) {\n const lightColor = color ?? paletteOf(theme).background.paper;\n const glassOpacity = opacity ?? glassOpacityOf(theme);\n return {\n ...lightDarkStylesOf(theme,\n glassSurfaceOf(lightColor, {opacity: glassOpacity, isControl}),\n glassSurfaceOf(darkColor ?? lightColor, {opacity: glassOpacity, isDark: true, isControl})),\n ...lensRulesOf(theme, {lightColor, darkColor: darkColor ?? lightColor, opacity: glassOpacity, isControl}),\n };\n}\n\n/**\n * El LENTE: lo que, según Apple, define al vidrio líquido («The primary way Liquid Glass visually defines itself is through\n * something called Lensing… previous materials scattered light, this new set of materials dynamically bends, shapes, and\n * concentrates light», WWDC25 «Meet Liquid Glass»). La orilla dobla lo de atrás (un filtro SVG de desplazamiento,\n * GlassLensFilter) y el centro queda más claro: menos tinte y menos desenfoque que el esmerilado.\n *\n * Solo en Chrome y sus parientes (Edge, Brave, Arc): son los únicos que aplican un filtro SVG en backdrop-filter; Firefox\n * lo acepta sin pintarlo (el vidrio se quedaría sin desenfoque) y Safari no lo entiende. Por eso va detrás de la marca que\n * GlassLensFilter pone en <body> solo en esos navegadores; en los demás queda el esmerilado. Con «Reducir transparencia» o\n * «Aumentar contraste» se pinta sólido igual que siempre.\n *\n * OJO: el modo oscuro va en su propia regla y no anidado: el selector de oscuro vive en <html> y, anidado debajo de la\n * marca de <body>, buscaría un .dark DENTRO de <body> y nunca coincidiría.\n *\n * @param {object} theme\n * @param {{lightColor: string, darkColor: string, opacity: number, isControl: boolean}} options\n * @returns {object}\n */\nfunction lensRulesOf(theme, {lightColor, darkColor, opacity, isControl}) {\n const lens = isControl ? GLASS_LENS.control : GLASS_LENS.panel;\n const lensOpacity = Math.max(0, opacity - lens.clearerBy);\n const backdrop = `blur(${lens.blurPixels}px) saturate(180%) url(#${lens.filterId})`;\n const lensSurfaceOf = (solidColor, isDark) => {\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(lensOpacity * 100)}%, transparent)`,\n backdropFilter: backdrop,\n WebkitBackdropFilter: backdrop,\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n };\n const lensKey = `${GLASS_LENS_FLAG_SELECTOR} &`;\n // la regla de oscuro del tema: un selector (*:where(.dark) &) o una media query, según cómo elija el modo el proyecto\n const darkKey = Object.keys(theme.applyStyles('dark', {}))[0];\n if (!darkKey) {\n return {[lensKey]: lensSurfaceOf(lightColor, false)};\n }\n const darkLens = darkKey.startsWith('@')\n ? {[darkKey]: {[lensKey]: lensSurfaceOf(darkColor, true)}}\n : {[darkKey.replace('&', lensKey)]: lensSurfaceOf(darkColor, true)};\n return {[lensKey]: lensSurfaceOf(lightColor, false), ...darkLens};\n}\n\n/**\n * El mapa de desplazamiento del lente (una imagen SVG): rojo para lo de lado y verde para lo de arriba abajo; 128 en medio\n * es «no mover». En la orilla de la izquierda y la de arriba vale más (toma de adentro) y en la de la derecha y la de\n * abajo, menos (también de adentro): así la orilla dobla lo de atrás hacia el centro, como una lente convexa, y nunca toma\n * de afuera del vidrio (afuera no hay imagen y la orilla se quedaba en blanco).\n *\n * @param {number} edgeFraction lo ancho de la orilla que dobla (0 a 0.5 del tamaño del vidrio)\n * @returns {string} data URI para el feImage del filtro\n */\nexport function lensDisplacementMapOf(edgeFraction) {\n const inner = 1 - edgeFraction;\n const svg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\" preserveAspectRatio=\"none\"><defs>'\n + `<linearGradient id=\"x\" x1=\"0\" x2=\"1\"><stop offset=\"0\" stop-color=\"#ff0000\"/><stop offset=\"${edgeFraction}\" stop-color=\"#800000\"/>`\n + `<stop offset=\"${inner}\" stop-color=\"#800000\"/><stop offset=\"1\" stop-color=\"#000000\"/></linearGradient>`\n + `<linearGradient id=\"y\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#00ff00\"/><stop offset=\"${edgeFraction}\" stop-color=\"#008000\"/>`\n + `<stop offset=\"${inner}\" stop-color=\"#008000\"/><stop offset=\"1\" stop-color=\"#000000\"/></linearGradient>`\n + '</defs><rect width=\"100\" height=\"100\" fill=\"url(#x)\"/><rect width=\"100\" height=\"100\" fill=\"url(#y)\" style=\"mix-blend-mode:screen\"/></svg>';\n return `data:image/svg+xml,${encodeURIComponent(svg)}`;\n}\n\n/**\n * El material del contenido sobre un color: esmerilado como el vidrio pero sin el brillo del borde, y más transparente.\n *\n * @param {string} solidColor\n * @param {object} [options]\n * @param {number} [options.opacity] la del vidrio; el material le suma MATERIAL_OPACITY_OFFSET de su modo (le resta)\n * @param {boolean} [options.isDark]\n * @returns {object}\n */\nexport function materialSurfaceOf(solidColor, {opacity = GLASS_OPACITY, isDark = false} = {}) {\n const edge = isDark ? MATERIAL_EDGE_DARK : MATERIAL_EDGE_LIGHT;\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n const offset = isDark ? MATERIAL_OPACITY_OFFSET.dark : MATERIAL_OPACITY_OFFSET.light;\n const materialOpacity = Math.min(1, Math.max(0, opacity + offset));\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(materialOpacity * 100)}%, transparent)`,\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n backgroundImage: 'none',\n boxShadow: `0 0 0 1px ${edge.outline}, ${edge.shadow}`,\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * El material del contenido en claro y en oscuro (las tarjetas con glass: {surfaces: true}). Para algo del proyecto que va\n * en el contenido: sx={(theme) => materialStylesOf(theme)}.\n *\n * @param {object} theme\n * @param {object} [options]\n * @param {string} [options.color] por omisión el de las tarjetas (background.paper)\n * @param {string} [options.darkColor] por omisión el mismo\n * @returns {object}\n */\nexport function materialStylesOf(theme, {color, darkColor} = {}) {\n const lightColor = color ?? paletteOf(theme).background.paper;\n const opacity = glassOpacityOf(theme);\n return lightDarkStylesOf(theme, materialSurfaceOf(lightColor, {opacity}), materialSurfaceOf(darkColor ?? lightColor, {opacity, isDark: true}));\n}\n\n/**\n * Solo el fondo del material, sin su línea ni su sombra: para lo que va DENTRO de una tarjeta y debe tapar lo que pasa por\n * debajo sin verse como un parche sólido (la columna pegada de la tabla al deslizarla de lado). Va como capa\n * (background-image) y no como color: así el color propio del elemento (el del renglón: la cebra, el cursor encima) se\n * sigue pintando encima.\n *\n * Con {thick: true}, el material «grueso» de Apple (HIG «Materials»: los gruesos son para leer sobre otro contenido): el\n * mismo desenfoque al 92 %. Es el de la columna fija de una tabla: con el delgado de la tarjeta (45 a 55 %) se veían pasar\n * por detrás las otras columnas (lo reportaron).\n *\n * @param {object} theme\n * @param {object} [options]\n * @param {boolean} [options.thick] el material grueso, para tapar lo que pasa por debajo\n * @returns {object}\n */\nexport function materialBackgroundOf(theme, {thick = false} = {}) {\n const paper = paletteOf(theme).background.paper;\n const opacityOf = (offset) => (thick ? THICK_MATERIAL_PERCENT : Math.round(Math.min(1, Math.max(0, glassOpacityOf(theme) + offset)) * 100));\n const layerOf = (color) => `linear-gradient(${color}, ${color})`;\n const frostedOf = (tint, opacity) => ({\n backgroundImage: layerOf(`color-mix(in srgb, ${tint} ${opacity}%, transparent)`),\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n });\n return {\n ...lightDarkStylesOf(theme,\n frostedOf(paper, opacityOf(MATERIAL_OPACITY_OFFSET.light)),\n frostedOf(`color-mix(in srgb, ${paper}, white ${DARK_GLASS_LIFT_PERCENT}%)`, opacityOf(MATERIAL_OPACITY_OFFSET.dark))),\n ...solidFallbackOf({backgroundImage: layerOf(paper), backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * La «gota» que marca lo elegido dentro de un control (la pestaña, la opción, el renglón del menú lateral): en claro, una\n * pastilla blanca con su brillo y su sombra; en oscuro, un relleno gris parejo, como el renglón elegido de las barras\n * laterales de Apple en oscuro. No es transparente del todo: va DENTRO de algo que ya puede ser de vidrio (un menú), y\n * vidrio sobre vidrio no se lee.\n * OJO, oscuro: con blanco al 14 % casi no se veía, y su línea de luz arriba dibujaba un arco raro en la esquina («no se\n * nota la selección», «se ve raro»; lo reportaron). Va al 18 % y sin línea.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function glassDropOf(theme) {\n const palette = paletteOf(theme);\n return lightDarkStylesOf(theme, {\n backgroundColor: palette.background.paper,\n color: palette.text.primary,\n boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 1px 3px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.08)',\n }, {\n backgroundColor: `color-mix(in srgb, ${palette.common.white} ${DARK_DROP_WHITE_PERCENT}%, transparent)`,\n boxShadow: '0 1px 3px rgba(0, 0, 0, 0.35)',\n });\n}\n\n/**\n * El relleno de un control lleno de color (el botón principal, la etiqueta elegida) para llevar letra BLANCA y que se lea:\n * el color del tema oscurecido lo justo para llegar a 4.5 : 1 (WCAG). En oscuro la paleta trae letra negra sobre el\n * naranja («El suyo», «Guardar»: se veía feo, lo reportaron) y el blanco sobre el naranja tal cual da 2.85 : 1; con el\n * naranja al 75 % y blanco da 4.79 : 1. Se calcula con el color que elija el proyecto, no con uno fijo.\n *\n * @param {string} rawColor el color en #RRGGBB o #RGB (el de la paleta, no la variable CSS)\n * @returns {string|null} el relleno en #RRGGBB; null si el color no viene en hexadecimal\n */\nexport function whiteLabelFillOf(rawColor) {\n const channels = hexChannelsOf(rawColor);\n if (!channels) {\n return null;\n }\n // del color tal cual hacia el negro, en pasos de 5 %, hasta que la letra blanca se lea\n for (let share = 100; share >= MIN_FILL_SHARE_PERCENT; share -= FILL_SHARE_STEP_PERCENT) {\n const mixed = channels.map((channel) => Math.round((channel * share) / 100));\n if (contrastWithWhiteOf(mixed) >= MIN_TEXT_CONTRAST) {\n return `#${mixed.map((channel) => channel.toString(16).padStart(2, '0')).join('')}`;\n }\n }\n return `#${channels.map((channel) => Math.round((channel * MIN_FILL_SHARE_PERCENT) / 100).toString(16).padStart(2, '0')).join('')}`;\n}\n\n/**\n * Los tres canales de un color #RRGGBB o #RGB (0 a 255); null si no es hexadecimal.\n *\n * @param {string} color\n * @returns {number[]|null}\n */\nfunction hexChannelsOf(color) {\n const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(color ?? '').trim());\n if (!match) {\n return null;\n }\n const digits = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join('') : match[1];\n return [0, 2, 4].map((start) => parseInt(digits.slice(start, start + 2), 16));\n}\n\n/**\n * El contraste de la letra blanca sobre un color (fórmula de WCAG).\n *\n * @param {number[]} channels\n * @returns {number}\n */\nfunction contrastWithWhiteOf(channels) {\n const [red, green, blue] = channels.map((channel) => {\n const unit = channel / 255;\n return unit <= 0.03928 ? unit / 12.92 : ((unit + 0.055) / 1.055) ** 2.4;\n });\n const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;\n return 1.05 / (luminance + 0.05);\n}\n\n/**\n * Los tonos de los campos por omisión, por modo: fill (el relleno), hoverFill (con el cursor encima), border y hoverBorder\n * (la orilla; transparente: el pozo de Apple no lleva). createToolkitTheme los pone en la paleta de cada modo\n * (palette.field) y el proyecto los cambia con `fields` o con su paleta: blancos con borde, del tono de su marca…\n */\nexport const FIELD_TONES = Object.freeze({\n light: Object.freeze({fill: FIELD_WELL_FILL.light.rest, hoverFill: FIELD_WELL_FILL.light.hover, border: 'transparent', hoverBorder: 'transparent'}),\n dark: Object.freeze({fill: FIELD_WELL_FILL.dark.rest, hoverFill: FIELD_WELL_FILL.dark.hover, border: 'transparent', hoverBorder: 'transparent'}),\n});\n\n/**\n * Los tonos de los campos del tema (palette.field, que llena createToolkitTheme; con sus variables CSS cambian solos con el\n * modo, sin reglas de oscuro), o null si el tema no los trae (un createTheme de MUI sin Calcita).\n *\n * @param {object} theme\n * @returns {{fill: string, hoverFill: string, border: string, hoverBorder: string}|null}\n */\nexport function fieldPaletteOf(theme) {\n return paletteOf(theme).field ?? null;\n}\n\n/** La orilla de un campo: una línea por dentro (sombra inset), que no mueve el texto ni pelea con el aro del foco (outline). */\nfunction fieldEdgeOf(color) {\n return `inset 0 0 0 1px ${color}`;\n}\n\n/**\n * Las esquinas de los campos: las que pidió el proyecto en su tema (createToolkitTheme fields.radius), o la variable\n * --calcita-field-radius con las de cada estilo de respaldo.\n */\nfunction fieldRadiusOf(theme, lookRadius) {\n return theme.toolkit?.fields?.radius ?? cssTokenOf('fieldRadius', `${lookRadius}px`);\n}\n\n/**\n * El «pozo» de un campo de texto dentro del contenido (una tarjeta, un formulario agrupado), como los de Apple: un relleno\n * parejo con esquinas de 12 px, del gris de relleno de Apple («tertiary fill»): tenue en claro y más claro que la tarjeta en\n * oscuro (con negro, en oscuro se perdía). Sin vidrio adentro de la tarjeta: ahí no tiene nada que dejar\n * ver. Con el cursor encima se marca apenas; antes el relleno salía solo con el cursor y en cuadro («hovers espantosos»).\n * Los tonos y las esquinas los puede cambiar el proyecto (createToolkitTheme fields).\n *\n * @param {object} theme\n * @param {{radius?: number}} [options] las esquinas del estilo que lo pide (el minimalista las tiene más chicas)\n * @returns {object}\n */\nexport function fieldWellStylesOf(theme, {radius = FIELD_WELL_RADIUS_PIXELS} = {}) {\n const base = {backgroundImage: 'none', backdropFilter: 'none', WebkitBackdropFilter: 'none', borderRadius: fieldRadiusOf(theme, radius)};\n const field = fieldPaletteOf(theme);\n if (field) {\n return {\n ...base,\n backgroundColor: field.fill,\n boxShadow: fieldEdgeOf(field.border),\n '&:hover': {backgroundColor: field.hoverFill, boxShadow: fieldEdgeOf(field.hoverBorder)},\n };\n }\n const {light, dark} = FIELD_TONES;\n return {\n ...base,\n ...lightDarkStylesOf(theme,\n {backgroundColor: light.fill, boxShadow: fieldEdgeOf(light.border), '&:hover': {backgroundColor: light.hoverFill, boxShadow: fieldEdgeOf(light.hoverBorder)}},\n {backgroundColor: dark.fill, boxShadow: fieldEdgeOf(dark.border), '&:hover': {backgroundColor: dark.hoverFill, boxShadow: fieldEdgeOf(dark.hoverBorder)}}),\n };\n}\n\n/**\n * Los tonos de los campos como variables (--calcita-field-*) para un contenedor: el renglón de FormSection las pone en sí\n * mismo y sus campos las leen (fieldWellFromVariablesOf). OJO, trampa pagada: el renglón le gana al tema con selectores\n * largos («html body &&& .MuiOutlinedInput-root») y una regla de oscuro anidada ahí abajo nunca aplicaba (en oscuro el\n * campo salía con el gris de claro). En el contenedor la regla de oscuro sí aplica, y los campos heredan la variable.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function fieldVariablesOf(theme) {\n const variablesOf = (tones) => ({\n '--calcita-field-fill': tones.fill,\n '--calcita-field-hover-fill': tones.hoverFill,\n '--calcita-field-edge': fieldEdgeOf(tones.border),\n '--calcita-field-hover-edge': fieldEdgeOf(tones.hoverBorder),\n });\n const field = fieldPaletteOf(theme);\n if (field) {\n return variablesOf(field);\n }\n return {...variablesOf(FIELD_TONES.light), ...theme.applyStyles('dark', variablesOf(FIELD_TONES.dark))};\n}\n\n/**\n * El pozo de un campo leyendo las variables de su contenedor (fieldVariablesOf).\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function fieldWellFromVariablesOf(theme) {\n return {\n backgroundImage: 'none',\n backdropFilter: 'none',\n WebkitBackdropFilter: 'none',\n borderRadius: fieldRadiusOf(theme, FIELD_WELL_RADIUS_PIXELS),\n backgroundColor: 'var(--calcita-field-fill)',\n boxShadow: 'var(--calcita-field-edge)',\n '&:hover': {backgroundColor: 'var(--calcita-field-hover-fill)', boxShadow: 'var(--calcita-field-hover-edge)'},\n };\n}\n\n/**\n * Un campo de texto suelto sobre el lienzo (fuera de una tarjeta): una lámina de vidrio clara en claro y el gris de relleno\n * de Apple en oscuro, más claro que el fondo casi negro. Con «Reducir transparencia» se pinta sólido.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function canvasFieldStylesOf(theme) {\n const paper = paletteOf(theme).background.paper;\n return {\n backgroundImage: 'none',\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n borderRadius: cssTokenOf('fieldRadius', `${FIELD_WELL_RADIUS_PIXELS}px`),\n ...lightDarkStylesOf(theme, {\n backgroundColor: CANVAS_FIELD.light.rest,\n boxShadow: `0 0 0 1px ${CANVAS_FIELD.light.edge}`,\n '&:hover': {backgroundColor: CANVAS_FIELD.light.hover},\n }, {\n backgroundColor: CANVAS_FIELD.dark.rest,\n boxShadow: `0 0 0 1px ${CANVAS_FIELD.dark.edge}`,\n '&:hover': {backgroundColor: CANVAS_FIELD.dark.hover},\n }),\n ...solidFallbackOf({backgroundColor: paper, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * El color de un TEXTO que debe ir del color del tema (una liga, un botón de solo texto, el renglón elegido del menú, el\n * valor de una fecha): el color de la paleta oscurecido en claro y aclarado en oscuro, para que se lea. Medido (WCAG pide\n * 4.5 : 1 en letra normal): el naranja de la marca sobre una tarjeta clara daba 2.78 : 1 y sobre su propio tinte en oscuro\n * 4.41 : 1 (el reporte: «cuesta leer», «se pierde»); con 30 % de negro en claro da 5.24 : 1 y con 30 % de blanco en\n * oscuro 8.23 : 1. Los botones con relleno de color NO usan esto: van con letra de un solo color (HIG «Buttons»: «Avoid\n * applying a similar color to button labels and content layer backgrounds»).\n *\n * @param {object} theme\n * @param {string} [colorName] primary, error, success… (por omisión primary)\n * @returns {object} {color, y el de oscuro}\n */\nexport function accentTextStylesOf(theme, colorName = 'primary') {\n const palette = paletteOf(theme);\n const main = (palette[colorName] ?? palette.primary).main;\n const keep = 100 - ACCENT_TEXT_SHADE_PERCENT;\n return lightDarkStylesOf(theme, {color: `color-mix(in srgb, ${main} ${keep}%, black)`}, {color: `color-mix(in srgb, ${main} ${keep}%, white)`});\n}\n\n/**\n * Un fondo FIJO a la ventana que no se mueve al deslizar, también en el iPhone: va en una capa aparte (::before con\n * position: fixed) detrás del contenido del elemento. Así el vidrio se ve pasar por encima de un fondo quieto.\n *\n * OJO: background-attachment: fixed no sirve para esto. Safari del iPhone y del iPad lo ignora y el fondo se movía con\n * todo (lo vio el autor en el teléfono); además, en escritorio vuelve a pintar todo el fondo en cada paso al deslizar.\n * La capa va con z-index -1 dentro de un contexto propio (isolation): queda arriba del color del elemento y abajo de su\n * contenido. Si el elemento es una caja con esquinas (la escena de un ejemplo), clip-path la recorta a la caja.\n *\n * @param {object} layerStyles el fondo de la capa (backgroundColor, backgroundImage…; puede llevar el modo oscuro)\n * @param {object} [options]\n * @param {string} [options.clipPath] p. ej. 'inset(0 round 20px)' para una caja; sin él, la capa ocupa toda la ventana\n * @returns {object} estilos para el sx del elemento\n */\nexport function fixedBackgroundLayerOf(layerStyles, {clipPath} = {}) {\n return {\n isolation: 'isolate',\n ...(clipPath && {clipPath}),\n // OJO, iPhone: con inset: 0 el alto de la capa cambia cuando la barra de Safari se esconde al deslizar, y el fondo\n // «seguía al scroll» (lo vio el autor en su teléfono). Medida fija de toda la pantalla (100vw × 100lvh, el alto con\n // la barra escondida; 100vh donde no hay lvh) y su propia capa de composición (translateZ), que es lo que la deja\n // quieta en WebKit\n '&::before': {\n content: '\"\"',\n position: 'fixed',\n top: 0,\n left: 0,\n width: '100vw',\n height: '100vh',\n '@supports (height: 100lvh)': {height: '100lvh'},\n transform: 'translateZ(0)',\n willChange: 'transform',\n zIndex: -1,\n pointerEvents: 'none',\n ...layerStyles,\n },\n };\n}\n\n/**\n * El fondo del lienzo con vidrio: manchas del color principal y del de información, fijas a la ventana, para\n * que el vidrio tenga algo que desenfocar. AppLayout ya lo pone; una pantalla fuera del marco (el inicio de sesión) lo usa\n * con sx={(theme) => ambientBackgroundOf(theme)}.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function ambientBackgroundOf(theme) {\n const palette = paletteOf(theme);\n const washOf = (strength, tones) => [\n `radial-gradient(60vmax 45vmax at 0% 0%, color-mix(in srgb, ${tones.primary} ${strength.primary}%, transparent), transparent 70%)`,\n `radial-gradient(50vmax 40vmax at 100% 15%, color-mix(in srgb, ${tones.info} ${strength.info}%, transparent), transparent 70%)`,\n `radial-gradient(55vmax 45vmax at 70% 100%, color-mix(in srgb, ${tones.accent} ${strength.accent}%, transparent), transparent 70%)`,\n `radial-gradient(45vmax 35vmax at 45% 50%, color-mix(in srgb, ${tones.info} ${strength.center}%, transparent), transparent 70%)`,\n ].join(', ');\n const lightTones = {primary: palette.primary.main, info: palette.info.main, accent: palette.primary.light};\n // en oscuro, los tonos OSCUROS de la paleta sobre un casi negro: el color tiñe sin aclarar el fondo, y las tarjetas y el\n // vidrio (más claros) no se pierden (ver DARK_CANVAS)\n const darkTones = {primary: palette.primary.dark, info: palette.info.dark, accent: palette.primary.dark};\n return {\n ...lightDarkStylesOf(theme, {backgroundColor: palette.background.default}, {backgroundColor: DARK_CANVAS.base}),\n ...fixedBackgroundLayerOf(lightDarkStylesOf(theme,\n {backgroundColor: palette.background.default, backgroundImage: washOf(AMBIENT_STRENGTH_LIGHT, lightTones)},\n {backgroundColor: DARK_CANVAS.base, backgroundImage: washOf(AMBIENT_STRENGTH_DARK, darkTones)})),\n };\n}\n\n// la orilla de arriba con la barra en cápsulas: cuánto tinte del lienzo lleva y cuánto desenfoca (px)\nconst SCROLL_EDGE_TINT_PERCENT = 55;\nconst SCROLL_EDGE_BLUR_PIXELS = 12;\n\n/**\n * La orilla de arriba cuando la barra va en cápsulas (TOP_BAR.capsules con vidrio, como las ventanas de macOS Tahoe; Apple le\n * llama «scroll edge effect»): lo que pasa por debajo se desenfoca y se desvanece hacia el contenido, para que el título\n * y las cápsulas se lean sobre cualquier cosa. Con «Reducir transparencia» o sin backdrop-filter, el color del lienzo.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function scrollEdgeStylesOf(theme) {\n const palette = paletteOf(theme);\n const tintOf = (canvas) => `color-mix(in srgb, ${canvas} ${SCROLL_EDGE_TINT_PERCENT}%, transparent)`;\n const blur = `blur(${SCROLL_EDGE_BLUR_PIXELS}px)`;\n // de arriba (cubre) hacia abajo (se desvanece): sin raya donde termina\n const fade = 'linear-gradient(to bottom, black 45%, transparent)';\n return {\n ...lightDarkStylesOf(theme, {backgroundColor: tintOf(palette.background.default)}, {backgroundColor: tintOf(DARK_CANVAS.base)}),\n backdropFilter: blur,\n WebkitBackdropFilter: blur,\n maskImage: fade,\n WebkitMaskImage: fade,\n ...solidFallbackOf({backgroundColor: palette.background.default, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"glassSurface.js","sources":["../../src/theme/glassSurface.js"],"sourcesContent":["// El «vidrio líquido» (Liquid Glass de Apple, 2025) en su versión de CSS: una superficie poco transparente que deja ver,\n// desenfocado, lo que pasa por detrás, con un brillo en el borde de arriba. La guía de Apple (Human Interface Guidelines,\n// «Materials»: https://developer.apple.com/design/human-interface-guidelines/materials) separa dos capas, y aquí igual:\n// - VIDRIO (glassStylesOf): «a distinct functional layer for controls and navigation elements… that floats above the\n// content layer». En el framework: la barra de arriba, el menú lateral, los menús y ventanitas, los diálogos, los\n// tooltips, los avisos, el panel de descargas, el botón flotante y, en los controles, la «gota» que marca lo elegido.\n// - MATERIAL (materialStylesOf): «Don't use Liquid Glass in the content layer»; el contenido usa los materiales estándar\n// (vidrio esmerilado, sin el brillo del borde) y los gruesos, más opacos, porque «provide better contrast for text».\n// Es lo que llevan las tarjetas con createToolkitTheme({glass: {surfaces: true}}).\n//\n// Decisiones, con su porqué:\n// - 60 % opaco por omisión. Con 80 %, en claro el vidrio era casi blanco sobre un lienzo casi blanco y se leía como\n// «cápsulas con sombra» (lo dijo el autor), no como vidrio: Apple dice que el vidrio «has no inherent color, and\n// instead takes on colors from the content directly behind it», y para eso tiene que dejar pasar el color. El material\n// del contenido es «delgado» (MATERIAL_OPACITY_OFFSET): con 74 % las tarjetas se veían planas, de un solo color (lo dijo\n// el autor); con el desenfoque de 20 px el texto se sigue leyendo. El proyecto lo cambia con\n// createToolkitTheme({glass: {opacity: 0.8}}), que mueve las dos.\n// - Lo que dibuja el vidrio es su filo de luz, como la orilla de una lente: brillante arriba a la izquierda (de donde\n// viene la luz), más suave abajo a la derecha, y un resplandor por dentro; la sombra de afuera es corta.\n// - Se pinta sólido si la persona pidió menos transparencia o más contraste en su equipo, o si el navegador no sabe\n// desenfocar: la misma guía pide respetar «Reducir transparencia», y Apple tuvo que agregar el control en iOS 26.1\n// porque el texto sobre vidrio no se leía.\n// - Con vidrio, el lienzo lleva unas manchas de color (ambientBackgroundOf): sobre un lienzo liso, el vidrio no\n// se distingue de una superficie sólida, porque no hay nada que desenfocar.\n// - Esquinas concéntricas: la de afuera es la de adentro más el margen (los renglones del menú lateral son de 12 px a 8 px\n// del borde, así que el panel es de 20 px). Apple lo pide así; si no, las esquinas «no casan» y se nota.\n// - Sin refracción (la lupa que deforma lo de atrás): en la web solo sale con filtros SVG que hoy funcionan nada más en\n// Chrome, o con WebGL. Esto es la versión que funciona en todos.\n// - color-mix y no alpha(): sirve igual con las variables CSS del tema (var(--mui-palette-…)) que con un color normal.\n//\n// OJO: backdrop-filter vuelve a la superficie el «contenedor» de lo que lleve adentro con position: fixed. Lo que deba\n// quedar fijo a la ventana va fuera de ella (los menús de MUI ya van en un portal).\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {paletteOf} from '@/theme/components/paletteOf.js';\nimport {lightDarkStylesOf} from '@/theme/lightDarkStyles.js';\nimport {cssTokenOf} from '@/theme/cssTokens.js';\n\n/** Qué tan opaca es la superficie (0 a 1). */\nexport const GLASS_OPACITY = 0.6;\n\n/** Las esquinas de lo que flota grande (menú lateral, barra, diálogos): renglones de 12 px a 8 px del borde. */\nexport const GLASS_PANEL_RADIUS_PIXELS = 20;\n\n/** Las esquinas de los menús y ventanitas: renglones de 8 px a 4 px del borde. */\nexport const GLASS_MENU_RADIUS_PIXELS = 12;\n\n// las esquinas y el desenfoque como variables CSS que el proyecto cambia de un jalón (cssTokens.js), con el valor de siempre\n// como respaldo\nexport const PANEL_RADIUS = cssTokenOf('panelRadius', `${GLASS_PANEL_RADIUS_PIXELS}px`);\nexport const MENU_RADIUS = cssTokenOf('menuRadius', `${GLASS_MENU_RADIUS_PIXELS}px`);\n\n/**\n * Lo que se separa de la orilla de la ventana lo que flota (la barra, el menú lateral, los cajones y la hoja de abajo):\n * Apple lo pide para que el contenido se asome por detrás («half sheets are inset from the edge of the display to allow\n * content to peek through from beneath them», Adopting Liquid Glass).\n */\nexport const GLASS_INSET_PIXELS = 8;\n\nexport const GLASS_BLUR = `blur(${cssTokenOf('glassBlur')}) saturate(${cssTokenOf('glassSaturate')})`;\n\n/**\n * El lente de cada tamaño (ver lensRulesOf), medido en capturas de Chrome: el control chico (botón, chip) más claro y con\n * la orilla más marcada; el panel (barra, menú, diálogo) más grueso, porque lleva texto («Liquid Glass appears more opaque\n * in larger elements like sidebars to preserve legibility», HIG «Color»). edgeFraction y scale van en fracción del tamaño\n * del vidrio; clearerBy es cuánto menos tinte lleva que el esmerilado.\n */\nexport const GLASS_LENS = Object.freeze({\n control: {filterId: 'toolkit-glass-lens-control', edgeFraction: 0.25, scale: 0.2, blurPixels: 3, clearerBy: 0.25},\n panel: {filterId: 'toolkit-glass-lens-panel', edgeFraction: 0.12, scale: 0.05, blurPixels: 10, clearerBy: 0.1},\n});\n\n// el gris de la gota en oscuro (por ciento de blanco): lo elegido se tiene que notar (glassDropOf)\nconst DARK_DROP_WHITE_PERCENT = 18;\n// cuánto negro (en claro) o blanco (en oscuro) lleva un texto del color del tema para leerse (accentTextStylesOf)\nconst ACCENT_TEXT_SHADE_PERCENT = 30;\n// el pozo de los campos (fieldWellStylesOf): el gris de relleno de Apple en claro y un oscurecido en oscuro\nconst FIELD_WELL_RADIUS_PIXELS = 12;\nconst FIELD_WELL_FILL = Object.freeze({\n light: {rest: 'rgba(118, 118, 128, 0.12)', hover: 'rgba(118, 118, 128, 0.18)'},\n // en oscuro el gris de Apple, MÁS CLARO que lo de atrás (su «tertiary fill»): con negro al 32 % el campo se perdía\n dark: {rest: 'rgba(118, 118, 128, 0.24)', hover: 'rgba(118, 118, 128, 0.30)'},\n});\n// el campo suelto sobre el lienzo (canvasFieldStylesOf): lámina clara en claro, oscura en oscuro, con su filo\nconst CANVAS_FIELD = Object.freeze({\n light: {rest: 'rgba(255, 255, 255, 0.62)', hover: 'rgba(255, 255, 255, 0.78)', edge: 'rgba(15, 23, 42, 0.10)'},\n // en oscuro, el gris de relleno de Apple (más claro que el fondo). Con negro al 34 % el campo contra el fondo casi negro\n // medía 1.01 : 1 y no se veía (lo reportaron: el buscador «se pierde»); con este, 1.26 : 1, y su filo aparte\n dark: {rest: 'rgba(118, 118, 128, 0.24)', hover: 'rgba(118, 118, 128, 0.30)', edge: 'rgba(255, 255, 255, 0.10)'},\n});\n// el contraste mínimo de WCAG para letra normal, y cómo se oscurece un relleno hasta lograrlo (whiteLabelFillOf)\nconst MIN_TEXT_CONTRAST = 4.5;\nconst FILL_SHARE_STEP_PERCENT = 5;\nconst MIN_FILL_SHARE_PERCENT = 30;\n\n/** La marca que GlassLensFilter pone en <body> cuando el navegador sí pinta el lente. */\nexport const GLASS_LENS_FLAG_SELECTOR = 'body[data-glass-lens=\"on\"]';\n\n/**\n * El selector de «este control está dentro de una tarjeta» (cualquier Paper: tarjeta, diálogo, menú), donde el control ya\n * no es vidrio. Va en dos versiones, sin y con la marca del lente: la regla del lente (una etiqueta y dos clases) le ganaba\n * a la de la tarjeta (dos clases) y los botones dentro de una tarjeta volvían a ser vidrio. Medido en Chrome.\n *\n * @param {string} [suffix] lo que va pegado al control (p. ej. ':not(.MuiButtonGroup-grouped)')\n * @returns {string}\n */\nexport function insideContentSelectorOf(suffix = '') {\n return `.MuiPaper-root &${suffix}, ${GLASS_LENS_FLAG_SELECTOR} .MuiPaper-root &${suffix}`;\n}\nconst REDUCED_TRANSPARENCY_QUERY = '@media (prefers-reduced-transparency: reduce), (prefers-contrast: more)';\nconst NO_BACKDROP_FILTER_QUERY = '@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))';\n\n// qué tan fuertes son las manchas del lienzo (por ciento del color). En oscuro van MÁS fuertes que en claro: sobre un lienzo\n// casi negro, unas manchas tenues no dan nada que desenfocar y el vidrio se ve como un gris sólido.\n// La de en medio (center) va detrás del contenido: sin ella, en claro las tarjetas de vidrio quedaban sobre blanco y se\n// veían sólidas (medido en las capturas de Chrome).\nconst AMBIENT_STRENGTH_LIGHT = Object.freeze({primary: 40, info: 30, accent: 30, center: 16});\n// en oscuro: manchas de los tonos oscuros sobre un casi negro con un toque azul (como los fondos oscuros de Apple). Antes\n// eran los tonos vivos al 36 % sobre #0A0A0A: aclaraban el fondo hasta el gris de las tarjetas y los elementos se perdían\n// (lo reportaron; medido: tarjeta contra fondo 1.12 : 1). Ahora 1.28 : 1 en el peor punto\nconst AMBIENT_STRENGTH_DARK = Object.freeze({primary: 18, info: 18, accent: 12, center: 10});\nconst DARK_CANVAS = Object.freeze({base: '#05070B'});\n\n// el filo de la lente: la luz de arriba a la izquierda (rimLight), la de abajo a la derecha más suave (rimShade), un\n// resplandor por dentro (innerGlow), una línea fina de afuera que lo separa del lienzo (outline) y la sombra que lo\n// despega (shadow). En oscuro el filo es más tenue, pero la sombra más fuerte: una negra suave no se ve sobre casi negro.\n// Un control chico (botón, chip) lleva una sombra corta (controlShadow): la de un panel se veía como neblina alrededor y,\n// dentro de una tira que se desliza (los filtros rápidos), la tira la cortaba en un rectángulo.\nconst GLASS_EDGE_LIGHT = Object.freeze({\n rimLight: 'rgba(255, 255, 255, 0.95)', rimShade: 'rgba(255, 255, 255, 0.5)', innerGlow: 'rgba(255, 255, 255, 0.45)',\n outline: 'rgba(15, 23, 42, 0.07)', shadow: '0 6px 20px rgba(15, 23, 42, 0.10)',\n controlShadow: '0 1px 2px rgba(15, 23, 42, 0.10), 0 2px 6px rgba(15, 23, 42, 0.06)',\n});\nconst GLASS_EDGE_DARK = Object.freeze({\n rimLight: 'rgba(255, 255, 255, 0.32)', rimShade: 'rgba(255, 255, 255, 0.12)', innerGlow: 'rgba(255, 255, 255, 0.07)',\n outline: 'rgba(0, 0, 0, 0.35)', shadow: '0 8px 28px rgba(0, 0, 0, 0.45)',\n controlShadow: '0 1px 2px rgba(0, 0, 0, 0.40), 0 2px 6px rgba(0, 0, 0, 0.25)',\n});\n// lo ancho del resplandor de adentro (px): lo que se ve como el grosor del vidrio en la orilla\nconst INNER_GLOW_PIXELS = 12;\n// en oscuro el vidrio se aclara un poco sobre su color: un gris igual de oscuro que el lienzo no se distingue de él aunque\n// desenfoque. Es lo que Apple hace en oscuro: lo que va al frente usa los colores «elevated», más claros, «making\n// foreground interfaces appear to advance» (HIG, Dark Mode). Con 8 % las tarjetas y los controles se perdían en el fondo\n// oscuro (lo reportaron); con 16 % y el lienzo nuevo (DARK_CANVAS) la tarjeta se separa 1.28 : 1 del fondo y el control\n// 1.32 : 1 (medido; Apple, su tarjeta #1C1C1E sobre negro: 1.23 : 1)\nconst DARK_GLASS_LIFT_PERCENT = 16;\n\n// el material del contenido: sin brillo de borde (eso es del vidrio de los controles), con una línea fina de afuera que lo\n// separa del lienzo y una sombra suave. En oscuro la línea es clara: una oscura no se ve sobre casi negro\nconst MATERIAL_EDGE_LIGHT = Object.freeze({outline: 'rgba(15, 23, 42, 0.08)', shadow: '0 1px 2px rgba(15, 23, 42, 0.06), 0 8px 24px rgba(15, 23, 42, 0.06)'});\nconst MATERIAL_EDGE_DARK = Object.freeze({outline: 'rgba(255, 255, 255, 0.08)', shadow: '0 8px 24px rgba(0, 0, 0, 0.35)'});\n// el material del contenido es «delgado» (HIG «Materials»: «Thinner materials… help people retain their context by\n// providing a visible reminder of the content that's in the background»): menos opaco que el vidrio de los controles. Con\n// el «grueso» (+0.14, 74 %) las tarjetas se veían planas; el texto se lee por el desenfoque de 20 px, y con «Reducir\n// transparencia» se pinta sólido. En oscuro resta menos: con 45 % la tarjeta casi no se separaba del fondo (medido 1.12 : 1)\nconst MATERIAL_OPACITY_OFFSET = Object.freeze({light: -0.15, dark: -0.05});\n// el material «grueso» (por ciento): tapa lo que pasa por debajo, como la columna fija de una tabla\nconst THICK_MATERIAL_PERCENT = 92;\n\n/**\n * Si el tema trae el vidrio prendido (createToolkitTheme({glass: …})). Lo usan los componentes del framework que flotan\n * (el marco, el panel de descargas, los avisos) y lo puede usar el proyecto para lo suyo.\n *\n * @param {object} theme el tema de MUI\n * @returns {boolean}\n */\nexport function isGlassTheme(theme) {\n return Boolean(theme?.toolkit?.glass);\n}\n\n/**\n * La opacidad del vidrio del tema: la que pidió el proyecto o GLASS_OPACITY.\n *\n * @param {object} theme\n * @returns {number}\n */\nexport function glassOpacityOf(theme) {\n return theme?.toolkit?.glass?.opacity ?? GLASS_OPACITY;\n}\n\n/**\n * El estilo de una superficie de vidrio sobre un color.\n *\n * @param {string} solidColor el color sólido de la superficie (p. ej. palette.background.paper o primary.main)\n * @param {object} [options]\n * @param {number} [options.opacity] por omisión GLASS_OPACITY\n * @param {boolean} [options.isDark] en oscuro el vidrio se aclara un poco y su filo brilla más\n * @param {boolean} [options.isControl] un control chico (botón, chip): sombra corta en vez de la de un panel\n * @returns {object} estilos para styleOverrides o sx\n */\nexport function glassSurfaceOf(solidColor, {opacity = GLASS_OPACITY, isDark = false, isControl = false} = {}) {\n const edge = isDark ? GLASS_EDGE_DARK : GLASS_EDGE_LIGHT;\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(opacity * 100)}%, transparent)`,\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n backgroundImage: 'linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0) 40%)',\n boxShadow: [\n `inset 1px 1px 0 ${edge.rimLight}`,\n `inset -1px -1px 0 ${edge.rimShade}`,\n `inset 0 0 ${INNER_GLOW_PIXELS}px ${edge.innerGlow}`,\n `0 0 0 1px ${edge.outline}`,\n isControl ? edge.controlShadow : edge.shadow,\n ].join(', '),\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * Lo que se pinta en lugar del vidrio si la persona pidió menos transparencia o más contraste, o si el navegador no sabe\n * desenfocar. Para lo transparente que no es una superficie (el fondo oscuro detrás de un diálogo, la perilla del\n * deslizador al arrastrarla).\n *\n * @param {object} solidStyle los estilos sólidos\n * @returns {object}\n */\nexport function solidFallbackOf(solidStyle) {\n return {[REDUCED_TRANSPARENCY_QUERY]: solidStyle, [NO_BACKDROP_FILTER_QUERY]: solidStyle};\n}\n\n/**\n * El vidrio sobre un color del tema en claro y en oscuro, con la opacidad del tema. Es lo que usan los ajustes de\n * componentes, y lo que usa el proyecto para algo suyo que flote:\n * sx={(theme) => glassStylesOf(theme)} // sobre el color de las tarjetas\n * sx={(theme) => glassStylesOf(theme, {color: paletteOf(theme).primary.main})} // vidrio del color principal\n *\n * @param {object} theme el tema de MUI\n * @param {object} [options]\n * @param {string} [options.color] el color de la superficie; por omisión el de las tarjetas (background.paper)\n * @param {string} [options.darkColor] el de modo oscuro, si es otro; por omisión el mismo\n * @param {number} [options.opacity] por omisión la del tema\n * @param {boolean} [options.isControl] un control chico (botón, chip): sombra corta en vez de la de un panel\n * @returns {object}\n */\nexport function glassStylesOf(theme, {color, darkColor, opacity, isControl = false} = {}) {\n const lightColor = color ?? paletteOf(theme).background.paper;\n const glassOpacity = opacity ?? glassOpacityOf(theme);\n return {\n ...lightDarkStylesOf(theme,\n glassSurfaceOf(lightColor, {opacity: glassOpacity, isControl}),\n glassSurfaceOf(darkColor ?? lightColor, {opacity: glassOpacity, isDark: true, isControl})),\n ...lensRulesOf(theme, {lightColor, darkColor: darkColor ?? lightColor, opacity: glassOpacity, isControl}),\n };\n}\n\n/**\n * El LENTE: lo que, según Apple, define al vidrio líquido («The primary way Liquid Glass visually defines itself is through\n * something called Lensing… previous materials scattered light, this new set of materials dynamically bends, shapes, and\n * concentrates light», WWDC25 «Meet Liquid Glass»). La orilla dobla lo de atrás (un filtro SVG de desplazamiento,\n * GlassLensFilter) y el centro queda más claro: menos tinte y menos desenfoque que el esmerilado.\n *\n * Solo en Chrome y sus parientes (Edge, Brave, Arc): son los únicos que aplican un filtro SVG en backdrop-filter; Firefox\n * lo acepta sin pintarlo (el vidrio se quedaría sin desenfoque) y Safari no lo entiende. Por eso va detrás de la marca que\n * GlassLensFilter pone en <body> solo en esos navegadores; en los demás queda el esmerilado. Con «Reducir transparencia» o\n * «Aumentar contraste» se pinta sólido igual que siempre.\n *\n * OJO: el modo oscuro va en su propia regla y no anidado: el selector de oscuro vive en <html> y, anidado debajo de la\n * marca de <body>, buscaría un .dark DENTRO de <body> y nunca coincidiría.\n *\n * @param {object} theme\n * @param {{lightColor: string, darkColor: string, opacity: number, isControl: boolean}} options\n * @returns {object}\n */\nfunction lensRulesOf(theme, {lightColor, darkColor, opacity, isControl}) {\n const lens = isControl ? GLASS_LENS.control : GLASS_LENS.panel;\n const lensOpacity = Math.max(0, opacity - lens.clearerBy);\n const backdrop = `blur(${lens.blurPixels}px) saturate(180%) url(#${lens.filterId})`;\n const lensSurfaceOf = (solidColor, isDark) => {\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(lensOpacity * 100)}%, transparent)`,\n backdropFilter: backdrop,\n WebkitBackdropFilter: backdrop,\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n };\n const lensKey = `${GLASS_LENS_FLAG_SELECTOR} &`;\n // la regla de oscuro del tema: un selector (*:where(.dark) &) o una media query, según cómo elija el modo el proyecto\n const darkKey = Object.keys(theme.applyStyles('dark', {}))[0];\n if (!darkKey) {\n return {[lensKey]: lensSurfaceOf(lightColor, false)};\n }\n const darkLens = darkKey.startsWith('@')\n ? {[darkKey]: {[lensKey]: lensSurfaceOf(darkColor, true)}}\n : {[darkKey.replace('&', lensKey)]: lensSurfaceOf(darkColor, true)};\n return {[lensKey]: lensSurfaceOf(lightColor, false), ...darkLens};\n}\n\n/**\n * El mapa de desplazamiento del lente (una imagen SVG): rojo para lo de lado y verde para lo de arriba abajo; 128 en medio\n * es «no mover». En la orilla de la izquierda y la de arriba vale más (toma de adentro) y en la de la derecha y la de\n * abajo, menos (también de adentro): así la orilla dobla lo de atrás hacia el centro, como una lente convexa, y nunca toma\n * de afuera del vidrio (afuera no hay imagen y la orilla se quedaba en blanco).\n *\n * @param {number} edgeFraction lo ancho de la orilla que dobla (0 a 0.5 del tamaño del vidrio)\n * @returns {string} data URI para el feImage del filtro\n */\nexport function lensDisplacementMapOf(edgeFraction) {\n const inner = 1 - edgeFraction;\n const svg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\" preserveAspectRatio=\"none\"><defs>'\n + `<linearGradient id=\"x\" x1=\"0\" x2=\"1\"><stop offset=\"0\" stop-color=\"#ff0000\"/><stop offset=\"${edgeFraction}\" stop-color=\"#800000\"/>`\n + `<stop offset=\"${inner}\" stop-color=\"#800000\"/><stop offset=\"1\" stop-color=\"#000000\"/></linearGradient>`\n + `<linearGradient id=\"y\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#00ff00\"/><stop offset=\"${edgeFraction}\" stop-color=\"#008000\"/>`\n + `<stop offset=\"${inner}\" stop-color=\"#008000\"/><stop offset=\"1\" stop-color=\"#000000\"/></linearGradient>`\n + '</defs><rect width=\"100\" height=\"100\" fill=\"url(#x)\"/><rect width=\"100\" height=\"100\" fill=\"url(#y)\" style=\"mix-blend-mode:screen\"/></svg>';\n return `data:image/svg+xml,${encodeURIComponent(svg)}`;\n}\n\n/**\n * El material del contenido sobre un color: esmerilado como el vidrio pero sin el brillo del borde, y más transparente.\n *\n * @param {string} solidColor\n * @param {object} [options]\n * @param {number} [options.opacity] la del vidrio; el material le suma MATERIAL_OPACITY_OFFSET de su modo (le resta)\n * @param {boolean} [options.isDark]\n * @returns {object}\n */\nexport function materialSurfaceOf(solidColor, {opacity = GLASS_OPACITY, isDark = false} = {}) {\n const edge = isDark ? MATERIAL_EDGE_DARK : MATERIAL_EDGE_LIGHT;\n const tint = isDark ? `color-mix(in srgb, ${solidColor}, white ${DARK_GLASS_LIFT_PERCENT}%)` : solidColor;\n const offset = isDark ? MATERIAL_OPACITY_OFFSET.dark : MATERIAL_OPACITY_OFFSET.light;\n const materialOpacity = Math.min(1, Math.max(0, opacity + offset));\n return {\n backgroundColor: `color-mix(in srgb, ${tint} ${Math.round(materialOpacity * 100)}%, transparent)`,\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n backgroundImage: 'none',\n boxShadow: `0 0 0 1px ${edge.outline}, ${edge.shadow}`,\n ...solidFallbackOf({backgroundColor: solidColor, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * El material del contenido en claro y en oscuro (las tarjetas con glass: {surfaces: true}). Para algo del proyecto que va\n * en el contenido: sx={(theme) => materialStylesOf(theme)}.\n *\n * @param {object} theme\n * @param {object} [options]\n * @param {string} [options.color] por omisión el de las tarjetas (background.paper)\n * @param {string} [options.darkColor] por omisión el mismo\n * @returns {object}\n */\nexport function materialStylesOf(theme, {color, darkColor} = {}) {\n const lightColor = color ?? paletteOf(theme).background.paper;\n const opacity = glassOpacityOf(theme);\n return lightDarkStylesOf(theme, materialSurfaceOf(lightColor, {opacity}), materialSurfaceOf(darkColor ?? lightColor, {opacity, isDark: true}));\n}\n\n/**\n * Solo el fondo del material, sin su línea ni su sombra: para lo que va DENTRO de una tarjeta y debe tapar lo que pasa por\n * debajo sin verse como un parche sólido (la columna pegada de la tabla al deslizarla de lado). Va como capa\n * (background-image) y no como color: así el color propio del elemento (el del renglón: la cebra, el cursor encima) se\n * sigue pintando encima.\n *\n * Con {thick: true}, el material «grueso» de Apple (HIG «Materials»: los gruesos son para leer sobre otro contenido): el\n * mismo desenfoque al 92 %. Es el de la columna fija de una tabla: con el delgado de la tarjeta (45 a 55 %) se veían pasar\n * por detrás las otras columnas (lo reportaron).\n *\n * @param {object} theme\n * @param {object} [options]\n * @param {boolean} [options.thick] el material grueso, para tapar lo que pasa por debajo\n * @returns {object}\n */\nexport function materialBackgroundOf(theme, {thick = false} = {}) {\n const paper = paletteOf(theme).background.paper;\n const opacityOf = (offset) => (thick ? THICK_MATERIAL_PERCENT : Math.round(Math.min(1, Math.max(0, glassOpacityOf(theme) + offset)) * 100));\n const layerOf = (color) => `linear-gradient(${color}, ${color})`;\n const frostedOf = (tint, opacity) => ({\n backgroundImage: layerOf(`color-mix(in srgb, ${tint} ${opacity}%, transparent)`),\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n });\n return {\n ...lightDarkStylesOf(theme,\n frostedOf(paper, opacityOf(MATERIAL_OPACITY_OFFSET.light)),\n frostedOf(`color-mix(in srgb, ${paper}, white ${DARK_GLASS_LIFT_PERCENT}%)`, opacityOf(MATERIAL_OPACITY_OFFSET.dark))),\n ...solidFallbackOf({backgroundImage: layerOf(paper), backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * La «gota» que marca lo elegido dentro de un control (la pestaña, la opción, el renglón del menú lateral): en claro, una\n * pastilla blanca con su brillo y su sombra; en oscuro, un relleno gris parejo, como el renglón elegido de las barras\n * laterales de Apple en oscuro. No es transparente del todo: va DENTRO de algo que ya puede ser de vidrio (un menú), y\n * vidrio sobre vidrio no se lee.\n * OJO, oscuro: con blanco al 14 % casi no se veía, y su línea de luz arriba dibujaba un arco raro en la esquina («no se\n * nota la selección», «se ve raro»; lo reportaron). Va al 18 % y sin línea.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function glassDropOf(theme) {\n const palette = paletteOf(theme);\n return lightDarkStylesOf(theme, {\n backgroundColor: palette.background.paper,\n color: palette.text.primary,\n boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 1px 3px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.08)',\n }, {\n backgroundColor: `color-mix(in srgb, ${palette.common.white} ${DARK_DROP_WHITE_PERCENT}%, transparent)`,\n boxShadow: '0 1px 3px rgba(0, 0, 0, 0.35)',\n });\n}\n\n/**\n * El relleno de un control lleno de color (el botón principal, la etiqueta elegida) para llevar letra BLANCA y que se lea:\n * el color del tema oscurecido lo justo para llegar a 4.5 : 1 (WCAG). En oscuro la paleta trae letra negra sobre el\n * naranja («El suyo», «Guardar»: se veía feo, lo reportaron) y el blanco sobre el naranja tal cual da 2.85 : 1; con el\n * naranja al 75 % y blanco da 4.79 : 1. Se calcula con el color que elija el proyecto, no con uno fijo.\n *\n * @param {string} rawColor el color en #RRGGBB o #RGB (el de la paleta, no la variable CSS)\n * @returns {string|null} el relleno en #RRGGBB; null si el color no viene en hexadecimal\n */\nexport function whiteLabelFillOf(rawColor) {\n const channels = hexChannelsOf(rawColor);\n if (!channels) {\n return null;\n }\n // del color tal cual hacia el negro, en pasos de 5 %, hasta que la letra blanca se lea\n for (let share = 100; share >= MIN_FILL_SHARE_PERCENT; share -= FILL_SHARE_STEP_PERCENT) {\n const mixed = channels.map((channel) => Math.round((channel * share) / 100));\n if (contrastWithWhiteOf(mixed) >= MIN_TEXT_CONTRAST) {\n return `#${mixed.map((channel) => channel.toString(16).padStart(2, '0')).join('')}`;\n }\n }\n return `#${channels.map((channel) => Math.round((channel * MIN_FILL_SHARE_PERCENT) / 100).toString(16).padStart(2, '0')).join('')}`;\n}\n\n/**\n * Los tres canales de un color #RRGGBB o #RGB (0 a 255); null si no es hexadecimal.\n *\n * @param {string} color\n * @returns {number[]|null}\n */\nfunction hexChannelsOf(color) {\n const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(color ?? '').trim());\n if (!match) {\n return null;\n }\n const digits = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join('') : match[1];\n return [0, 2, 4].map((start) => parseInt(digits.slice(start, start + 2), 16));\n}\n\n/**\n * El contraste de la letra blanca sobre un color (fórmula de WCAG).\n *\n * @param {number[]} channels\n * @returns {number}\n */\nfunction contrastWithWhiteOf(channels) {\n const [red, green, blue] = channels.map((channel) => {\n const unit = channel / 255;\n return unit <= 0.03928 ? unit / 12.92 : ((unit + 0.055) / 1.055) ** 2.4;\n });\n const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;\n return 1.05 / (luminance + 0.05);\n}\n\n/**\n * Los tonos de los campos por omisión, por modo: fill (el relleno), hoverFill (con el cursor encima), border y hoverBorder\n * (la orilla; transparente: el pozo de Apple no lleva). createToolkitTheme los pone en la paleta de cada modo\n * (palette.field) y el proyecto los cambia con `fields` o con su paleta: blancos con borde, del tono de su marca…\n */\nexport const FIELD_TONES = Object.freeze({\n light: Object.freeze({fill: FIELD_WELL_FILL.light.rest, hoverFill: FIELD_WELL_FILL.light.hover, border: 'transparent', hoverBorder: 'transparent'}),\n dark: Object.freeze({fill: FIELD_WELL_FILL.dark.rest, hoverFill: FIELD_WELL_FILL.dark.hover, border: 'transparent', hoverBorder: 'transparent'}),\n});\n\n/**\n * Los tonos de los campos del tema (palette.field, que llena createToolkitTheme; con sus variables CSS cambian solos con el\n * modo, sin reglas de oscuro), o null si el tema no los trae (un createTheme de MUI sin Calcita).\n *\n * @param {object} theme\n * @returns {{fill: string, hoverFill: string, border: string, hoverBorder: string}|null}\n */\nexport function fieldPaletteOf(theme) {\n return paletteOf(theme).field ?? null;\n}\n\n/** La orilla de un campo: una línea por dentro (sombra inset), que no mueve el texto ni pelea con el aro del foco (outline). */\nfunction fieldEdgeOf(color) {\n return `inset 0 0 0 1px ${color}`;\n}\n\n// los campos de un renglón de FormSection son cápsulas. OJO: así se aprobaron, aunque salían por accidente: el renglón\n// decía 12 y sx lo multiplicaba por el radio del tema (96 px). Con la variable el número se lee en px, así que la cápsula\n// se escribe tal cual (999 px: con 40 px de alto da lo mismo que 96)\nconst FORM_ROW_FIELD_RADIUS = '999px';\n\n/** Un radio en px: un número lleva «px» (en sx un número se multiplica por el radio del tema); un texto va tal cual. */\nfunction pixelsOf(radius) {\n return typeof radius === 'number' ? `${radius}px` : radius;\n}\n\n/**\n * Las esquinas de los campos: las que pidió el proyecto en su tema (createToolkitTheme fields.radius; un número va en px\n * en todos lados), o la variable --calcita-field-radius con las de cada estilo de respaldo.\n */\nfunction fieldRadiusOf(theme, lookRadius) {\n const projectRadius = theme.toolkit?.fields?.radius;\n return projectRadius !== undefined && projectRadius !== null ? pixelsOf(projectRadius) : cssTokenOf('fieldRadius', pixelsOf(lookRadius));\n}\n\n/**\n * El «pozo» de un campo de texto dentro del contenido (una tarjeta, un formulario agrupado), como los de Apple: un relleno\n * parejo con esquinas de 12 px, del gris de relleno de Apple («tertiary fill»): tenue en claro y más claro que la tarjeta en\n * oscuro (con negro, en oscuro se perdía). Sin vidrio adentro de la tarjeta: ahí no tiene nada que dejar\n * ver. Con el cursor encima se marca apenas; antes el relleno salía solo con el cursor y en cuadro («hovers espantosos»).\n * Los tonos y las esquinas los puede cambiar el proyecto (createToolkitTheme fields).\n *\n * @param {object} theme\n * @param {{radius?: number}} [options] las esquinas del estilo que lo pide (el minimalista las tiene más chicas)\n * @returns {object}\n */\nexport function fieldWellStylesOf(theme, {radius = FIELD_WELL_RADIUS_PIXELS} = {}) {\n const base = {backgroundImage: 'none', backdropFilter: 'none', WebkitBackdropFilter: 'none', borderRadius: fieldRadiusOf(theme, radius)};\n const field = fieldPaletteOf(theme);\n if (field) {\n return {\n ...base,\n backgroundColor: field.fill,\n boxShadow: fieldEdgeOf(field.border),\n '&:hover': {backgroundColor: field.hoverFill, boxShadow: fieldEdgeOf(field.hoverBorder)},\n };\n }\n const {light, dark} = FIELD_TONES;\n return {\n ...base,\n ...lightDarkStylesOf(theme,\n {backgroundColor: light.fill, boxShadow: fieldEdgeOf(light.border), '&:hover': {backgroundColor: light.hoverFill, boxShadow: fieldEdgeOf(light.hoverBorder)}},\n {backgroundColor: dark.fill, boxShadow: fieldEdgeOf(dark.border), '&:hover': {backgroundColor: dark.hoverFill, boxShadow: fieldEdgeOf(dark.hoverBorder)}}),\n };\n}\n\n/**\n * Los tonos de los campos como variables (--calcita-field-*) para un contenedor: el renglón de FormSection las pone en sí\n * mismo y sus campos las leen (fieldWellFromVariablesOf). OJO, trampa pagada: el renglón le gana al tema con selectores\n * largos («html body &&& .MuiOutlinedInput-root») y una regla de oscuro anidada ahí abajo nunca aplicaba (en oscuro el\n * campo salía con el gris de claro). En el contenedor la regla de oscuro sí aplica, y los campos heredan la variable.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function fieldVariablesOf(theme) {\n const variablesOf = (tones) => ({\n '--calcita-field-fill': tones.fill,\n '--calcita-field-hover-fill': tones.hoverFill,\n '--calcita-field-edge': fieldEdgeOf(tones.border),\n '--calcita-field-hover-edge': fieldEdgeOf(tones.hoverBorder),\n });\n const field = fieldPaletteOf(theme);\n if (field) {\n return variablesOf(field);\n }\n return {...variablesOf(FIELD_TONES.light), ...theme.applyStyles('dark', variablesOf(FIELD_TONES.dark))};\n}\n\n/**\n * El pozo de un campo leyendo las variables de su contenedor (fieldVariablesOf).\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function fieldWellFromVariablesOf(theme) {\n return {\n backgroundImage: 'none',\n backdropFilter: 'none',\n WebkitBackdropFilter: 'none',\n borderRadius: fieldRadiusOf(theme, FORM_ROW_FIELD_RADIUS),\n backgroundColor: 'var(--calcita-field-fill)',\n boxShadow: 'var(--calcita-field-edge)',\n '&:hover': {backgroundColor: 'var(--calcita-field-hover-fill)', boxShadow: 'var(--calcita-field-hover-edge)'},\n };\n}\n\n/**\n * Un campo de texto suelto sobre el lienzo (fuera de una tarjeta): una lámina de vidrio clara en claro y el gris de relleno\n * de Apple en oscuro, más claro que el fondo casi negro. Con «Reducir transparencia» se pinta sólido.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function canvasFieldStylesOf(theme) {\n const paper = paletteOf(theme).background.paper;\n return {\n backgroundImage: 'none',\n backdropFilter: GLASS_BLUR,\n WebkitBackdropFilter: GLASS_BLUR,\n borderRadius: cssTokenOf('fieldRadius', `${FIELD_WELL_RADIUS_PIXELS}px`),\n ...lightDarkStylesOf(theme, {\n backgroundColor: CANVAS_FIELD.light.rest,\n boxShadow: `0 0 0 1px ${CANVAS_FIELD.light.edge}`,\n '&:hover': {backgroundColor: CANVAS_FIELD.light.hover},\n }, {\n backgroundColor: CANVAS_FIELD.dark.rest,\n boxShadow: `0 0 0 1px ${CANVAS_FIELD.dark.edge}`,\n '&:hover': {backgroundColor: CANVAS_FIELD.dark.hover},\n }),\n ...solidFallbackOf({backgroundColor: paper, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n\n/**\n * El color de un TEXTO que debe ir del color del tema (una liga, un botón de solo texto, el renglón elegido del menú, el\n * valor de una fecha): el color de la paleta oscurecido en claro y aclarado en oscuro, para que se lea. Medido (WCAG pide\n * 4.5 : 1 en letra normal): el naranja de la marca sobre una tarjeta clara daba 2.78 : 1 y sobre su propio tinte en oscuro\n * 4.41 : 1 (el reporte: «cuesta leer», «se pierde»); con 30 % de negro en claro da 5.24 : 1 y con 30 % de blanco en\n * oscuro 8.23 : 1. Los botones con relleno de color NO usan esto: van con letra de un solo color (HIG «Buttons»: «Avoid\n * applying a similar color to button labels and content layer backgrounds»).\n *\n * @param {object} theme\n * @param {string} [colorName] primary, error, success… (por omisión primary)\n * @returns {object} {color, y el de oscuro}\n */\nexport function accentTextStylesOf(theme, colorName = 'primary') {\n const palette = paletteOf(theme);\n const main = (palette[colorName] ?? palette.primary).main;\n const keep = 100 - ACCENT_TEXT_SHADE_PERCENT;\n return lightDarkStylesOf(theme, {color: `color-mix(in srgb, ${main} ${keep}%, black)`}, {color: `color-mix(in srgb, ${main} ${keep}%, white)`});\n}\n\n/**\n * Un fondo FIJO a la ventana que no se mueve al deslizar, también en el iPhone: va en una capa aparte (::before con\n * position: fixed) detrás del contenido del elemento. Así el vidrio se ve pasar por encima de un fondo quieto.\n *\n * OJO: background-attachment: fixed no sirve para esto. Safari del iPhone y del iPad lo ignora y el fondo se movía con\n * todo (lo vio el autor en el teléfono); además, en escritorio vuelve a pintar todo el fondo en cada paso al deslizar.\n * La capa va con z-index -1 dentro de un contexto propio (isolation): queda arriba del color del elemento y abajo de su\n * contenido. Si el elemento es una caja con esquinas (la escena de un ejemplo), clip-path la recorta a la caja.\n *\n * @param {object} layerStyles el fondo de la capa (backgroundColor, backgroundImage…; puede llevar el modo oscuro)\n * @param {object} [options]\n * @param {string} [options.clipPath] p. ej. 'inset(0 round 20px)' para una caja; sin él, la capa ocupa toda la ventana\n * @returns {object} estilos para el sx del elemento\n */\nexport function fixedBackgroundLayerOf(layerStyles, {clipPath} = {}) {\n return {\n isolation: 'isolate',\n ...(clipPath && {clipPath}),\n // OJO, iPhone: con inset: 0 el alto de la capa cambia cuando la barra de Safari se esconde al deslizar, y el fondo\n // «seguía al scroll» (lo vio el autor en su teléfono). Medida fija de toda la pantalla (100vw × 100lvh, el alto con\n // la barra escondida; 100vh donde no hay lvh) y su propia capa de composición (translateZ), que es lo que la deja\n // quieta en WebKit\n '&::before': {\n content: '\"\"',\n position: 'fixed',\n top: 0,\n left: 0,\n width: '100vw',\n height: '100vh',\n '@supports (height: 100lvh)': {height: '100lvh'},\n transform: 'translateZ(0)',\n willChange: 'transform',\n zIndex: -1,\n pointerEvents: 'none',\n ...layerStyles,\n },\n };\n}\n\n/**\n * El fondo del lienzo con vidrio: manchas del color principal y del de información, fijas a la ventana, para\n * que el vidrio tenga algo que desenfocar. AppLayout ya lo pone; una pantalla fuera del marco (el inicio de sesión) lo usa\n * con sx={(theme) => ambientBackgroundOf(theme)}.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function ambientBackgroundOf(theme) {\n const palette = paletteOf(theme);\n const washOf = (strength, tones) => [\n `radial-gradient(60vmax 45vmax at 0% 0%, color-mix(in srgb, ${tones.primary} ${strength.primary}%, transparent), transparent 70%)`,\n `radial-gradient(50vmax 40vmax at 100% 15%, color-mix(in srgb, ${tones.info} ${strength.info}%, transparent), transparent 70%)`,\n `radial-gradient(55vmax 45vmax at 70% 100%, color-mix(in srgb, ${tones.accent} ${strength.accent}%, transparent), transparent 70%)`,\n `radial-gradient(45vmax 35vmax at 45% 50%, color-mix(in srgb, ${tones.info} ${strength.center}%, transparent), transparent 70%)`,\n ].join(', ');\n const lightTones = {primary: palette.primary.main, info: palette.info.main, accent: palette.primary.light};\n // en oscuro, los tonos OSCUROS de la paleta sobre un casi negro: el color tiñe sin aclarar el fondo, y las tarjetas y el\n // vidrio (más claros) no se pierden (ver DARK_CANVAS)\n const darkTones = {primary: palette.primary.dark, info: palette.info.dark, accent: palette.primary.dark};\n return {\n ...lightDarkStylesOf(theme, {backgroundColor: palette.background.default}, {backgroundColor: DARK_CANVAS.base}),\n ...fixedBackgroundLayerOf(lightDarkStylesOf(theme,\n {backgroundColor: palette.background.default, backgroundImage: washOf(AMBIENT_STRENGTH_LIGHT, lightTones)},\n {backgroundColor: DARK_CANVAS.base, backgroundImage: washOf(AMBIENT_STRENGTH_DARK, darkTones)})),\n };\n}\n\n// la orilla de arriba con la barra en cápsulas: cuánto tinte del lienzo lleva y cuánto desenfoca (px)\nconst SCROLL_EDGE_TINT_PERCENT = 55;\nconst SCROLL_EDGE_BLUR_PIXELS = 12;\n\n/**\n * La orilla de arriba cuando la barra va en cápsulas (TOP_BAR.capsules con vidrio, como las ventanas de macOS Tahoe; Apple le\n * llama «scroll edge effect»): lo que pasa por debajo se desenfoca y se desvanece hacia el contenido, para que el título\n * y las cápsulas se lean sobre cualquier cosa. Con «Reducir transparencia» o sin backdrop-filter, el color del lienzo.\n *\n * @param {object} theme\n * @returns {object}\n */\nexport function scrollEdgeStylesOf(theme) {\n const palette = paletteOf(theme);\n const tintOf = (canvas) => `color-mix(in srgb, ${canvas} ${SCROLL_EDGE_TINT_PERCENT}%, transparent)`;\n const blur = `blur(${SCROLL_EDGE_BLUR_PIXELS}px)`;\n // de arriba (cubre) hacia abajo (se desvanece): sin raya donde termina\n const fade = 'linear-gradient(to bottom, black 45%, transparent)';\n return {\n ...lightDarkStylesOf(theme, {backgroundColor: tintOf(palette.background.default)}, {backgroundColor: tintOf(DARK_CANVAS.base)}),\n backdropFilter: blur,\n WebkitBackdropFilter: blur,\n maskImage: fade,\n WebkitMaskImage: fade,\n ...solidFallbackOf({backgroundColor: palette.background.default, backdropFilter: 'none', WebkitBackdropFilter: 'none'}),\n };\n}\n"],"names":[],"mappings":"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"}
@@ -6,8 +6,9 @@ export declare const MENU_POSITION: Readonly<{
6
6
  /**
7
7
  * @param {object} props
8
8
  * @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
9
- * @param {import('react').ReactNode} [props.wordmark] el logo ancho, la marca con su nombre en una sola imagen (como el de muchas
10
- * empresas): 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ú
9
+ * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo
10
+ * ancho, la marca con su nombre en una sola imagen (como el de muchas empresas), o uno para cada modo ({light, dark}: el de
11
+ * 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ú
11
12
  * encogido se queda el logo chico (logo), si lo hay. brandName sigue siendo su nombre para el lector de pantalla
12
13
  * @param {string} [props.brandName] el nombre de la app; por omisión el appName de ToolkitProvider
13
14
  * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)
@@ -44,7 +45,10 @@ export declare const MENU_POSITION: Readonly<{
44
45
  */
45
46
  export declare const AppLayout: (props: {
46
47
  logo?: import('react').ReactNode;
47
- wordmark?: import('react').ReactNode;
48
+ wordmark?: import('react').ReactNode | {
49
+ light?: import('react').ReactNode;
50
+ dark?: import('react').ReactNode;
51
+ };
48
52
  brandName?: string;
49
53
  brandCaption?: string;
50
54
  homePath?: string;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @param {object} props
3
3
  * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), el que se queda con el menú encogido
4
- * @param {import('react').ReactNode} [props.wordmark] el logo ancho (en lugar del logo y el nombre arriba)
4
+ * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} [props.wordmark] el logo
5
+ * ancho (en lugar del logo y el nombre arriba), o uno para cada modo ({light, dark})
5
6
  * @param {string} props.brandName el nombre de la app
6
7
  * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)
7
8
  * @param {string} props.homePath
@@ -17,7 +18,10 @@
17
18
  */
18
19
  export default function SideMenu(props: {
19
20
  logo?: import('react').ReactNode;
20
- wordmark?: import('react').ReactNode;
21
+ wordmark?: import('react').ReactNode | {
22
+ light?: import('react').ReactNode;
23
+ dark?: import('react').ReactNode;
24
+ };
21
25
  brandName: string;
22
26
  brandCaption?: string;
23
27
  homePath: string;
@@ -1,6 +1,18 @@
1
1
  /**
2
- * @param {{children: import('react').ReactNode}} props el logo (una <img>, un <svg> o lo que pinte el proyecto)
2
+ * ¿Llegó una imagen para cada modo ({light, dark}) y no un logo solo?
3
+ *
4
+ * @param {unknown} wordmark
5
+ * @returns {boolean}
6
+ */
7
+ export declare function isWordmarkPair(wordmark: unknown): boolean;
8
+ /**
9
+ * @param {object} props
10
+ * @param {import('react').ReactNode | {light?: import('react').ReactNode, dark?: import('react').ReactNode}} props.children el
11
+ * logo (una <img>, un <svg> o lo que pinte el proyecto) o uno para cada modo: {light, dark}
3
12
  */
4
13
  export default function Wordmark(props: {
5
- children: import('react').ReactNode;
14
+ children: import('react').ReactNode | {
15
+ light?: import('react').ReactNode;
16
+ dark?: import('react').ReactNode;
17
+ };
6
18
  }): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackcraftmx/calcita",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
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",