Skip to main content

leptos_forms_rs/components/
rich_text_input.rs

1use crate::core::types::FieldValue;
2use leptos::prelude::*;
3
4/// Rich text input component with advanced formatting capabilities
5#[component]
6pub fn RichTextInput(
7    /// Field name for the input
8    #[prop(into)]
9    name: String,
10    /// Current value of the field
11    #[prop(into)]
12    value: Signal<FieldValue>,
13    /// Callback when the value changes
14    #[prop(into)]
15    _on_change: Callback<FieldValue>,
16    /// Placeholder text
17    #[prop(optional, into)]
18    placeholder: Option<String>,
19    /// Whether the field is required
20    #[prop(optional)]
21    required: Option<bool>,
22    /// Whether the field is disabled
23    #[prop(optional)]
24    disabled: Option<bool>,
25    /// CSS classes
26    #[prop(optional, into)]
27    class: Option<String>,
28    /// Error message to display
29    #[prop(optional, into)]
30    error: Option<String>,
31    /// Whether the field has an error
32    #[prop(optional)]
33    has_error: Option<bool>,
34    /// Toolbar configuration
35    #[prop(optional)]
36    show_toolbar: Option<bool>,
37    /// Minimum height in pixels
38    #[prop(optional)]
39    min_height: Option<u32>,
40    /// Maximum height in pixels
41    #[prop(optional)]
42    max_height: Option<u32>,
43) -> impl IntoView {
44    let show_toolbar = show_toolbar.unwrap_or(true);
45    let min_height = min_height.unwrap_or(200);
46    let max_height = max_height.unwrap_or(600);
47
48    let current_value = move || match value.get() {
49        FieldValue::String(s) => s,
50        _ => String::new(),
51    };
52
53    let toolbar_view = move || {
54        if show_toolbar {
55            Some(view! {
56                <div class="rich-text-toolbar">
57                    <button type="button" class="toolbar-btn">"B"</button>
58                    <button type="button" class="toolbar-btn">"I"</button>
59                    <button type="button" class="toolbar-btn">"U"</button>
60                    <div class="toolbar-separator"></div>
61                    <button type="button" class="toolbar-btn">"โ€ข"</button>
62                    <button type="button" class="toolbar-btn">"1."</button>
63                    <div class="toolbar-separator"></div>
64                    <button type="button" class="toolbar-btn">"๐Ÿ”—"</button>
65                    <button type="button" class="toolbar-btn">"๐Ÿ–ผ๏ธ"</button>
66                    <div class="toolbar-separator"></div>
67                    <button type="button" class="toolbar-btn">"๐Ÿงน"</button>
68                </div>
69            })
70        } else {
71            None
72        }
73    };
74
75    let error_view = move || {
76        error.as_ref().map(|error_msg| {
77            view! {
78                <div class="error-message">
79                    {error_msg.clone()}
80                </div>
81            }
82        })
83    };
84
85    view! {
86        <div class={format!("rich-text-input {}", class.unwrap_or_default())}>
87            {toolbar_view}
88
89            <textarea
90                name={name.clone()}
91                placeholder={placeholder}
92                required={required}
93                disabled={disabled}
94                class={move || {
95                    let mut classes = vec!["rich-text-editor"];
96                    if has_error.unwrap_or(false) {
97                        classes.push("error");
98                    }
99                    if disabled.unwrap_or(false) {
100                        classes.push("disabled");
101                    }
102                    classes.join(" ")
103                }}
104                style={format!("min-height: {}px; max-height: {}px;", min_height, max_height)}
105            >{current_value}</textarea>
106
107            {error_view}
108        </div>
109    }
110}