103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import type { HTMLAttributes, ReactNode } from "react";
|
|
import { cn } from "./cn";
|
|
|
|
export interface AdminNavigationContext {
|
|
id: string;
|
|
label: string;
|
|
description?: string;
|
|
groupLabel?: string;
|
|
icon?: ReactNode;
|
|
active?: boolean;
|
|
onSelect?: () => void;
|
|
onToggle?: () => void;
|
|
}
|
|
|
|
export interface AdminNavigationItem {
|
|
id: string;
|
|
label: string;
|
|
icon?: ReactNode;
|
|
}
|
|
|
|
export interface AdminNavigationPanelProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
eyebrow?: string;
|
|
title: ReactNode;
|
|
contexts?: AdminNavigationContext[];
|
|
items: AdminNavigationItem[];
|
|
activeId?: string;
|
|
footer?: ReactNode;
|
|
closeLabel?: string;
|
|
onClose: () => void;
|
|
onItemChange: (id: string) => void;
|
|
}
|
|
|
|
export function AdminNavigationPanel({
|
|
eyebrow = "NODE.DC",
|
|
title,
|
|
contexts = [],
|
|
items,
|
|
activeId,
|
|
footer,
|
|
closeLabel = "Закрыть администрирование",
|
|
onClose,
|
|
onItemChange,
|
|
className,
|
|
...props
|
|
}: AdminNavigationPanelProps) {
|
|
return (
|
|
<aside className={cn("nodedc-admin-panel", className)} {...props}>
|
|
<header className="nodedc-admin-panel__head">
|
|
<div>
|
|
<p>{eyebrow}</p>
|
|
<h2>{title}</h2>
|
|
</div>
|
|
<button type="button" className="nodedc-admin-panel__close" aria-label={closeLabel} onClick={onClose}>
|
|
<span className="nodedc-close-mark" aria-hidden="true" />
|
|
</button>
|
|
</header>
|
|
|
|
<div className="nodedc-admin-panel__contexts">
|
|
{contexts.map((context) => (
|
|
<div className="nodedc-admin-panel__context-group" key={context.id}>
|
|
{context.groupLabel ? <span className="nodedc-admin-panel__group-label">{context.groupLabel}</span> : null}
|
|
<div
|
|
className={cn("nodedc-admin-context", context.onToggle && "nodedc-admin-context--split")}
|
|
data-active={context.active ? "true" : undefined}
|
|
>
|
|
<button type="button" className="nodedc-admin-context__main" onClick={context.onSelect}>
|
|
<span className="nodedc-admin-context__icon" aria-hidden="true">{context.icon}</span>
|
|
<span className="nodedc-admin-context__body">
|
|
<strong>{context.label}</strong>
|
|
{context.description ? <small>{context.description}</small> : null}
|
|
</span>
|
|
</button>
|
|
{context.onToggle ? (
|
|
<button type="button" className="nodedc-admin-context__toggle" aria-label={`Выбрать: ${context.label}`} onClick={context.onToggle}>
|
|
<span className="nodedc-select-trigger__chevron" aria-hidden="true" />
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<nav className="nodedc-admin-panel__nav" aria-label="Разделы администрирования">
|
|
{items.map((item) => (
|
|
<button
|
|
key={item.id}
|
|
type="button"
|
|
className="nodedc-admin-panel__nav-item"
|
|
data-active={activeId === item.id ? "true" : undefined}
|
|
onClick={() => onItemChange(item.id)}
|
|
>
|
|
<span className="nodedc-admin-panel__nav-icon" aria-hidden="true">{item.icon}</span>
|
|
<span>{item.label}</span>
|
|
</button>
|
|
))}
|
|
</nav>
|
|
|
|
{footer ? <footer className="nodedc-admin-panel__footer">{footer}</footer> : null}
|
|
</aside>
|
|
);
|
|
}
|
|
|