diff --git a/frontend/src/components/ui/button_action.rs b/frontend/src/components/ui/button_action.rs
new file mode 100644
index 0000000..e7efaf6
--- /dev/null
+++ b/frontend/src/components/ui/button_action.rs
@@ -0,0 +1,65 @@
+use leptos::prelude::*;
+use tailwind_fuse::tw_merge;
+use crate::components::ui::button::{Button, ButtonVariant};
+
+#[component]
+pub fn ButtonAction(
+ children: Children,
+ #[prop(into)] on_action: Callback<()>,
+ #[prop(optional, into)] class: String,
+ #[prop(default = 1000)] hold_duration: u64,
+ #[prop(default = ButtonVariant::Default)] variant: ButtonVariant,
+) -> impl IntoView {
+ let is_holding = RwSignal::new(false);
+
+ let on_down = move |_| is_holding.set(true);
+ let on_up = move |_| is_holding.set(false);
+
+ Effect::new(move |_| {
+ if is_holding.get() {
+ leptos::task::spawn_local(async move {
+ gloo_timers::future::TimeoutFuture::new(hold_duration as u32).await;
+ if is_holding.get_untracked() {
+ on_action.run(());
+ is_holding.set(false);
+ }
+ });
+ }
+ });
+
+ let merged_class = move || tw_merge!(
+ "relative overflow-hidden transition-all active:scale-[0.98]",
+ class.clone()
+ );
+
+ view! {
+
+
+ }
+}
diff --git a/frontend/src/components/ui/mod.rs b/frontend/src/components/ui/mod.rs
index e8810ef..884bb2e 100644
--- a/frontend/src/components/ui/mod.rs
+++ b/frontend/src/components/ui/mod.rs
@@ -2,6 +2,7 @@ pub mod accordion;
pub mod alert_dialog;
pub mod badge;
pub mod button;
+pub mod button_action;
pub mod card;
pub mod checkbox;
pub mod context_menu;