1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

Closes #140: Genericize footer links and menus and allow multiple definitions

This commit is contained in:
checktheroads
2021-05-30 01:05:07 -07:00
parent 69f21a4d64
commit c0914f6216
8 changed files with 157 additions and 72 deletions

View File

@@ -1,16 +1,37 @@
import { useMemo } from 'react';
import { Button, Menu, MenuButton, MenuList } from '@chakra-ui/react';
import { Markdown } from '~/components';
import { useColorValue, useBreakpointValue } from '~/context';
import { useOpposingColor } from '~/hooks';
import { useColorValue, useBreakpointValue, useConfig } from '~/context';
import { useOpposingColor, useStrf } from '~/hooks';
import type { IConfig } from '~/types';
import type { TFooterButton } from './types';
/**
* Filter the configuration object based on values that are strings for formatting.
*/
function getConfigFmt(config: IConfig): Record<string, string> {
const fmt = {} as Record<string, string>;
for (const [k, v] of Object.entries(config)) {
if (typeof v === 'string') {
fmt[k] = v;
}
}
return fmt;
}
export const FooterButton: React.FC<TFooterButton> = (props: TFooterButton) => {
const { content, title, side, ...rest } = props;
const config = useConfig();
const fmt = useMemo(() => getConfigFmt(config), []);
const fmtContent = useStrf(content, fmt);
const placement = side === 'left' ? 'top' : side === 'right' ? 'top-end' : undefined;
const bg = useColorValue('white', 'gray.900');
const color = useOpposingColor(bg);
const size = useBreakpointValue({ base: 'xs', lg: 'sm' });
return (
<Menu placement={placement} preventOverflow isLazy>
<MenuButton
@@ -32,11 +53,12 @@ export const FooterButton: React.FC<TFooterButton> = (props: TFooterButton) => {
boxShadow="2xl"
textAlign="left"
overflowY="auto"
whiteSpace="normal"
mx={{ base: 1, lg: 2 }}
maxW={{ base: '100%', lg: '50vw' }}
{...rest}
>
<Markdown content={content} />
<Markdown content={fmtContent} />
</MenuList>
</Menu>
);

View File

@@ -1,27 +1,43 @@
import { useMemo } from 'react';
import dynamic from 'next/dynamic';
import { Button, Flex, Link, Icon, HStack, useToken } from '@chakra-ui/react';
import { Flex, Icon, HStack, useToken } from '@chakra-ui/react';
import { If } from '~/components';
import { useConfig, useMobile, useColorValue, useBreakpointValue } from '~/context';
import { useStrf } from '~/hooks';
import { FooterButton } from './button';
import { ColorModeToggle } from './colorMode';
import { FooterLink } from './link';
import { isLink, isMenu } from './types';
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
import type { TLink, TMenu } from '~/types';
const CodeIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiCode));
const ExtIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/go').then(i => i.GoLinkExternal));
function buildItems(links: TLink[], menus: TMenu[]): [(TLink | TMenu)[], (TLink | TMenu)[]] {
const leftLinks = links.filter(link => link.side === 'left');
const leftMenus = menus.filter(menu => menu.side === 'left');
const rightLinks = links.filter(link => link.side === 'right');
const rightMenus = menus.filter(menu => menu.side === 'right');
const left = [...leftLinks, ...leftMenus].sort((a, b) => (a.order > b.order ? 1 : -1));
const right = [...rightLinks, ...rightMenus].sort((a, b) => (a.order > b.order ? 1 : -1));
return [left, right];
}
export const Footer: React.FC = () => {
const { web, content, primary_asn } = useConfig();
const footerBg = useColorValue('blackAlpha.50', 'whiteAlpha.100');
const footerColor = useColorValue('black', 'white');
const extUrl = useStrf(web.external_link.url, { primary_asn }) ?? '/';
const size = useBreakpointValue({ base: useToken('sizes', 4), lg: useToken('sizes', 6) });
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
const isMobile = useMobile();
const [left, right] = useMemo(() => buildItems(web.links, web.menus), []);
return (
<HStack
px={6}
@@ -30,30 +46,42 @@ export const Footer: React.FC = () => {
zIndex={1}
as="footer"
bg={footerBg}
whiteSpace="nowrap"
color={footerColor}
spacing={{ base: 8, lg: 6 }}
d={{ base: 'inline-block', lg: 'flex' }}
overflowY={{ base: 'auto', lg: 'unset' }}
justifyContent={{ base: 'center', lg: 'space-between' }}
>
<If c={web.terms.enable}>
<FooterButton side="left" content={content.terms} title={web.terms.title} />
</If>
<If c={web.help_menu.enable}>
<FooterButton side="left" content={content.help_menu} title={web.help_menu.title} />
</If>
<If c={web.external_link.enable}>
<Button
as={Link}
isExternal
href={extUrl}
size={btnSize}
variant="ghost"
rightIcon={<ExtIcon />}
aria-label={web.external_link.title}
>
{web.external_link.title}
</Button>
</If>
{left.map(item => {
if (isLink(item)) {
const url = useStrf(item.url, { primary_asn }) ?? '/';
const icon: Partial<ButtonProps & LinkProps> = {};
if (item.show_icon) {
icon.rightIcon = <ExtIcon />;
}
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
} else if (isMenu(item)) {
return (
<FooterButton key={item.title} side="left" content={item.content} title={item.title} />
);
}
})}
{!isMobile && <Flex p={0} flex="1 0 auto" maxWidth="100%" mr="auto" />}
{right.map(item => {
if (isLink(item)) {
const url = useStrf(item.url, { primary_asn }) ?? '/';
const icon: Partial<ButtonProps & LinkProps> = {};
if (item.show_icon) {
icon.rightIcon = <ExtIcon />;
}
return <FooterLink href={url} title={item.title} {...icon} />;
} else if (isMenu(item)) {
return <FooterButton side="right" content={item.content} title={item.title} />;
}
})}
<If c={web.credit.enable}>
<FooterButton
side="right"

View File

@@ -0,0 +1,13 @@
import { Button, Link, useBreakpointValue } from '@chakra-ui/react';
import type { TFooterLink } from './types';
export const FooterLink: React.FC<TFooterLink> = (props: TFooterLink) => {
const { title } = props;
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
return (
<Button as={Link} isExternal size={btnSize} variant="ghost" aria-label={title} {...props}>
{title}
</Button>
);
};

View File

@@ -1,4 +1,5 @@
import type { ButtonProps, MenuListProps } from '@chakra-ui/react';
import type { ButtonProps, LinkProps, MenuListProps } from '@chakra-ui/react';
import type { TLink, TMenu } from '~/types';
type TFooterSide = 'left' | 'right';
@@ -8,8 +9,18 @@ export interface TFooterButton extends Omit<MenuListProps, 'title'> {
content: string;
}
export type TFooterLink = ButtonProps & LinkProps & { title: string };
export type TFooterItems = 'help' | 'credit' | 'terms';
export interface TColorModeToggle extends ButtonProps {
size?: string;
}
export function isLink(item: TLink | TMenu): item is TLink {
return 'url' in item;
}
export function isMenu(item: TLink | TMenu): item is TMenu {
return 'content' in item;
}