htmx_components/server/
modal.rs

1use super::transition::Transition;
2use super::yc_control::YcControl;
3use crate::server::attrs::Attrs;
4use rscx::{component, html, props};
5
6const MODALS_ID: &str = "modal-live-region";
7pub fn modal_target() -> String {
8    format!("#{}", MODALS_ID)
9}
10
11pub enum ModalSize {
12    Small,
13    Medium,
14    Large,
15    SmallScreen,
16    MediumScreen,
17    Custom(String),
18}
19
20#[props]
21pub struct ModalProps {
22    #[builder(default = ModalSize::Medium)]
23    size: ModalSize,
24
25    #[builder(setter(into))]
26    children: String,
27}
28
29#[component]
30pub fn Modal(props: ModalProps) -> String {
31    html! {
32        <YcControl
33            control="modal"
34            class="relative z-10"
35            role="dialog"
36            aria_labelledby="modal-title"
37            attrs=Attrs::with("aria-modal", "true".into())
38        >
39            <Transition
40                class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
41                enter="ease-out duration-300"
42                enter_from="opacity-0"
43                enter_to="opacity-100"
44                leave="ease-in duration-200"
45                leave_from="opacity-100"
46                leave_to="opacity-0"
47            />
48            <div class="fixed inset-0 z-10 w-screen overflow-y-auto">
49                <div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
50                    <Transition
51                        class={
52                            let m_width = match props.size {
53                                ModalSize::Small => "sm:max-w-sm".to_string(),
54                                ModalSize::Medium => "sm:max-w-md".to_string(),
55                                ModalSize::Large => "sm:max-w-lg".to_string(),
56                                ModalSize::SmallScreen => "sm:max-w-screen-sm".to_string(),
57                                ModalSize::MediumScreen => "sm:max-w-screen-md".to_string(),
58                                ModalSize::Custom(width) => width,
59                            };
60                            format!("relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 w-full {} sm:p-6", m_width)
61                        }
62                        attrs=Attrs::with("data-modal-panel", "true".into())
63                        enter="ease-out duration-300"
64                        enter_from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
65                        enter_to="opacity-100 translate-y-0 sm:scale-100"
66                        leave="ease-in duration-200"
67                        leave_from="opacity-100 translate-y-0 sm:scale-100"
68                        leave_to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
69                        >
70                            <div>
71                                {props.children}
72                            </div>
73                    </Transition>
74                </div>
75            </div>
76        </YcControl>
77    }
78}
79
80// TODO! Provide option to show close "X" link in modal
81/*
82    <div class="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
83        <button type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
84            <span class="sr-only">Close</span>
85            <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
86                <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
87            </svg>
88        </button>
89    </div>
90*/
91
92#[component]
93pub fn ConfirmDeleteModal() -> String {
94    html! {
95        <Modal>
96            <div class="sm:flex sm:items-start">
97                <div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
98                    <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
99                        <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
100                    </svg>
101                </div>
102                <div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
103                    <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title" data-confirm-delete-title>Deactivate account</h3>
104                    <div class="mt-2">
105                        <p class="text-sm text-gray-500" data-confirm-delete-message>Are you sure you want to delete this item?.</p>
106                    </div>
107                </div>
108            </div>
109            <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
110                <button data-toggle-action="close" data-confirm-action="delete" type="button" class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto">Delete</button>
111                <button data-toggle-action="close" type="button" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">Cancel</button>
112            </div>
113        </Modal>
114    }
115}
116
117/**
118 * ModalLiveRegion
119 *
120 * Holds all pre-rendered modals to to be rendered client-side by Modals control.
121 */
122#[component]
123pub fn ModalLiveRegion() -> String {
124    html! {
125      <div id=MODALS_ID>
126          <section data-modal-content>
127          </section>
128          <template id="tpl-confirm-delete-modal">
129              <ConfirmDeleteModal />
130          </template>
131      </div>
132    }
133}