mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
215 lines
4.5 KiB
TypeScript
215 lines
4.5 KiB
TypeScript
import clsx from "clsx";
|
|
import { X } from "lucide-react";
|
|
import type * as React from "react";
|
|
import { Button } from "react-aria-components";
|
|
import { Link } from "react-router";
|
|
import { SendouButton } from "~/components/elements/Button";
|
|
import type { Tables } from "~/db/tables";
|
|
import { Avatar } from "./Avatar";
|
|
import styles from "./SideNav.module.css";
|
|
|
|
export function SideNav({
|
|
children,
|
|
className,
|
|
footer,
|
|
top,
|
|
topCentered,
|
|
collapsed,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
footer?: React.ReactNode;
|
|
top?: React.ReactNode;
|
|
topCentered?: boolean;
|
|
collapsed?: boolean;
|
|
}) {
|
|
return (
|
|
<nav
|
|
className={clsx(styles.sideNav, className, {
|
|
[styles.sideNavCollapsed]: collapsed,
|
|
})}
|
|
>
|
|
<div
|
|
className={clsx(styles.sideNavTop, {
|
|
[styles.sideNavTopCentered]: topCentered,
|
|
})}
|
|
>
|
|
{top}
|
|
</div>
|
|
<div className={clsx(styles.sideNavInner, "scrollbar")}>{children}</div>
|
|
{footer}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export function SideNavHeader({
|
|
children,
|
|
icon,
|
|
showClose,
|
|
action,
|
|
}: {
|
|
children: React.ReactNode;
|
|
icon?: React.ReactNode;
|
|
showClose?: boolean;
|
|
action?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<header className={styles.sideNavHeader}>
|
|
{icon ? <div className={styles.iconContainer}>{icon}</div> : null}
|
|
<h2>{children}</h2>
|
|
{action ? (
|
|
<span className={styles.sideNavHeaderAction}>{action}</span>
|
|
) : null}
|
|
{showClose ? (
|
|
<SendouButton
|
|
icon={<X />}
|
|
variant="minimal"
|
|
slot="close"
|
|
className={styles.sideNavHeaderClose}
|
|
/>
|
|
) : null}
|
|
</header>
|
|
);
|
|
}
|
|
|
|
function ListItemContent({
|
|
children,
|
|
user,
|
|
imageUrl,
|
|
overlayIconUrl,
|
|
subtitle,
|
|
badge,
|
|
badgeVariant,
|
|
suppressSubtitleHydrationWarning,
|
|
}: {
|
|
children: React.ReactNode;
|
|
user?: Pick<Tables["User"], "discordId" | "discordAvatar">;
|
|
imageUrl?: string;
|
|
overlayIconUrl?: string;
|
|
subtitle?: React.ReactNode;
|
|
badge?: React.ReactNode;
|
|
badgeVariant?: "default" | "warning";
|
|
suppressSubtitleHydrationWarning?: boolean;
|
|
}) {
|
|
return (
|
|
<>
|
|
{user ? (
|
|
<Avatar user={user} size="xxsm" />
|
|
) : imageUrl ? (
|
|
<div className={styles.listLinkImageContainer}>
|
|
<img src={imageUrl} alt="" className={styles.listLinkImage} />
|
|
{overlayIconUrl ? (
|
|
<img
|
|
src={overlayIconUrl}
|
|
alt=""
|
|
className={styles.listLinkOverlayIcon}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
<div className={styles.listLinkContent}>
|
|
<span className={styles.listLinkTitle}>{children}</span>
|
|
{subtitle || badge ? (
|
|
<div className={styles.listLinkSubtitleRow}>
|
|
{subtitle ? (
|
|
<span
|
|
className={styles.listLinkSubtitle}
|
|
suppressHydrationWarning={suppressSubtitleHydrationWarning}
|
|
>
|
|
{subtitle}
|
|
</span>
|
|
) : null}
|
|
{typeof badge === "string" ? (
|
|
<span
|
|
className={clsx(styles.listLinkBadge, {
|
|
[styles.listLinkBadgeWarning]: badgeVariant === "warning",
|
|
})}
|
|
>
|
|
{badge}
|
|
</span>
|
|
) : (
|
|
badge
|
|
)}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function ListLink({
|
|
children,
|
|
to,
|
|
onClick,
|
|
isActive,
|
|
imageUrl,
|
|
overlayIconUrl,
|
|
user,
|
|
subtitle,
|
|
badge,
|
|
badgeVariant,
|
|
}: {
|
|
children: React.ReactNode;
|
|
to: string;
|
|
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
isActive?: boolean;
|
|
imageUrl?: string;
|
|
overlayIconUrl?: string;
|
|
user?: Pick<Tables["User"], "discordId" | "discordAvatar">;
|
|
subtitle?: React.ReactNode;
|
|
badge?: React.ReactNode;
|
|
badgeVariant?: "default" | "warning";
|
|
}) {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
className={styles.listLink}
|
|
onClick={onClick}
|
|
aria-current={isActive ? "page" : undefined}
|
|
>
|
|
<ListItemContent
|
|
user={user}
|
|
imageUrl={imageUrl}
|
|
overlayIconUrl={overlayIconUrl}
|
|
subtitle={subtitle}
|
|
badge={badge}
|
|
badgeVariant={badgeVariant}
|
|
suppressSubtitleHydrationWarning
|
|
>
|
|
{children}
|
|
</ListItemContent>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function ListButton({
|
|
children,
|
|
user,
|
|
subtitle,
|
|
badge,
|
|
badgeVariant,
|
|
}: {
|
|
children: React.ReactNode;
|
|
user?: Pick<Tables["User"], "discordId" | "discordAvatar">;
|
|
subtitle?: string | null;
|
|
badge?: string | null;
|
|
badgeVariant?: "default" | "warning";
|
|
}) {
|
|
return (
|
|
<Button className={styles.listButton}>
|
|
<ListItemContent
|
|
user={user}
|
|
subtitle={subtitle}
|
|
badge={badge}
|
|
badgeVariant={badgeVariant}
|
|
>
|
|
{children}
|
|
</ListItemContent>
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
export function SideNavFooter({ children }: { children: React.ReactNode }) {
|
|
return <div className={styles.sideNavFooter}>{children}</div>;
|
|
}
|