1use blockworx_kernel::Event;
16use blockworx_tools::commands::CommandId;
17use dioxus::prelude::*;
18
19use crate::chrome::Availability;
20use crate::icons::Icon;
21use crate::shell::Shell;
22
23pub const TAP: &str = "relative grid size-target place-items-center rounded-xl text-zinc-700 \
25 transition duration-150 ease-out live:hover:bg-zinc-950/6 live:active:scale-90 \
26 dead:opacity-30 dark:text-zinc-200 dark:live:hover:bg-white/10 \
27 [&_svg]:size-5 [&_svg]:stroke-[1.8]";
28
29pub const CELL: &str = "relative grid size-13 place-items-center rounded-cell text-zinc-600 \
32 transition duration-150 ease-out live:hover:bg-zinc-950/6 live:active:scale-90 \
33 dead:opacity-30 dark:text-zinc-300 dark:live:hover:bg-white/10 \
34 [&_svg]:size-6 [&_svg]:stroke-[1.75]";
35
36pub const ARMED: &str = "relative grid size-13 place-items-center rounded-cell \
38 bg-sky-600 text-white transition duration-150 ease-out live:active:scale-90 \
39 dark:bg-sky-500 dark:text-zinc-950 [&_svg]:size-6 [&_svg]:stroke-[1.75]";
40
41#[component]
52pub fn Press(
53 shell: Shell,
54 id: CommandId,
55 raises: Option<CommandId>,
56 says: String,
57 icon: Option<Icon>,
58 class: String,
59 #[props(default)]
61 children: Element,
62) -> Element {
63 let chrome = shell.chrome();
64 let offered = chrome
65 .read()
66 .commands
67 .face(id)
68 .map(|face| face.availability);
69 let hint = match offered {
72 Some(Availability::Withheld) => format!(
73 "{says} \u{2014} {}",
74 crate::chrome::Withholding::of(
75 chrome.read().top_bar.lens.viewing,
76 chrome.read().writable,
77 )
78 .says(),
79 ),
80 _ => crate::chords::hinted(shell.token(), &says, id),
81 };
82 let pressed = raises.unwrap_or(id);
83 let shell = use_hook(|| CopyValue::new(shell));
84 rsx! {
85 Pressable {
86 class: "{class}",
87 title: "{hint}",
88 "aria-label": "{says}",
89 "data-cmd": "{id.name()}",
92 pressing: if offered.is_none_or(Availability::disabled) {
93 Pressing::Dead
94 } else {
95 Pressing::Live
96 },
97 onpress: move |()| {
98 let shell = shell.read();
99 shell.works();
100 shell.say(Event::Command(pressed));
101 },
102 if let Some(icon) = icon {
103 Face { icon }
104 }
105 {children}
106 }
107 }
108}
109
110#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
112pub enum Pressing {
113 #[default]
114 Live,
115 Dead,
116}
117
118#[component]
120pub fn Pressable(
121 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
122 #[props(default)] pressing: Pressing,
123 onpress: EventHandler<()>,
124 children: Element,
125) -> Element {
126 let dead = if pressing == Pressing::Dead {
127 "true"
128 } else {
129 "false"
130 };
131 rsx! {
132 div {
133 role: "button",
134 "aria-disabled": dead,
135 "data-disabled": dead,
136 onclick: move |_| {
137 if pressing == Pressing::Live {
138 onpress.call(());
139 }
140 },
141 ..attributes,
142 {children}
143 }
144 }
145}
146
147#[component]
149pub fn Face(icon: Icon) -> Element {
150 rsx! {
151 span { class: "contents", dangerous_inner_html: icon.inked() }
152 }
153}