use leptos::html;
use leptos::prelude::*;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SplitAxis {
Row,
Column,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum DockSide {
Start,
End,
}
#[derive(Clone, Copy)]
struct DockAxis(SplitAxis);
#[component]
pub fn DockLayout(
#[prop(default = SplitAxis::Row)] axis: SplitAxis,
#[prop(into, optional)] class: String,
children: Children,
) -> impl IntoView {
provide_context(DockAxis(axis));
let orientation = match axis {
SplitAxis::Row => "row",
SplitAxis::Column => "column",
};
view! {
<div class=format!("nightshade-dock-layout {orientation} {class}")>{children()}</div>
}
}
#[component]
pub fn DockMain(#[prop(into, optional)] class: String, children: Children) -> impl IntoView {
view! { <div class=format!("nightshade-dock-main {class}")>{children()}</div> }
}
#[component]
pub fn DockPanel(
#[prop(into, optional)] title: String,
#[prop(default = DockSide::Start)] side: DockSide,
size: RwSignal<f64>,
#[prop(default = 140.0)] min: f64,
#[prop(default = 680.0)] max: f64,
#[prop(optional)] collapsible: bool,
#[prop(optional)] collapsed: Option<RwSignal<bool>>,
children: ChildrenFn,
) -> impl IntoView {
let axis = use_context::<DockAxis>()
.map(|context| context.0)
.unwrap_or(SplitAxis::Row);
let horizontal = axis == SplitAxis::Row;
let collapsed = collapsed.unwrap_or_else(|| RwSignal::new(false));
let has_title = !title.is_empty();
let handle_ref = NodeRef::<html::Div>::new();
let drag = StoredValue::new(None::<(f64, f64)>);
let invert = matches!(side, DockSide::End);
let pointer_position = move |event: &web_sys::PointerEvent| {
if horizontal {
event.client_x() as f64
} else {
event.client_y() as f64
}
};
let on_pointerdown = move |event: web_sys::PointerEvent| {
event.prevent_default();
drag.set_value(Some((pointer_position(&event), size.get_untracked())));
if let Some(node) = handle_ref.get() {
let _ = node.set_pointer_capture(event.pointer_id());
}
};
let on_pointermove = move |event: web_sys::PointerEvent| {
if let Some((start_pointer, start_size)) = drag.get_value() {
let mut delta = pointer_position(&event) - start_pointer;
if invert {
delta = -delta;
}
size.set((start_size + delta).clamp(min, max));
}
};
let on_pointerup = move |event: web_sys::PointerEvent| {
drag.set_value(None);
if let Some(node) = handle_ref.get() {
let _ = node.release_pointer_capture(event.pointer_id());
}
};
let axis_class = if horizontal { "row" } else { "column" };
let side_class = match side {
DockSide::Start => "start",
DockSide::End => "end",
};
let style = move || {
if collapsed.get() {
"flex:0 0 auto".to_string()
} else {
format!("flex:0 0 {}px", size.get())
}
};
let children = StoredValue::new(children);
view! {
<div
class=format!("nightshade-dock-panel {axis_class} {side_class}")
class:collapsed=move || collapsed.get()
style=style
>
{(has_title || collapsible)
.then(|| {
view! {
<div class="nightshade-dock-header">
<span class="nightshade-dock-title">{title.clone()}</span>
{collapsible
.then(|| {
view! {
<button
class="nightshade-dock-collapse"
aria-label="Toggle panel"
aria-expanded=move || (!collapsed.get()).to_string()
on:click=move |_| collapsed.update(|value| *value = !*value)
>
{move || if collapsed.get() { "+" } else { "-" }}
</button>
}
})}
</div>
}
})}
<Show when=move || !collapsed.get() fallback=|| ()>
<div class="nightshade-dock-body">{children.with_value(|render| render())}</div>
</Show>
<Show when=move || !collapsed.get() fallback=|| ()>
<div
node_ref=handle_ref
class=format!("nightshade-dock-handle {axis_class} {side_class}")
on:pointerdown=on_pointerdown
on:pointermove=on_pointermove
on:pointerup=on_pointerup
></div>
</Show>
</div>
}
}