|
@@ -0,0 +1,47 @@
|
|
|
|
|
+import React from "react";
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * ExplorerPageShell
|
|
|
|
|
+ *
|
|
|
|
|
+ * Consistent framing for all Explorer pages:
|
|
|
|
|
+ * - Title + optional description
|
|
|
|
|
+ * - Breadcrumbs as ReactNode (supports dropdown composition)
|
|
|
|
|
+ * - Optional actions (refresh, etc.)
|
|
|
|
|
+ *
|
|
|
|
|
+ * The shell intentionally does not "build" breadcrumbs to avoid tight coupling
|
|
|
|
|
+ * and to keep dropdown variants easy and clean.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {{
|
|
|
|
|
+ * title: string,
|
|
|
|
|
+ * description?: string|null,
|
|
|
|
|
+ * breadcrumbs?: React.ReactNode,
|
|
|
|
|
+ * actions?: React.ReactNode,
|
|
|
|
|
+ * children: React.ReactNode
|
|
|
|
|
+ * }} props
|
|
|
|
|
+ */
|
|
|
|
|
+export default function ExplorerPageShell({
|
|
|
|
|
+ title,
|
|
|
|
|
+ description = null,
|
|
|
|
|
+ breadcrumbs = null,
|
|
|
|
|
+ actions = null,
|
|
|
|
|
+ children,
|
|
|
|
|
+}) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ <div className="flex items-start justify-between gap-4">
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
|
|
|
|
+ {description ? (
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{description}</p>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {actions ? <div className="shrink-0">{actions}</div> : null}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {breadcrumbs ? <div>{breadcrumbs}</div> : null}
|
|
|
|
|
+
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|