import { View } from 'app/design/view'
import { appSetting, menuItemsByName, menuItemsByNameNew } from 'app/lib/util'
import { useTranslation } from 'react-i18next';
import { useMemo, memo } from 'react';
import { NeoButtonLink } from 'app/design/controls'
import { useCurrentUserNoCounters } from 'app/context/user';
import { useMenuData } from 'app/context/menu-data';
import { useLayoutSettings } from 'app/context/layout-settings';
import DropdownMenu from 'app/ui/atoms/dropdown-menu';
import { SOUND_LEVEL_ITEMS, useSoundsLevel } from 'app/lib/hooks/use-sounds-level';
export const MENU_FOOTER_CLASSES = 'flex w-full items-center border-t border-border/60 justify-center flex-row flex-wrap gap-1 p-4 min-h-16';
// Settings use the historical misspelling; keep this key aligned with settings/layout.js.
const AVAILABLE_LANGS_SETTING_KEY = 'avaliable_langs';
const FOOTER_THEME_ITEMS = [
{ id: 'auto', titleKey: 'theme_footer_auto', labelKey: 'theme_level_auto', icon: 'Eclipse' },
{ id: 'light', titleKey: 'theme_footer_light', labelKey: 'theme_level_light', icon: 'Sun' },
{ id: 'dark', titleKey: 'theme_footer_dark', labelKey: 'theme_level_dark', icon: 'Moon' },
];
const linkVariantToNeoStyle = {
default: 'plain',
secondary: 'borderless',
accent: 'link',
primary: 'borderedProminent',
ghost: 'borderless',
link: 'link',
};
const linkSizeToNeoControlSize = {
xs: 'mini',
sm: 'small',
md: 'regular',
base: 'regular',
lg: 'large',
};
const normalizeFooterHref = (href = '') => {
const value = typeof href === 'string' ? href.trim() : '';
if (!value) return '';
if (
value.startsWith('/') ||
value.startsWith('#') ||
value.startsWith('//') ||
/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value)
) {
return value;
}
return `/${value}`;
};
const resolveFooterLang = (langs, lang, langCode) => {
if (lang && langs.includes(lang)) return lang;
if (langCode && langs.includes(langCode)) return langCode;
return langs[0] || '';
};
const menuFooterSkeletonWidths = ['w-12', 'w-12', 'w-12', 'w-12', 'w-12'];
function MenuFooterSkeleton({ cntClasses, children, role }) {
return (
{menuFooterSkeletonWidths.map((width, index) => (
))}
{children}
);
}
function FooterThemeSwitcher({ visualProps }) {
const { t } = useTranslation();
const { themeName, setThemeName } = useLayoutSettings();
const currentThemeName = themeName || 'auto';
const items = useMemo(() => (
FOOTER_THEME_ITEMS.map((theme) => ({
key: theme.id,
id: theme.id,
name: theme.id,
title: t(theme.titleKey),
icon: theme.icon,
selected: currentThemeName === theme.id,
}))
), [currentThemeName, t]);
const currentThemeItem = FOOTER_THEME_ITEMS.find((theme) => theme.id === currentThemeName) ?? FOOTER_THEME_ITEMS[0];
const themeLabel = t(currentThemeItem.labelKey);
// Icon only: the icon shows the current theme, and the menu names each one.
return (
setThemeName(item.id)}
buttonProps={{
image: currentThemeItem.icon,
accessibilityLabel: themeLabel,
tooltip: themeLabel,
style: visualProps.style,
controlSize: visualProps.controlSize,
className: visualProps.className,
textClassName: visualProps.textClassName,
}}
/>
);
}
function FooterSoundsLevel({ visualProps }) {
const { t } = useTranslation();
const { level, setLevel } = useSoundsLevel();
// The chosen level answers itself (`setLevel` plays it at the new level), so its option makes no sound of its own.
const items = SOUND_LEVEL_ITEMS.map((option) => ({
key: option.id,
id: option.id,
name: option.id,
title: t(option.titleKey),
icon: option.icon,
selected: level === option.id,
sound: false,
}));
const current = SOUND_LEVEL_ITEMS.find((option) => option.id === level) ?? SOUND_LEVEL_ITEMS[0];
const label = t(current.labelKey);
// Icon only, like the theme switcher: the icon shows the level, and the menu names each one.
return (
setLevel(item.id)}
buttonProps={{
image: current.icon,
accessibilityLabel: label,
tooltip: label,
style: visualProps.style,
controlSize: visualProps.controlSize,
className: visualProps.className,
textClassName: visualProps.textClassName,
}}
/>
);
}
function FooterLanguageSwitcher({ visualProps }) {
const { t } = useTranslation();
const { lang, langCode, setLang } = useLayoutSettings();
const configuredLangs = appSetting('layout', AVAILABLE_LANGS_SETTING_KEY);
const langs = Array.isArray(configuredLangs) ? configuredLangs : [];
const currentLang = resolveFooterLang(langs, lang, langCode);
const items = useMemo(() => (
langs.map((language) => ({
key: language,
id: language,
name: language,
title: t('lang_' + language),
icon: 'Languages',
selected: currentLang === language,
}))
), [currentLang, langs, t]);
if (langs.length <= 1 || !currentLang) return null;
const langLabel = t('lang_' + currentLang);
return (
setLang(item.id)}
buttonProps={{
label: langLabel,
image: 'Languages',
accessibilityLabel: langLabel,
style: visualProps.style,
controlSize: visualProps.controlSize,
className: visualProps.className,
textClassName: visualProps.textClassName,
}}
/>
);
}
function MenuFooterComponent({
cntClasses = MENU_FOOTER_CLASSES,
btnStyle,
menu_items,
variant,
size,
itemClassName,
}) {
const { t } = useTranslation();
const currentUser = useCurrentUserNoCounters();
const { hydrated } = useLayoutSettings();
const usesRemoteFooter = !menu_items && appSetting('layout', 'user_remote_config');
const showThemeSwitcher = appSetting('dashboard', 'switch_theme');
const { available: soundsAvailable } = useSoundsLevel();
const configuredLangs = appSetting('layout', AVAILABLE_LANGS_SETTING_KEY);
const showLanguageSwitcher = hydrated && Array.isArray(configuredLangs) && configuredLangs.length > 1;
const showSoundsLevel = hydrated && soundsAvailable;
const { menuData, isFetched, isLoading } = useMenuData(
(menu_items || !hydrated) ? null : appSetting('menu_items', 'objects', 'footer')
);
const visualProps = useMemo(() => {
const legacy = btnStyle || {};
const legacyVariant = variant ?? legacy.variant ?? 'accent';
const legacySize = size ?? legacy.size ?? 'sm';
return {
style: legacy.style ?? linkVariantToNeoStyle[legacyVariant] ?? 'link',
controlSize: legacy.controlSize ?? linkSizeToNeoControlSize[legacySize] ?? 'small',
className: legacy.containerClassName ?? '',
textClassName: itemClassName ?? legacy.textClassName ?? legacy.className ?? '',
};
}, [btnStyle, variant, size, itemClassName]);
const menu_launcher_items = useMemo(() => (
menu_items || (usesRemoteFooter
? menuItemsByNameNew('menu_post', menuData, currentUser)
: menuItemsByName('', appSetting('menu_items', 'menu_footer'), currentUser))
), [menu_items, usesRemoteFooter, menuData, currentUser]);
if (usesRemoteFooter && (!hydrated || isLoading || !isFetched)) {
return (
{showLanguageSwitcher ? : null}
{showThemeSwitcher ? : null}
{showSoundsLevel ? : null}
);
}
if (menu_launcher_items.length === 0 && menuData && !showThemeSwitcher && !showLanguageSwitcher && !showSoundsLevel)
return null;
return (
{menu_launcher_items.map((item, index) => (
))}
{showLanguageSwitcher ? : null}
{showThemeSwitcher ? : null}
{showSoundsLevel ? : null}
);
}
export default memo(MenuFooterComponent);