use crate::{
core::{
assets,
attrs::{component_attributes, FcData, Kind},
},
prelude::*,
theme::use_stylesheets,
};
const STATUS: Bem = Bem::new("fc-agent-status");
const METRIC: Bem = Bem::new("fc-metric");
const TRANSFER: Bem = Bem::new("fc-transfer");
const APPROVAL: Bem = Bem::new("fc-approval");
const PROMPT: Bem = Bem::new("fc-user-prompt");
const DEVICE: Bem = Bem::new("fc-device-auth");
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Record {
pub id: Option<Id>,
pub seq: Option<u64>,
pub state: Option<Lifecycle>,
pub ts: Option<Timestamp>,
}
impl Record {
fn attributes(&self, kind: Kind) -> Spread {
FcData {
kind: Some(kind),
id: self.id.clone(),
seq: self.seq,
state: self.state,
ts: self.ts.clone(),
..FcData::default()
}
.attributes()
}
}
fn shell(
bem: Bem,
kind: Kind,
label: &AccessibleName,
record: &Record,
class: Option<&str>,
attributes: Vec<Attribute>,
) -> Spread {
let mut attributes = component_attributes(
attributes,
ClassList::new(bem.block()).extend(class),
&["aria-label"],
);
attributes.push(Attribute::new("aria-label", label.to_string(), None, false));
attributes.extend(record.attributes(kind));
attributes
}
#[component]
pub fn AgentStatus(
state: Lifecycle,
label: Element,
#[props(default)] meta: Option<Element>,
#[props(default = SpinnerKind::Sparkle)] spinner: SpinnerKind,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
) -> Element {
let mut attributes = component_attributes(
attributes,
ClassList::new(STATUS.block()).extend(class.as_deref()),
&[],
);
attributes.extend(
FcData {
kind: Some(Kind::AgentStatus),
state: Some(state),
..FcData::default()
}
.attributes(),
);
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_AGENT_STATUS_CSS } }
div { ..attributes,
Spinner { kind: spinner, size: SpinnerSize::Sm, label: SpinnerLabel::Decorative }
span { class: STATUS.element("label"), role: "status", {label} }
if let Some(meta) = meta { span { class: STATUS.element("meta"), {meta} } }
}
}
}
#[component]
pub fn Metric(
label: AccessibleName,
title: Element,
value: Option<f64>,
display: String,
#[props(default)] meta: Option<Element>,
#[props(default)] actions: Option<Element>,
#[props(default)]
status: Option<Element>,
#[props(default)]
region: bool,
#[props(default)] record: Record,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
) -> Element {
let attributes = shell(
METRIC,
Kind::Metric,
&label,
&record,
class.as_deref(),
attributes,
);
let body = rsx! {
{status}
dl {
dt { class: METRIC.element("title"), {title} }
dd { class: METRIC.element("value"),
if let Some(value) = value { data { value: "{value}", "{display}" } } else { "{display}" }
}
if let Some(meta) = meta { dd { class: METRIC.element("meta"), {meta} } }
}
if let Some(actions) = actions { div { class: METRIC.element("actions"), {actions} } }
};
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_METRIC_CSS } }
{card(region, attributes, body)}
}
}
#[component]
pub fn Transfer(
label: AccessibleName,
title: Element,
#[props(default = HeadingLevel::H3)] heading: HeadingLevel,
#[props(default)]
meta: Option<Element>,
#[props(default)]
progress: Option<ProgressValue>,
#[props(default)] actions: Option<Element>,
#[props(default)] status: Option<Element>,
#[props(default)] region: bool,
#[props(default)] record: Record,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let attributes = shell(
TRANSFER,
Kind::Transfer,
&label,
&record,
class.as_deref(),
attributes,
);
let body = rsx! {
{status}
{super::heading(heading, TRANSFER.element("title"), title)}
if let Some(meta) = meta { p { class: TRANSFER.element("meta"), {meta} } }
{children}
if let Some(progress) = progress { Progress { label: label.clone(), value: progress } }
if let Some(actions) = actions { div { class: TRANSFER.element("actions"), {actions} } }
};
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_TRANSFER_CSS } }
{card(region, attributes, body)}
}
}
#[component]
pub fn Approval(
label: AccessibleName,
title: Element,
#[props(default = HeadingLevel::H3)] heading: HeadingLevel,
#[props(default)] meta: Option<Element>,
#[props(default)]
actions: Option<Element>,
#[props(default)] status: Option<Element>,
#[props(default)] region: bool,
#[props(default)] record: Record,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let attributes = shell(
APPROVAL,
Kind::Approval,
&label,
&record,
class.as_deref(),
attributes,
);
let body = rsx! {
{status}
{super::heading(heading, APPROVAL.element("title"), title)}
{children}
if let Some(meta) = meta { p { class: APPROVAL.element("meta"), {meta} } }
if let Some(actions) = actions { div { class: APPROVAL.element("actions"), {actions} } }
};
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_APPROVAL_CSS } }
{card(region, attributes, body)}
}
}
#[component]
pub fn UserPrompt(
label: AccessibleName,
title: Element,
#[props(default = HeadingLevel::H3)] heading: HeadingLevel,
#[props(default)] meta: Option<Element>,
#[props(default)] actions: Option<Element>,
#[props(default)] onsubmit: Option<EventHandler<()>>,
#[props(default)] status: Option<Element>,
#[props(default)] region: bool,
#[props(default)] record: Record,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let attributes = shell(
PROMPT,
Kind::UserPrompt,
&label,
&record,
class.as_deref(),
attributes,
);
let body = rsx! {
{status}
{super::heading(heading, PROMPT.element("title"), title)}
{children}
if let Some(meta) = meta { p { class: PROMPT.element("meta"), {meta} } }
if let Some(actions) = actions { div { class: PROMPT.element("actions"), {actions} } }
};
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_USER_PROMPT_CSS } }
if region {
section { ..attributes, {body} }
} else {
form { onsubmit: move |event| { event.prevent_default(); if let Some(onsubmit) = onsubmit { onsubmit.call(()); } },
..attributes, {body}
}
}
}
}
#[component]
pub fn DeviceAuth(
label: AccessibleName,
title: Element,
#[props(default = HeadingLevel::H3)] heading: HeadingLevel,
#[props(default)]
code: Option<String>,
#[props(default)] meta: Option<Element>,
#[props(default)] actions: Option<Element>,
#[props(default)] status: Option<Element>,
#[props(default)] region: bool,
#[props(default)] record: Record,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let attributes = shell(
DEVICE,
Kind::DeviceAuth,
&label,
&record,
class.as_deref(),
attributes,
);
let body = rsx! {
{status}
{super::heading(heading, DEVICE.element("title"), title)}
{children}
if let Some(code) = code { code { class: DEVICE.element("code"), "{code}" } }
if let Some(meta) = meta { p { class: DEVICE.element("meta"), {meta} } }
if let Some(actions) = actions { div { class: DEVICE.element("actions"), {actions} } }
};
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_DEVICE_AUTH_CSS } }
{card(region, attributes, body)}
}
}
fn card(region: bool, attributes: Spread, body: Element) -> Element {
if region {
rsx! { section { ..attributes, {body} } }
} else {
rsx! { article { ..attributes, {body} } }
}
}