mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-25 04:37:13 -05:00
269 lines
5.7 KiB
TypeScript
269 lines
5.7 KiB
TypeScript
import clsx from "clsx";
|
|
import { X } from "lucide-react";
|
|
import type * as React from "react";
|
|
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,
|
|
...rest
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
footer?: React.ReactNode;
|
|
top?: React.ReactNode;
|
|
topCentered?: boolean;
|
|
collapsed?: boolean;
|
|
} & Omit<React.ComponentPropsWithRef<"nav">, "children" | "className">) {
|
|
return (
|
|
<nav
|
|
className={clsx(styles.sideNav, className, {
|
|
[styles.sideNavCollapsed]: collapsed,
|
|
})}
|
|
{...rest}
|
|
>
|
|
<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"> & {
|
|
customAvatarUrl?: string | null;
|
|
};
|
|
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,
|
|
isActive,
|
|
imageUrl,
|
|
overlayIconUrl,
|
|
user,
|
|
subtitle,
|
|
badge,
|
|
badgeVariant,
|
|
}: {
|
|
children: React.ReactNode;
|
|
to: string;
|
|
isActive?: boolean;
|
|
imageUrl?: string;
|
|
overlayIconUrl?: string;
|
|
user?: Pick<Tables["User"], "discordId" | "discordAvatar"> & {
|
|
customAvatarUrl?: string | null;
|
|
};
|
|
subtitle?: React.ReactNode;
|
|
badge?: React.ReactNode;
|
|
badgeVariant?: "default" | "warning";
|
|
}) {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
prefetch="intent"
|
|
className={styles.listLink}
|
|
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,
|
|
...rest
|
|
}: {
|
|
children: React.ReactNode;
|
|
user?: Pick<Tables["User"], "discordId" | "discordAvatar"> & {
|
|
customAvatarUrl?: string | null;
|
|
};
|
|
subtitle?: string | null;
|
|
badge?: string | null;
|
|
badgeVariant?: "default" | "warning";
|
|
} & Omit<React.ComponentPropsWithoutRef<"button">, "children">) {
|
|
return (
|
|
<button type="button" className={styles.listButton} {...rest}>
|
|
<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>;
|
|
}
|
|
|
|
export function NavIconContainer({ children }: { children: React.ReactNode }) {
|
|
return <div className={styles.iconContainer}>{children}</div>;
|
|
}
|
|
|
|
export function NavListButton({
|
|
children,
|
|
className,
|
|
onClick,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
onClick: () => void;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={clsx(styles.listButton, className)}
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function NavListImage({ src }: { src: string }) {
|
|
return <img src={src} alt="" className={styles.listLinkImage} />;
|
|
}
|
|
|
|
export function NavListTexts({ children }: { children: React.ReactNode }) {
|
|
return <div className={styles.listLinkContent}>{children}</div>;
|
|
}
|
|
|
|
export function NavListTitle({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<span className={clsx(styles.listLinkTitle, className)}>{children}</span>
|
|
);
|
|
}
|
|
|
|
export function NavListSubtitle({ children }: { children: React.ReactNode }) {
|
|
return <span className={styles.listLinkSubtitle}>{children}</span>;
|
|
}
|