/******************************************************************************** * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * https://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ import { FunctionComponent, useContext, useState } from 'react'; import { Box, Container, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import type { Theme } from '@mui/material/styles'; import { Link as RouteLink } from 'react-router'; import { KbdKey } from '../components/kbd-key'; import { useShortcut } from '../hooks/use-shortcut'; import { Eyebrow, accentHover, focusOutline } from '../components/page-primitives'; import { MONO_FONT } from '../default/theme'; import { CustomFooterSettings, StructuredFooterSettings } from '../page-settings'; import { MainContext } from '../context'; const footerLinkStyles = ({ theme }: { theme: Theme }) => ({ fontSize: '0.8125rem', color: theme.palette.text.secondary, textDecoration: 'none', display: 'block', '&:hover': { color: theme.palette.secondary.light }, ...focusOutline(theme) }); const FooterLink = styled('a')(footerLinkStyles); const FooterRouteLink = styled(RouteLink)(footerLinkStyles); const SocialIconButton = styled('a')(({ theme }) => ({ width: 34, height: 34, borderRadius: theme.shape.borderRadius, border: `1px solid ${theme.palette.divider}`, backgroundColor: theme.palette.background.paper, color: theme.palette.text.secondary, display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', transition: 'border-color 0.14s, color 0.14s', ...accentHover(theme), ...focusOutline(theme) })); const ShortcutsButton = styled('button')(({ theme }) => ({ background: 'none', border: 'none', cursor: 'pointer', padding: 0, display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem', color: theme.palette.text.disabled, '&:hover': { color: theme.palette.secondary.light }, ...focusOutline(theme) })); /** Legacy footer chrome: fixed to the viewport bottom as on pre-redesign layouts, hover toggles `expanded`. */ const LegacyFooterRoot = styled('footer')(({ theme }) => ({ position: 'fixed', bottom: 0, zIndex: 50, width: '100%', padding: `${theme.spacing(1)} ${theme.spacing(2)}`, backgroundColor: theme.palette.background.paper, boxShadow: '0px -2px 6px 0px rgba(0, 0, 0, 0.5)', backgroundImage: theme.palette.mode === 'dark' ? 'linear-gradient(rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.08))' : undefined })); const LegacyFooter: FunctionComponent<{ footer: CustomFooterSettings }> = ({ footer }) => { const Content = footer.content; const [expanded, setExpanded] = useState(false); return ( setExpanded(true)} onMouseLeave={() => setExpanded(false)}> ); }; export interface AppFooterProps { onOpenShortcuts: () => void; } export const AppFooter: FunctionComponent = ({ onOpenShortcuts }) => { const { pageSettings, version } = useContext(MainContext); const footer = pageSettings.elements.footer; // The shortcuts button (and its ? hint) only renders in the structured footer, // so register the shortcut here and disable it for custom/legacy footers. const isStructuredFooter = typeof footer !== 'function' && !(footer && 'content' in footer); useShortcut({ key: '?', label: 'Show keyboard shortcuts', order: 0, callback: onOpenShortcuts, enabled: isStructuredFooter }); if (typeof footer === 'function') { const CustomFooter = footer; return ; } if (footer && 'content' in footer) { return ; } return ; }; interface StructuredFooterProps { footer?: StructuredFooterSettings; version: string | null; onOpenShortcuts: () => void; } const StructuredFooter: FunctionComponent = ({ footer, version, onOpenShortcuts }) => ( {footer && (footer.brand || footer.columns) && ( {footer.brand && ( {footer.brand.logo} {footer.brand.name} {footer.brand.description && ( {footer.brand.description} )} {footer.social && footer.social.length > 0 && ( {footer.social.map(s => ( {s.icon} ))} )} )} {footer.columns?.map((column, ci) => ( {column.heading} {column.links.map((l, li) => !l.external && l.href.startsWith('/') && !l.href.startsWith('//') ? ( {l.label} ) : ( {l.label} ) )} ))} )} {footer?.copyright} {footer?.extra} Keyboard shortcuts ? {footer?.showVersion && version && ( {version} )} );