Build Module Studio application composition

This commit is contained in:
DCCONSTRUCTIONS
2026-07-11 21:37:20 +03:00
parent d3e2859ca1
commit 50718a33a1
22 changed files with 2243 additions and 35 deletions
+41 -5
View File
@@ -1,5 +1,6 @@
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "./cn.js";
import { SortableList } from "./DragDrop.js";
export interface AdminNavigationContext {
id: string;
@@ -16,12 +17,15 @@ export interface AdminNavigationItem {
id: string;
label: string;
icon?: ReactNode;
sortable?: boolean;
}
export interface AdminNavigationPanelProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
eyebrow?: string;
title: ReactNode;
contexts?: AdminNavigationContext[];
headerActions?: ReactNode;
contextSlot?: ReactNode;
items: AdminNavigationItem[];
activeId?: string;
footer?: ReactNode;
@@ -29,12 +33,15 @@ export interface AdminNavigationPanelProps extends Omit<HTMLAttributes<HTMLEleme
navigationLabel?: string;
onClose: () => void;
onItemChange: (id: string) => void;
onItemsReorder?: (ids: string[]) => void;
}
export function AdminNavigationPanel({
eyebrow = "NODE.DC",
title,
contexts = [],
headerActions,
contextSlot,
items,
activeId,
footer,
@@ -42,21 +49,27 @@ export function AdminNavigationPanel({
navigationLabel = "Разделы администрирования",
onClose,
onItemChange,
onItemsReorder,
className,
...props
}: AdminNavigationPanelProps) {
return (
<aside className={cn("nodedc-admin-panel", className)} data-has-contexts={contexts.length > 0 ? "true" : undefined} {...props}>
<aside className={cn("nodedc-admin-panel", className)} data-has-contexts={contexts.length > 0 || contextSlot ? "true" : undefined} {...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>
<div className="nodedc-admin-panel__head-actions">
{headerActions}
<button type="button" className="nodedc-admin-panel__close" aria-label={closeLabel} onClick={onClose}>
<span className="nodedc-close-mark" aria-hidden="true" />
</button>
</div>
</header>
{contextSlot ? <div className="nodedc-admin-panel__context-slot">{contextSlot}</div> : null}
{contexts.length > 0 ? <div className="nodedc-admin-panel__contexts">
{contexts.map((context) => (
<div className="nodedc-admin-panel__context-group" key={context.id}>
@@ -83,7 +96,7 @@ export function AdminNavigationPanel({
</div> : null}
<nav className="nodedc-admin-panel__nav" aria-label={navigationLabel}>
{items.map((item) => (
{items.filter((item) => !item.sortable).map((item) => (
<button
key={item.id}
type="button"
@@ -95,6 +108,29 @@ export function AdminNavigationPanel({
<span>{item.label}</span>
</button>
))}
{items.some((item) => item.sortable) ? (
<SortableList
className="nodedc-admin-panel__sortable-nav"
items={items.filter((item) => item.sortable)}
getId={(item) => item.id}
onReorder={(next) => onItemsReorder?.(next.map((item) => item.id))}
>
{(item, { handle }) => (
<div className="nodedc-admin-panel__sortable-row">
<button
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>
{handle}
</div>
)}
</SortableList>
) : null}
</nav>
{footer ? <footer className="nodedc-admin-panel__footer">{footer}</footer> : null}