selectrs/yew.rs
1use std::rc::Rc;
2use yew::prelude::*;
3
4/// Properties for configuring the `Select` component.
5///
6/// The `Select` component creates a customizable dropdown list that allows you to choose
7/// a single or multiple options. It can be styled with custom classes and inline styles,
8/// and supports additional behaviors like multiple selections, disabled options, and
9/// change events for updating the selected value.
10///
11/// It works in combination with `Group` and `Option` components to provide a rich UI for
12/// selecting options from a list.
13/// Refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attributes) for more info.
14#[derive(Properties, PartialEq, Clone)]
15pub struct SelectProps {
16 /// The name of the select component.
17 ///
18 /// This represents the name attribute used in the underlying HTML `select` element.
19 /// It is important when the component is part of a form, as it defines the field name.
20 /// Defaults to an empty string if not provided.
21 #[prop_or_default]
22 pub name: &'static str,
23
24 /// The id of the select component.
25 ///
26 /// This represents the id attribute used in the underlying HTML `select` element.
27 /// It helps in uniquely identifying the component within the DOM.
28 /// Defaults to an empty string if not provided.
29 #[prop_or_default]
30 pub id: &'static str,
31
32 /// The placeholder text for the select component.
33 ///
34 /// This text is displayed when no option is selected and the `select` element is empty.
35 /// It provides a hint to the user on what to select. It is not visible after an option is chosen.
36 /// Defaults to an empty string if not provided.
37 #[prop_or_default]
38 pub placeholder: &'static str,
39
40 /// Whether the select component allows multiple selections.
41 ///
42 /// If set to `true`, the user can select more than one option. If set to `false`, only one option can be selected at a time.
43 /// Defaults to `false` if not provided.
44 #[prop_or_default]
45 pub multiple: bool,
46
47 /// Whether the select component is disabled.
48 ///
49 /// If set to `true`, the select component will be unresponsive and users will not be able to interact with it.
50 /// Defaults to `false` if not provided.
51 #[prop_or_default]
52 pub disabled: bool,
53
54 /// Whether the select option is required.
55 ///
56 /// This Boolean attribute indicates that a value must be selected from the dropdown.
57 /// If not selected, form submission will be blocked. Defaults to `false` if not provided.
58 #[prop_or_default]
59 pub required: bool,
60
61 /// The visible size of the select dropdown.
62 ///
63 /// If the `multiple` attribute is specified, this defines the number of visible rows in the list.
64 /// This is helpful when displaying a scrollable list of options.
65 /// Defaults to `0`, which means the default layout will be used.
66 #[prop_or(0)]
67 pub size: u64,
68
69 /// The form to associate the select element with.
70 ///
71 /// This attribute allows you to associate the select element with a form elsewhere in the document.
72 /// The value must be the `id` of a form element in the same document. If not provided, the `select`
73 /// element will be associated with its nearest ancestor form. Defaults to an empty string if not provided.
74 #[prop_or_default]
75 pub form: &'static str,
76
77 /// The autocomplete hint for the select element.
78 ///
79 /// This string provides a hint to the user agent's autocomplete feature, helping it to
80 /// pre-fill values based on the user's past selections. The value should match one of the
81 /// valid autocomplete values for the `<select>` element. Defaults to an empty string if not provided.
82 #[prop_or_default]
83 pub autocomplete: &'static str,
84
85 /// Automatically focuses the select element when the page loads.
86 ///
87 /// This Boolean attribute lets you specify that the select element should automatically
88 /// gain input focus when the page loads. Only one form element in a document can have this attribute.
89 /// Defaults to `false` if not provided.
90 #[prop_or_default]
91 pub autofocus: bool,
92
93 /// Callback triggered when the selected values change.
94 ///
95 /// This callback is executed whenever the user selects or deselects an option in the select box.
96 /// It receives a vector of strings representing the selected options. This is useful for updating
97 /// the selected values in the application state. Defaults to a no-op if not provided.
98 #[prop_or_default]
99 pub onchange: Callback<Vec<String>>,
100
101 /// Child components for the select component.
102 ///
103 /// This property allows you to pass one or more `Group` components as children of the `Select` component.
104 /// The `Group` components contain the `Option` components, which represent the individual selectable options.
105 /// Defaults to an empty list of children if not provided.
106 #[prop_or_default]
107 pub children: ChildrenWithProps<Group>,
108
109 /// Custom CSS class for the select container.
110 ///
111 /// This property allows for custom styling of the select container by specifying one or more CSS classes.
112 /// It is applied to the outer wrapper of the `select` element. Defaults to an empty string if not provided.
113 #[prop_or_default]
114 pub class: &'static str,
115
116 /// Inline styles for the select container.
117 ///
118 /// This property allows for custom inline styles to be applied directly to the select container.
119 /// It provides more granular control over the styling of the component, without the need for external CSS.
120 /// Defaults to an empty string if not provided.
121 #[prop_or_default]
122 pub style: &'static str,
123
124 /// Custom CSS class for the label container.
125 ///
126 /// This property allows for custom styling of the labels in the `Select` component. It applies to the wrapper
127 /// around the labels (for multi-selects or grouped selections). Defaults to an empty string if not provided.
128 #[prop_or_default]
129 pub labels_class: &'static str,
130
131 /// Inline styles for the label container.
132 ///
133 /// This property allows for custom inline styles to be applied directly to the label container. This is useful
134 /// for modifying the appearance of the labels within the select dropdown.
135 /// Defaults to an empty string if not provided.
136 #[prop_or_default]
137 pub labels_style: &'static str,
138
139 /// Custom CSS class for the individual labels.
140 ///
141 /// This property allows for custom styling of the labels within the dropdown options.
142 /// Defaults to an empty string if not provided.
143 #[prop_or_default]
144 pub label_class: &'static str,
145
146 /// Inline styles for the individual labels.
147 ///
148 /// This property allows for custom inline styles to be applied directly to the individual labels.
149 /// It can be used to adjust the style of each label element within the `select` component.
150 /// Defaults to an empty string if not provided.
151 #[prop_or_default]
152 pub label_style: &'static str,
153
154 /// Custom CSS class for the close button (for multi-select).
155 ///
156 /// This property allows for custom styling of the close button that appears next to selected values in a multi-select dropdown.
157 /// Defaults to an empty string if not provided.
158 #[prop_or_default]
159 pub close_class: &'static str,
160
161 /// Inline styles for the close button (for multi-select).
162 ///
163 /// This property allows for custom inline styles to be applied directly to the close button.
164 /// This can be used to change the appearance of the button that removes selected options in a multi-select.
165 /// Defaults to an empty string if not provided.
166 #[prop_or_default]
167 pub close_style: &'static str,
168
169 /// Custom CSS class for the select dropdown.
170 ///
171 /// This property allows for custom styling of the select dropdown box itself. This class is applied to the
172 /// `select` element in the rendered HTML. Defaults to an empty string if not provided.
173 #[prop_or_default]
174 pub select_class: &'static str,
175
176 /// Inline styles for the select dropdown.
177 ///
178 /// This property allows for custom inline styles to be applied directly to the select dropdown.
179 /// It gives more granular control over the dropdown's appearance, such as height, width, or border color.
180 /// Defaults to an empty string if not provided.
181 #[prop_or_default]
182 pub select_style: &'static str,
183}
184
185/// Select Component
186///
187/// A Yew component for creating a customizable select dropdown with support for single or multiple selections.
188/// The `Select` component can handle options, dynamically manage selection, and customize its appearance and behavior.
189///
190/// # Properties
191/// The component uses the `SelectProps` struct for its properties. Key properties include:
192///
193/// - **name**: The name of the select element (`&'static str`). Default: `""`.
194/// - **id**: The ID of the select element (`&'static str`). Default: `""`.
195/// - **placeholder**: Placeholder text for the select input when no options are selected (`&'static str`). Default: `""`.
196/// - **multiple**: Whether the select allows multiple selections (`bool`). Default: `false`.
197/// - **disabled**: Whether the select element is disabled (`bool`). Default: `false`.
198/// - **onchange**: Callback triggered when the selected values change (`Callback<Vec<String>>`). Default: no-op.
199/// - **children**: A collection of `Option` components as children (`ChildrenWithProps<Option>`). Default: empty.
200/// - **class**: Custom CSS class for the select container (`&'static str`). Default: `""`.
201/// - **style**: Inline styles for the select container (`&'static str`). Default: `""`.
202/// - **labels_class**: Custom class for the selected options' labels (`&'static str`). Default: `""`.
203/// - **labels_style**: Inline styles for the selected options' labels (`&'static str`). Default: `""`.
204/// - **label_class**: Custom class for each label when an option is selected (`&'static str`). Default: `""`.
205/// - **label_style**: Inline styles for each label when an option is selected (`&'static str`). Default: `""`.
206/// - **close_class**: Custom class for the close button (`&'static str`). Default: `""`.
207/// - **close_style**: Inline styles for the close button (`&'static str`). Default: `""`.
208/// - **select_class**: Custom CSS class for the select element itself (`&'static str`). Default: `""`.
209/// - **select_style**: Inline styles for the select element (`&'static str`). Default: `""`.
210/// - **size**: The number of visible options in a scrolling select (`usize`). Default: `0`.
211/// - **required**: Whether the select field is required (`bool`). Default: `false`.
212/// - **form**: The ID of the form that the select is associated with (`&'static str`). Default: `""`.
213/// - **autocomplete**: Hint for the browser's autocomplete feature (`&'static str`). Default: `""`.
214/// - **autofocus**: Whether the select should gain focus when the page loads (`bool`). Default: `false`.
215///
216/// # Features
217/// - Supports both single and multiple selection modes.
218/// - Customizable via CSS classes and inline styles.
219/// - Optionally displays a placeholder and manages selected items with chips (for multiple selections).
220/// - Trigger an `onchange` callback whenever the selection changes.
221///
222/// # Examples
223///
224/// ## Basic Usage
225/// ```rust
226/// use yew::prelude::*;
227/// use selectrs::yew::{Select, Option, Group};
228///
229/// #[function_component(App)]
230/// pub fn app() -> Html {
231/// let onchange = Callback::from(|selected_values: Vec<String>| {
232/// // Handle the selected values
233/// log::info!("Selected: {:?}", selected_values);
234/// });
235///
236/// html! {
237/// <Select onchange={onchange}>
238/// <Group>
239/// <Option value="Option1" label="Option 1" />
240/// <Option value="Option2" label="Option 2" />
241/// <Option value="Option3" label="Option 3" />
242/// </Group>
243/// </Select>
244/// }
245/// }
246/// ```
247///
248/// ## Multiple Selection
249/// ```rust
250/// use yew::prelude::*;
251/// use selectrs::yew::{Select, Option, Group};
252///
253/// #[function_component(App)]
254/// pub fn app() -> Html {
255/// let onchange = Callback::from(|selected_values: Vec<String>| {
256/// // Handle the selected values
257/// log::info!("Selected: {:?}", selected_values);
258/// });
259///
260/// html! {
261/// <Select multiple=true onchange={onchange}>
262/// <Group>
263/// <Option value="Option1" label="Option 1" />
264/// <Option value="Option2" label="Option 2" />
265/// <Option value="Option3" label="Option 3" />
266/// </Group>
267/// </Select>
268/// }
269/// }
270/// ```
271///
272/// ## Custom Styling and Placeholder
273/// ```rust
274/// use yew::prelude::*;
275/// use selectrs::yew::{Select, Option, Group};
276///
277/// #[function_component(App)]
278/// pub fn app() -> Html {
279/// html! {
280/// <Select
281/// placeholder="Select an option..."
282/// class="custom-select"
283/// style="width: 200px"
284/// size={5}
285/// >
286/// <Group>
287/// <Option value="Option1" label="Option 1" />
288/// <Option value="Option2" label="Option 2" />
289/// </Group>
290/// </Select>
291/// }
292/// }
293/// ```
294///
295/// ## With a Required Field
296/// ```rust
297/// use yew::prelude::*;
298/// use selectrs::yew::{Select, Option, Group};
299///
300/// #[function_component(App)]
301/// pub fn app() -> Html {
302/// html! {
303/// <Select
304/// required=true
305/// onchange={Callback::from(|selected_values: Vec<String>| {
306/// log::info!("Selected: {:?}", selected_values);
307/// })}
308/// >
309/// <Group>
310/// <Option value="Option1" label="Option 1" />
311/// <Option value="Option2" label="Option 2" />
312/// </Group>
313/// </Select>
314/// }
315/// }
316/// ```
317///
318/// # Behavior
319/// - The `Select` component handles single and multiple selections dynamically.
320/// - The selected values are updated using the `onchange` callback whenever the user interacts with the select options.
321/// - When multiple selection mode is enabled, selected values are displayed as chips with a close button to remove individual selections.
322/// - A placeholder option is displayed when no value is selected and the select is not disabled.
323///
324/// # Notes
325/// - The `children` property must contain `Option` components to populate the select dropdown.
326/// - If the `multiple` property is `true`, multiple options can be selected at once. If `false`, only one option can be selected.
327/// - Custom styling can be applied to the select container, options, and labels via CSS classes or inline styles.
328#[function_component(Select)]
329pub fn select(props: &SelectProps) -> Html {
330 let SelectProps {
331 name,
332 id,
333 placeholder,
334 multiple,
335 disabled,
336 onchange,
337 children,
338 class,
339 style,
340 labels_class,
341 labels_style,
342 label_class,
343 label_style,
344 close_class,
345 close_style,
346 select_class,
347 select_style,
348 size,
349 required,
350 form,
351 autocomplete,
352 autofocus,
353 } = props.clone();
354
355 let selected_values = use_state(Vec::<String>::new);
356 let selected = (*selected_values).clone();
357
358 let handle_group_change = {
359 let selected_values = selected_values.clone();
360 let on_change = onchange.clone();
361 Callback::from(move |value: String| {
362 let mut current_values = (*selected_values).clone();
363 if multiple {
364 if current_values.contains(&value) {
365 current_values.retain(|v| v != &value);
366 } else {
367 current_values.push(value);
368 }
369 } else {
370 current_values = vec![value];
371 }
372 selected_values.set(current_values.clone());
373 on_change.emit(current_values);
374 })
375 };
376
377 let remove_chip = {
378 let selected_values = selected_values.clone();
379 let on_change = onchange.clone();
380 Callback::from(move |value: String| {
381 let mut current_values = (*selected_values).clone();
382 current_values.retain(|v| v != &value);
383 selected_values.set(current_values.clone());
384 on_change.emit(current_values);
385 })
386 };
387
388 html! {
389 <div class={class} style={style}>
390 { if multiple {
391 html! {
392 <div class={labels_class} style={labels_style}>
393 { for selected.clone().into_iter().map(|value| html! {
394 <div class={label_class} style={label_style}>
395 { value.clone() }
396 <button class={close_class} style={close_style} onclick={remove_chip.clone().reform(move |_| value.clone())}>
397 { "x" }
398 </button>
399 </div>
400 }) }
401 </div>
402 }
403 } else {
404 html! {}
405 } }
406 <select
407 id={id}
408 name={name}
409 multiple={multiple}
410 class={select_class}
411 style={select_style}
412 disabled={disabled}
413 size={size.to_string()}
414 required={required}
415 form={form}
416 autocomplete={autocomplete}
417 autofocus={autofocus}
418 >
419 { if (!placeholder.is_empty() && selected.is_empty()) || disabled {
420 html! { <option disabled=true selected=true>{ placeholder }</option> }
421 } else {
422 html! {}
423 } }
424 if !disabled {
425 { for children.iter().map(|mut child| {
426 let props = Rc::make_mut(&mut child.props);
427
428 props.selected = selected.first().cloned().unwrap_or_default();
429 let handle_group_change = handle_group_change.clone();
430 props.onchange = Callback::from(move |value| handle_group_change.emit(value));
431
432 child
433 }) }
434 }
435 </select>
436 </div>
437 }
438}
439
440/// Properties for configuring the `Group` component.
441///
442/// The `Group` component allows you to group together `Option` elements.
443/// It provides customization for labels, selection behavior, and event handling. The group allows a common state and
444/// behavior across the contained options. This component supports customization of styles and classes, as well as
445/// interaction handling through the `onchange` callback.
446#[derive(Properties, PartialEq, Clone)]
447pub struct GroupProps {
448 /// The label for the group.
449 ///
450 /// This is the text that labels the entire group of options. This can be used to describe
451 /// the set of options the user is about to choose from, making it useful for accessibility.
452 /// Defaults to an empty string if not provided.
453 #[prop_or_default]
454 pub label: &'static str,
455
456 /// Indicates whether this is a group options.
457 ///
458 /// If `group` is set to `true`, the options in this group will be considered as part of the
459 /// `label` options group, where only one option can be selected at a time. If set to `false`, the
460 /// group is disabled. Defaults to `false` if not provided.
461 #[prop_or_default]
462 pub group: bool,
463
464 /// The currently selected option in the group.
465 ///
466 /// This represents the selected value within the group. It should be bound to a state to
467 /// reflect changes as the user selects different options. Defaults to an empty string if not provided.
468 #[prop_or_default]
469 pub selected: String,
470
471 /// Callback for when the selected option changes.
472 ///
473 /// This callback is triggered whenever the user selects a different option within the group.
474 /// The callback receives a string representing the newly selected option's value. Defaults to a no-op.
475 #[prop_or_default]
476 pub onchange: Callback<String>,
477
478 /// Child components of type `Option` for the group.
479 ///
480 /// This property allows you to pass one or more `Option` components as children of the `Group` component.
481 /// These `Option` components represent the individual selectable options within the group.
482 /// Defaults to an empty string if not provided.
483 #[prop_or_default]
484 pub children: ChildrenWithProps<Option>,
485
486 /// Custom CSS class for the group.
487 ///
488 /// This property allows for custom styling of the group container by specifying one or more CSS classes.
489 /// It is applied to the outer wrapper of the group, such as for styling the container element.
490 /// Defaults to an empty string if not provided.
491 #[prop_or_default]
492 pub class: &'static str,
493
494 /// Inline styles for the group.
495 ///
496 /// This property allows for custom inline styles to be applied directly to the group container.
497 /// It provides more granular control over the styling of the group and its elements without the need for external CSS.
498 /// Defaults to an empty string if not provided.
499 #[prop_or_default]
500 pub style: &'static str,
501}
502
503#[function_component(Group)]
504pub fn group(props: &GroupProps) -> Html {
505 let GroupProps {
506 label,
507 group,
508 selected,
509 onchange,
510 children,
511 class,
512 style,
513 } = props.clone();
514
515 if group {
516 html! {
517 <optgroup label={label} class={class} style={style}>
518 { for children.iter().map(|mut child| {
519 let props = Rc::make_mut(&mut child.props);
520 let is_selected = props.value == selected;
521 let onchange = onchange.clone();
522 let value = props.value;
523
524 props.selected = is_selected;
525 props.on_click = Callback::from(move |_| {
526 onchange.emit(value.to_string());
527 });
528
529 child
530 }) }
531 </optgroup>
532 }
533 } else {
534 html! {
535 { for children.iter().map(|mut child| {
536 let props = Rc::make_mut(&mut child.props);
537 let is_selected = props.value == selected;
538 let onchange = onchange.clone();
539 let value = props.value;
540
541 props.selected = is_selected;
542 props.on_click = Callback::from(move |_| {
543 onchange.emit(value.to_string());
544 });
545
546 child
547 }) }
548 }
549 }
550}
551
552/// Properties for configuring the `Option` component.
553///
554/// The `Option` component represents an individual selectable option within a group of options, such as a
555/// string, a radio button, checkbox, or a custom select element. It allows for customization of the option's value,
556/// label, selection state, and appearance. The component also supports event handling for user interactions
557/// (e.g., click events).
558#[derive(Properties, PartialEq, Clone)]
559pub struct OptionProps {
560 /// The value of the option.
561 ///
562 /// This is the underlying value associated with the option. It is typically used when the user selects
563 /// this option, and is submitted or processed based on the selected state of the option. Defaults to an
564 /// empty string if not provided.
565 #[prop_or_default]
566 pub value: &'static str,
567
568 /// The label displayed for the option.
569 ///
570 /// This property defines the content that is shown to the user as the label for the option. It can be
571 /// any valid child element, such as text, icons, or other components. Defaults to no label (empty).
572 #[prop_or_default]
573 pub label: Children,
574
575 /// Whether the option is selected.
576 ///
577 /// This property indicates if the option is currently selected. If set to `true`, the option is visually
578 /// marked as selected, and it may trigger related behavior such as updating state or submitting a form.
579 /// Defaults to `false` if not provided.
580 #[prop_or_default]
581 pub selected: bool,
582
583 /// Whether the option is disabled.
584 ///
585 /// If set to `true`, the option is considered disabled, meaning it cannot be interacted with by the user.
586 /// Disabled options may be visually different (e.g., grayed out). Defaults to `false` if not provided.
587 #[prop_or_default]
588 pub disabled: bool,
589
590 /// Callback for when the option is clicked.
591 ///
592 /// This callback is invoked when the user clicks on the option. It can be used to trigger actions such as
593 /// updating the selected state or performing any other interaction. Defaults to a no-op (no action).
594 #[prop_or_default]
595 pub on_click: Callback<()>,
596
597 /// Custom CSS class for the option.
598 ///
599 /// This property allows you to specify a custom CSS class for the option. This class is applied to the
600 /// individual option container, enabling you to style it differently from other options. Defaults to an empty
601 /// string if not provided.
602 #[prop_or_default]
603 pub class: &'static str,
604
605 /// Inline styles for the option.
606 ///
607 /// This property enables you to apply inline styles directly to the option element. It allows for precise
608 /// customization of the option's appearance without needing an external stylesheet. Defaults to an empty string
609 /// if not provided.
610 #[prop_or_default]
611 pub style: &'static str,
612
613 /// Custom class for a selected option.
614 ///
615 /// This property defines a custom CSS class that is applied when the option is selected. It enables you to
616 /// style the selected option differently, such as changing its background color or text style. Defaults to an
617 /// empty string if not provided.
618 #[prop_or_default]
619 pub selected_class: &'static str,
620
621 /// Inline styles for a selected option.
622 ///
623 /// This property defines inline styles applied when the option is selected. It provides direct control over
624 /// the selected state styling, allowing for unique visual differentiation between selected and non-selected
625 /// options. Defaults to an empty string if not provided.
626 #[prop_or_default]
627 pub selected_style: &'static str,
628}
629
630#[function_component(Option)]
631pub fn option(props: &OptionProps) -> Html {
632 let OptionProps {
633 label,
634 selected,
635 disabled,
636 on_click,
637 class,
638 style,
639 selected_style,
640 selected_class,
641 ..
642 } = props.clone();
643
644 let handle_click = {
645 let on_click = on_click.clone();
646 Callback::from(move |_| on_click.emit(()))
647 };
648
649 html! {
650 <option
651 class={format!("{} {}", class, if selected { selected_class } else { "" })}
652 style={format!("{} {}", style, if selected { selected_style } else { "" })}
653 onclick={move |ev: MouseEvent| {
654 ev.prevent_default();
655 handle_click.emit(ev);
656 }}
657 disabled={disabled}
658 >
659 { label }
660 </option>
661 }
662}