80 lines
3.2 KiB
Rust
80 lines
3.2 KiB
Rust
|
|
// content_graph/related_content.rs
|
||
|
|
//
|
||
|
|
// Sidebar widget showing content related to the current node.
|
||
|
|
// Pure HTML — no JS, fully SSR-safe.
|
||
|
|
// Data comes from RELATED_NODES static map generated at build time.
|
||
|
|
|
||
|
|
use leptos::prelude::*;
|
||
|
|
|
||
|
|
use super::generated::related::{RelatedNode, RELATED_NODES};
|
||
|
|
|
||
|
|
// ── Kind display helpers ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
fn kind_label(kind: &str) -> &'static str {
|
||
|
|
match kind {
|
||
|
|
"Implements" | "ImplementedBy" => "implements",
|
||
|
|
"RelatedTo" | "RelatedFrom" => "related",
|
||
|
|
"PartOf" | "ParentOf" => "part of",
|
||
|
|
"References" | "ReferencedBy" => "references",
|
||
|
|
"Extends" | "ExtendedBy" => "extends",
|
||
|
|
"ManifestsIn" => "manifests in",
|
||
|
|
"Resolves" => "resolves",
|
||
|
|
"Complements" => "complements",
|
||
|
|
"DependsOn" => "depends on",
|
||
|
|
"DocumentedIn" => "documented in",
|
||
|
|
_ => kind,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn kind_css(kind: &str) -> &'static str {
|
||
|
|
match kind {
|
||
|
|
"Implements" | "ImplementedBy" => "ds-badge-primary",
|
||
|
|
"RelatedTo" | "RelatedFrom" => "ds-badge-secondary",
|
||
|
|
"PartOf" | "ParentOf" => "ds-badge-accent",
|
||
|
|
"References" | "ReferencedBy" => "ds-badge-neutral",
|
||
|
|
_ => "ds-badge-ghost",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Component ─────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
/// Sidebar list of related content items for a given node.
|
||
|
|
///
|
||
|
|
/// Renders nothing if the node has no relationships.
|
||
|
|
#[component]
|
||
|
|
pub fn RelatedContent(node_id: String) -> impl IntoView {
|
||
|
|
let related: &'static [RelatedNode] = RELATED_NODES
|
||
|
|
.iter()
|
||
|
|
.find(|(id, _)| *id == node_id.as_str())
|
||
|
|
.map(|(_, nodes)| *nodes)
|
||
|
|
.unwrap_or(&[]);
|
||
|
|
|
||
|
|
if related.is_empty() {
|
||
|
|
return view! { <></> }.into_any();
|
||
|
|
}
|
||
|
|
|
||
|
|
view! {
|
||
|
|
<aside class="content-graph-related ds-card ds-bg-base-200 p-4 space-y-3">
|
||
|
|
<h3 class="text-sm font-semibold ds-text uppercase tracking-wide opacity-60">
|
||
|
|
"Related"
|
||
|
|
</h3>
|
||
|
|
<ul class="space-y-2">
|
||
|
|
{related.iter().map(|node| {
|
||
|
|
let badge = kind_css(node.kind);
|
||
|
|
let label = kind_label(node.kind);
|
||
|
|
view! {
|
||
|
|
<li class="flex items-start gap-2">
|
||
|
|
<span class=format!("ds-badge text-xs mt-0.5 shrink-0 {badge}")>
|
||
|
|
{label}
|
||
|
|
</span>
|
||
|
|
<span class="text-sm ds-text leading-snug">
|
||
|
|
{node.title}
|
||
|
|
</span>
|
||
|
|
</li>
|
||
|
|
}
|
||
|
|
}).collect_view()}
|
||
|
|
</ul>
|
||
|
|
</aside>
|
||
|
|
}.into_any()
|
||
|
|
}
|