Files
HKSingleParty/99_references/beacon-main/src/components/menu.tsx
2025-05-28 09:55:51 +08:00

206 lines
4.4 KiB
TypeScript

/**
* @file Menu component
*/
import {
IonContent,
IonIcon,
IonImg,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenu,
IonMenuToggle,
} from "@ionic/react";
import {
createOutline,
createSharp,
homeOutline,
homeSharp,
lockClosedOutline,
lockClosedSharp,
navigateCircleOutline,
navigateCircleSharp,
settingsOutline,
settingsSharp,
} from "ionicons/icons";
import {FC, useRef} from "react";
import {useLocation} from "react-router-dom";
import logo from "~/assets/logo.png";
import {useEphemeralStore} from "~/lib/stores/ephemeral";
import {AuthState} from "~/lib/types";
import {getAuthState} from "~/lib/utils";
/**
* Menu navigation item position
*/
enum NavItemPosition {
TOP = "top",
BOTTOM = "bottom",
}
/**
* Menu navigation item
*/
interface NavItem {
/**
* Item URL
*/
url: string;
/**
* Required authentication state to show this item or undefined if the item is always available
*/
requiredState: AuthState | undefined;
/**
* Item position
*/
position: NavItemPosition;
/**
* iOS icon
*/
iosIcon: string;
/**
* Material Design icon
*/
mdIcon: string;
/**
* Item title
*/
title: string;
}
/**
* Menu navigation items
*/
const navItems: NavItem[] = [
{
title: "Home",
requiredState: AuthState.UNAUTHENTICATED,
url: "/",
position: NavItemPosition.TOP,
iosIcon: homeOutline,
mdIcon: homeSharp,
},
{
title: "Authentication",
requiredState: AuthState.UNAUTHENTICATED,
url: "/auth/1",
position: NavItemPosition.TOP,
iosIcon: lockClosedOutline,
mdIcon: lockClosedSharp,
},
{
title: "Nearby",
requiredState: AuthState.AUTHENTICATED_TERMS,
url: "/nearby",
position: NavItemPosition.TOP,
iosIcon: navigateCircleOutline,
mdIcon: navigateCircleSharp,
},
{
title: "Create Post",
requiredState: AuthState.AUTHENTICATED_TERMS,
url: "/posts/create/1",
position: NavItemPosition.TOP,
iosIcon: createOutline,
mdIcon: createSharp,
},
{
title: "Settings",
requiredState: AuthState.AUTHENTICATED_TERMS,
url: "/settings",
position: NavItemPosition.BOTTOM,
iosIcon: settingsOutline,
mdIcon: settingsSharp,
},
];
/**
* Navigation item component
* @param item Navigation item
* @returns JSX
*/
const NavItem: FC<NavItem> = item => {
// Hooks
const location = useLocation();
return (
<IonMenuToggle autoHide={false}>
<IonItem
className={location.pathname === item.url ? "selected" : ""}
routerLink={item.url}
routerDirection="none"
lines="none"
detail={false}
>
<IonIcon
aria-hidden="true"
slot="start"
ios={item.iosIcon}
md={item.mdIcon}
/>
<IonLabel>{item.title}</IonLabel>
</IonItem>
</IonMenuToggle>
);
};
/**
* Menu component
* @returns JSX
*/
export const Menu: FC = () => {
// Hooks
const user = useEphemeralStore(state => state.user);
const menu = useRef<HTMLIonMenuElement>(null);
return (
<IonMenu contentId="main" type="overlay" ref={menu}>
<IonContent>
<IonList className="flex flex-col h-full py-4">
<IonListHeader className="p-0">
<div className="flex flex-row items-center justify-center my-8 w-full">
<IonImg alt="Beacon logo" className="h-14 w-14 mr-2" src={logo} />
<span className="ml-2 text-3xl">Beacon</span>
</div>
</IonListHeader>
{navItems
.filter(
navItem =>
user !== undefined &&
navItem.position === NavItemPosition.TOP &&
(navItem.requiredState === undefined ||
navItem.requiredState === getAuthState(user)),
)
.map((navItem, index) => (
<NavItem key={index} {...navItem} />
))}
<div className="flex-1" />
{navItems
.filter(
navItem =>
user !== undefined &&
navItem.position === NavItemPosition.BOTTOM &&
(navItem.requiredState === undefined ||
navItem.requiredState === getAuthState(user)),
)
.map((navItem, index) => (
<NavItem key={index} {...navItem} />
))}
</IonList>
</IonContent>
</IonMenu>
);
};