detaxine_ui/lib.rs
1//! # detaxine-ui
2//!
3//! `detaxine-ui` is a Leptos + Tailwind CSS component library compiled to WebAssembly.
4//! It provides a full set of accessible, themeable UI components for building modern
5//! web applications with [Leptos](https://leptos.dev).
6//!
7//! ## Live Demo
8//!
9//! [https://elonaire.github.io/detaxine-ui/](https://elonaire.github.io/detaxine-ui/)
10//!
11//! ## Installation
12//!
13//! Install the CLI and let it scaffold everything for you:
14//!
15//! ```bash
16//! cargo install detaxine-ui-cli
17//! dtx init my-app
18//! cd my-app
19//! trunk serve
20//! ```
21//!
22//! `dtx init` creates a ready-to-run Leptos + Trunk project with `detaxine-ui`
23//! as a dependency, `input.css` configured, and a Tailwind binary in place.
24//!
25//! ## Available Components
26//!
27//! ### Actions
28//!
29//! | Component | Description |
30//! |---|---|
31//! | [`BasicButton`] | Button with optional icon, disabled state, and custom styles |
32//! | [`ButtonGroup`] | Inline group of buttons with shared styles and rounded ends |
33//! | [`Carousel`] | Sliding panel carousel with navigation and dot indicators |
34//!
35//! ### Data Display
36//!
37//! | Component | Description |
38//! |---|---|
39//! | [`Badge`] | Overlaid count or status indicator anchored to a child element |
40//! | [`Chip`] | Removable tag with color temperature variants |
41//! | [`LabelTag`] | Static color-coded label |
42//! | [`Timeline`] | Vertical list of timestamped steps with icon/image/circle heads |
43//! | [`DataTable`] | Sortable, paginated table with optional row actions |
44//! | [`Pagination`] | Page navigation control |
45//!
46//! ### Feedback
47//!
48//! | Component | Description |
49//! |---|---|
50//! | [`BasicModal`] | Portal-based dialog with contextual icons and footer actions |
51//! | [`Popover`] | Viewport-aware floating panel with auto-alignment |
52//! | [`ProgressBar`] | Horizontal progress bar with determinate and indeterminate modes |
53//! | [`CircularProgress`] | Circular progress ring with optional percentage label |
54//! | [`Spinner`] | Animated SVG loading spinner with optional backdrop |
55//!
56//! ### Forms
57//!
58//! | Component | Description |
59//! |---|---|
60//! | [`InputField`] | Text, email, password, number, file and all other HTML input types |
61//! | [`CustomFileInput`] | Styled file picker with chip-based selected file list |
62//! | [`Textarea`] | Multi-line text input |
63//! | [`SelectInput`] | Native `<select>` dropdown |
64//! | [`CustomSelectInput`] | Searchable, chip-based single or multi-select |
65//! | [`CheckboxInputField`] | Single checkbox input |
66//! | [`CheckboxGroup`] | Grouped checkboxes with shared selection state |
67//! | [`RadioInputField`] | Single radio input |
68//! | [`RadioInputGroup`] | Grouped radio inputs with shared selection state |
69//! | [`ToggleSwitch`] | Boolean toggle built on a hidden checkbox |
70//! | [`DatePicker`] | Calendar date picker with min/max and disabled date support |
71//! | [`RichTextEditor`] | Contenteditable rich text editor with formatting toolbar |
72//! | [`ReactiveForm`] | Form wrapper that auto-submits on valid input/change |
73//!
74//! ### Navigation
75//!
76//! | Component | Description |
77//! |---|---|
78//! | [`Breadcrumbs`] | Route-derived breadcrumb trail |
79//! | [`Panel`] | Collapsible panel with clickable title |
80//! | [`Collapse`] | Accordion group of panels |
81//! | [`Tabs`] | Scrollable tabbed view with slot-based content |
82//! | [`Stepper`] | Multi-step form wizard with per-step validation |
83//!
84//! ## Quick Start
85//!
86//! ```rust
87//! use leptos::prelude::*;
88//! use detaxine_ui::components::actions::button::BasicButton;
89//!
90//! #[component]
91//! fn App() -> impl IntoView {
92//! view! {
93//! <BasicButton
94//! button_text="Click me"
95//! style_ext="bg-primary text-white hover:bg-secondary"
96//! onclick=Callback::new(move |_| leptos::logging::log!("clicked"))
97//! />
98//! }
99//! }
100//! ```
101//!
102//! ## Component Examples
103//!
104//! ### [`BasicButton`] and [`ButtonGroup`]
105//!
106//! ```rust
107//! use leptos::prelude::*;
108//! use detaxine_ui::components::actions::button::{BasicButton, ButtonGroup};
109//!
110//! #[component]
111//! fn Example() -> impl IntoView {
112//! view! {
113//! <ButtonGroup style_ext="bg-primary text-white hover:bg-secondary">
114//! <BasicButton button_text="Save" />
115//! <BasicButton button_text="Cancel" />
116//! </ButtonGroup>
117//! }
118//! }
119//! ```
120//!
121//! ### [`BasicModal`]
122//!
123//! ```rust
124//! use leptos::prelude::*;
125//! use detaxine_ui::components::feedback::modal::modal::{BasicModal, UseCase};
126//!
127//! #[component]
128//! fn Example() -> impl IntoView {
129//! let is_open = RwSignal::new(false);
130//! view! {
131//! <BasicModal
132//! title="Confirm"
133//! is_open=is_open
134//! use_case=UseCase::Confirmation
135//! on_click_primary=Callback::new(|_| {})
136//! on_cancel=Callback::new(|_| {})
137//! >
138//! <p>"Are you sure?"</p>
139//! </BasicModal>
140//! }
141//! }
142//! ```
143//!
144//! ### [`DataTable`]
145//!
146//! ```rust
147//! use leptos::prelude::*;
148//! use std::collections::HashMap;
149//! use detaxine_ui::components::data_display::table::data_table::{Column, DataTable, TableCellData};
150//!
151//! #[component]
152//! fn Example() -> impl IntoView {
153//! let columns = vec![
154//! Column::new("Name", true),
155//! Column::new("Role", false),
156//! ];
157//! let rows = vec![{
158//! let mut row = HashMap::new();
159//! row.insert("id".to_string(), TableCellData::String("1".into()));
160//! row.insert("Name".to_string(), TableCellData::String("Alice".into()));
161//! row.insert("Role".to_string(), TableCellData::String("Engineer".into()));
162//! row
163//! }];
164//! let data = RwSignal::new((columns, rows));
165//! view! {
166//! <DataTable data=data editable=true deletable=true />
167//! }
168//! }
169//! ```
170//!
171//! ### [`Stepper`]
172//!
173//! ```rust
174//! use leptos::prelude::*;
175//! use leptos::html::Form;
176//! use detaxine_ui::components::navigation::stepper::{Step, Stepper, StepInfo};
177//! use detaxine_ui::components::forms::input::{InputField, InputFieldType};
178//!
179//! #[component]
180//! fn Example() -> impl IntoView {
181//! view! {
182//! <Stepper
183//! step_labels=RwSignal::new(vec![
184//! StepInfo::new("Account", None),
185//! StepInfo::new("Confirm", None),
186//! ])
187//! final_button_text="Finish"
188//! send_all_form_refs=Callback::new(|_| {})
189//! is_linear=true
190//! >
191//! <Step>
192//! <InputField
193//! field_type=InputFieldType::Email
194//! label="Email"
195//! name="email"
196//! id_attr="email"
197//! required=true
198//! />
199//! </Step>
200//! <Step>
201//! <p>"Review and submit."</p>
202//! </Step>
203//! </Stepper>
204//! }
205//! }
206//! ```
207//!
208//! ### [`Tabs`]
209//!
210//! ```rust
211//! use leptos::prelude::*;
212//! use detaxine_ui::components::navigation::tabs::{Tab, TabLabel, Tabs};
213//!
214//! #[component]
215//! fn Example() -> impl IntoView {
216//! let labels = RwSignal::new(vec![
217//! TabLabel::new(ViewFn::from(|| view! { <span>"First"</span> })),
218//! TabLabel::new(ViewFn::from(|| view! { <span>"Second"</span> })),
219//! ]);
220//! view! {
221//! <Tabs tab_labels=labels>
222//! <Tab slot><p>"First tab content"</p></Tab>
223//! <Tab slot><p>"Second tab content"</p></Tab>
224//! </Tabs>
225//! }
226//! }
227//! ```
228//!
229//! ### [`Form Handling`]
230//!
231//! `ReactiveForm` wraps your fields and fires a native `submit` event
232//! automatically whenever `checkValidity()` returns true, on every
233//! `input` or `change` event. Read form data with the standard
234//! `FormData` API:
235//!
236//! ```rust
237//! use leptos::prelude::*;
238//! use web_sys::{HtmlFormElement, SubmitEvent};
239//! use serde::{Deserialize, Serialize};
240//! use detaxine_ui::{
241//! components::forms::{
242//! checkbox::{CheckboxGroup, CheckboxOption},
243//! reactive_form::ReactiveForm,
244//! input::{InputField, InputFieldType}
245//! },
246//! utils::forms::deserialize_form,
247//! };
248//! use leptos::wasm_bindgen::JsCast;
249//! use std::collections::HashSet;
250//!
251//! #[derive(Debug, Serialize, Deserialize)]
252//! struct RegistrationForm {
253//! interests: Vec<String>, // note that this matches the form field name attribute
254//! email: String, // note that this matches the form field name attribute
255//! }
256//!
257//! #[component]
258//! fn Example() -> impl IntoView {
259//! let selected = RwSignal::new(HashSet::new());
260//! let form_ref = NodeRef::new();
261//! let (registration_form_is_valid, set_registration_form_is_valid) = signal(false);
262//!
263//! let handle_submit = move |ev: SubmitEvent| {
264//! ev.prevent_default();
265//!
266//! let target = ev.target()
267//! .and_then(|t| t.dyn_into::<HtmlFormElement>().ok());
268//!
269//! if let Some(form) = target {
270//! set_registration_form_is_valid.set(form.check_validity());
271//!
272//! // You can use this in case you want to perform an action
273//! // based on the condition that the submit event was triggered
274//! // by, let's say a submit button.
275//! if let Some(_submitter) = ev.submitter() {
276//!
277//! }
278//! }
279//!
280//! // You might also put this into its own Effect for guaranteed reactivity.
281//! if registration_form_is_valid.get() {
282//! // This is how you deserialize a form's value.
283//! let deserialized_registration_form = deserialize_form::<RegistrationForm>(
284//! &form_ref,
285//! false,
286//! Some(&["interests"]), // note that this matches the form field name
287//! );
288//! }
289//! };
290//!
291//! view! {
292//! <ReactiveForm form_ref=form_ref on:submit=handle_submit>
293//! <CheckboxGroup
294//! legend="Interests"
295//! name="interests"
296//! options=RwSignal::new(vec![
297//! CheckboxOption::new("rust", "Rust", None),
298//! CheckboxOption::new("leptos", "Leptos", None),
299//! ])
300//! selected_values=selected
301//! />
302//! <InputField
303//! field_type=InputFieldType::Email
304//! name="email"
305//! label="Email"
306//! required=true
307//! />
308//! </ReactiveForm>
309//! }
310//! }
311//! ```
312//!
313//! ### [`RichTextEditor`]
314//!
315//! ```rust
316//! use leptos::prelude::*;
317//! use detaxine_ui::components::content::richtext_editor::{ExtraFormatingOption, RichTextEditor};
318//!
319//! #[component]
320//! fn Example() -> impl IntoView {
321//! let content = RwSignal::new("<p><br></p>".to_string());
322//! view! {
323//! <RichTextEditor
324//! initial_content=content
325//! id_attr="editor"
326//! name="body"
327//! extra_formating_options=vec![
328//! ExtraFormatingOption::Heading,
329//! ExtraFormatingOption::CodeBlock,
330//! ExtraFormatingOption::Lists,
331//! ExtraFormatingOption::ImageUpload,
332//! ]
333//! />
334//! }
335//! }
336//! ```
337//!
338//! ## Design Notes
339//!
340//! - **WebAssembly-first** — compiled to WASM via `wasm-pack` / `trunk`, with full
341//! access to `web_sys` APIs for DOM interaction.
342//! - **Tailwind CSS v4** — styles are distributed via `input.css` using `@apply`
343//! and `@source inline()` so the library's classes are always included in the
344//! consumer's build regardless of scanning boundaries.
345//! - **Reactive by default** — all stateful props accept `Signal`, `RwSignal`, or
346//! `MaybeProp` so components integrate naturally into any Leptos reactive graph.
347//! - **Accessible** — form components forward `id`, `name`, `required`, `readonly`,
348//! and `aria-*` attributes to the underlying HTML elements.
349//! - **Zero opinion on layout** — components expose `style_ext`, `ext_input_styles`,
350//! `ext_wrapper_styles`, and similar props so consumers can apply any Tailwind
351//! utility without forking the library.
352
353pub mod components;
354pub mod utils;
355
356// Re-exports
357pub use icondata;