cranpose_liquid/widgets/
search_field.rs1use crate::material::Glass;
4use crate::material::LiquidModifierExt;
5use crate::theme::{liquid_colors, liquid_typography};
6use cranpose_foundation::text::TextFieldState;
7use cranpose_macros::composable;
8use cranpose_ui::text::{SpanStyle, TextStyle};
9use cranpose_ui::widgets::{
10 BasicTextFieldDecorated, BasicTextFieldOptions, Box, BoxSpec, Row, RowSpec, Text,
11};
12use cranpose_ui::Modifier;
13use cranpose_ui_layout::{Alignment, VerticalAlignment};
14
15#[derive(Clone, Debug, PartialEq)]
17pub struct LiquidSearchFieldSpec {
18 pub placeholder: String,
19 pub on_glass: bool,
21}
22
23impl Default for LiquidSearchFieldSpec {
24 fn default() -> Self {
25 Self {
26 placeholder: "Search".to_string(),
27 on_glass: true,
28 }
29 }
30}
31
32#[composable]
34#[allow(non_snake_case)]
35pub fn LiquidSearchField(modifier: Modifier, state: TextFieldState, spec: LiquidSearchFieldSpec) {
36 let colors = liquid_colors();
37 let typography = liquid_typography();
38
39 let base = if spec.on_glass {
40 Modifier::empty().glass_effect(Glass::regular())
41 } else {
42 let fill = colors.fill;
43 Modifier::empty()
44 .rounded_corners(999.0)
45 .draw_behind(move |scope| {
46 scope.draw_round_rect(
47 cranpose_ui_graphics::Brush::solid(fill),
48 cranpose_ui_graphics::CornerRadii::uniform(999.0),
49 );
50 })
51 };
52
53 let placeholder = spec.placeholder.clone();
54 Box(
55 base.then(modifier).padding_symmetric(14.0, 9.0),
56 BoxSpec::default().content_alignment(Alignment::new(
57 cranpose_ui_layout::HorizontalAlignment::Start,
58 cranpose_ui_layout::VerticalAlignment::CenterVertically,
59 )),
60 move || {
61 let placeholder = placeholder.clone();
62 let body = typography.body.clone();
63 Row(
64 Modifier::empty().fill_max_width(),
65 RowSpec::default().vertical_alignment(VerticalAlignment::CenterVertically),
66 move || {
67 crate::icons::Icon(crate::icons::SEARCH, 18.0, colors.secondary_label);
68 Box(Modifier::empty().width(8.0), BoxSpec::default(), || {});
69
70 let field_style = TextStyle {
71 span_style: SpanStyle {
72 color: Some(colors.label),
73 ..body.span_style.clone()
74 },
75 ..body.clone()
76 };
77 let placeholder_style = TextStyle {
78 span_style: SpanStyle {
79 color: Some(colors.secondary_label),
80 ..body.span_style.clone()
81 },
82 ..body.clone()
83 };
84 let placeholder = placeholder.clone();
85 Box(
90 Modifier::empty().weight(1.0),
91 BoxSpec::default(),
92 move || {
93 let placeholder = placeholder.clone();
94 let placeholder_style = placeholder_style.clone();
95 BasicTextFieldDecorated(
96 state,
97 Modifier::empty().fill_max_width(),
98 BasicTextFieldOptions {
99 text_style: field_style.clone(),
100 ..BasicTextFieldOptions::default()
101 },
102 move |inner| {
103 let empty = state.text().is_empty();
104 let placeholder = placeholder.clone();
105 let placeholder_style = placeholder_style.clone();
106 Box(
107 Modifier::empty().fill_max_width(),
108 BoxSpec::default().content_alignment(Alignment::new(
109 cranpose_ui_layout::HorizontalAlignment::Start,
110 cranpose_ui_layout::VerticalAlignment::CenterVertically,
111 )),
112 move || {
113 if empty {
114 Text(
115 placeholder.clone(),
116 Modifier::empty(),
117 placeholder_style.clone(),
118 );
119 }
120 inner.inner_text_field();
121 },
122 )
123 },
124 );
125 },
126 );
127 },
128 );
129 },
130 );
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn search_spec_defaults_to_a_search_label_and_glass() {
139 let spec = LiquidSearchFieldSpec::default();
140 assert_eq!(spec.placeholder, "Search");
141 assert!(spec.on_glass);
142 }
143}
144
145#[composable]
147#[allow(non_snake_case)]
148pub fn SearchBar(modifier: Modifier, state: TextFieldState, placeholder: impl Into<String>) {
149 LiquidSearchField(
150 modifier,
151 state,
152 LiquidSearchFieldSpec {
153 placeholder: placeholder.into(),
154 ..Default::default()
155 },
156 );
157}
158
159#[composable]
161#[allow(non_snake_case)]
162pub fn SearchField(modifier: Modifier, state: TextFieldState, placeholder: impl Into<String>) {
163 SearchBar(modifier, state, placeholder);
164}