All files / packages/app/components page.tsx

100% Statements 22/22
100% Branches 10/10
100% Functions 8/8
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                    4104x 6544x   332x   8083x 8083x 1489x   6586x   4196x     332x               7780x                                                 332x 1769x 1769x 1769x 6627x   1769x                   7816x           7816x 1242x   7041x              
import React from "react";
 
import { AmountBadge } from "~app/components/amount-badge";
import { PageWrapper } from "~app/components/page-wrapper";
import { suspendedFallback } from "~app/components/suspense-wrapper";
import { NavigationContext } from "~app/contexts/navigation-context";
import { Icon } from "~components/icons";
import type { IconName } from "~components/icons";
import { Link } from "~components/link";
import { Text } from "~components/text";
import { cn } from "~components/utils";
import type { ViewReactNode } from "~components/view";
import { View } from "~components/view";
import type { FileRouteTypes } from "~web/entry/routeTree.gen";
 
type ShowProps = {
	useShow?: () => boolean;
};
 
type MenuElement = {
	iconName: IconName;
	text: string;
	pathname: FileRouteTypes["to"];
	useBadgeAmount?: () => number;
} & ShowProps;
 
const useZero = () => 0;
const useTrue = () => true;
 
const WithShow = suspendedFallback<React.PropsWithChildren<ShowProps>>(
	({ useShow = useTrue, children }) => {
		const show = useShow();
		if (!show) {
			return null;
		}
		return <>{children}</>;
	},
	() => null,
);
 
const MenuItemComponent: React.FC<MenuElement & { selected: boolean }> = ({
	iconName,
	pathname,
	text,
	useBadgeAmount = useZero,
	useShow,
	selected,
}) => (
	<WithShow useShow={useShow}>
		<Link
			key={pathname}
			to={pathname}
			className="flex flex-1 flex-col items-center justify-center"
			color={selected ? "primary" : "foreground"}
		>
			<AmountBadge useAmount={useBadgeAmount}>
				<Icon name={iconName} className="size-6" />
			</AmountBadge>
			<Text className={cn("text-sm/8", selected ? "text-primary" : undefined)}>
				{text}
			</Text>
		</Link>
	</WithShow>
);
 
type Props = {
	children?: ViewReactNode;
	elements: (MenuElement & {
		ItemWrapper?: React.FC<React.PropsWithChildren>;
		PageWrapper?: React.FC<React.PropsWithChildren>;
	})[];
};
 
export const Page: React.FC<Props> = ({ children, elements }) => {
	const { usePathname } = React.use(NavigationContext);
	const pathname = usePathname();
	const Wrapper = elements.find(
		(element) => element.pathname === pathname,
	)?.PageWrapper;
	return (
		<>
			<PageWrapper wrapper={Wrapper}>{children}</PageWrapper>
			<View
				className="bg-content1 fixed bottom-0 left-0 z-20 w-full flex-row p-2 shadow-lg"
				testID="sticky-menu"
			>
				<View className="mx-auto max-w-screen-sm flex-1 flex-row">
					{elements.map(({ ItemWrapper, ...props }) => {
						const element = (
							<MenuItemComponent
								key={props.pathname}
								{...props}
								selected={pathname.startsWith(props.pathname)}
							/>
						);
						if (ItemWrapper) {
							return <ItemWrapper key={props.pathname}>{element}</ItemWrapper>;
						}
						return element;
					})}
				</View>
			</View>
		</>
	);
};