Skip to main content

repose_material/material3/
chips.rs

1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4use std::sync::atomic::Ordering;
5
6use repose_core::*;
7use repose_ui::{Box, FlowRow, Row, ViewExt, anim::animate_color};
8
9use super::util::FILTERCHIP_COUNTER;
10use super::*;
11
12/// Color slots for chips (both non-selectable and selectable).
13#[derive(Clone, Copy, Debug)]
14pub struct ChipColors {
15    pub container_color: Color,
16    pub label_color: Color,
17    pub leading_icon_color: Color,
18    pub trailing_icon_color: Color,
19    pub disabled_container_color: Color,
20    pub disabled_label_color: Color,
21    pub disabled_leading_icon_color: Color,
22    pub disabled_trailing_icon_color: Color,
23    pub selected_container_color: Color,
24    pub selected_label_color: Color,
25    pub selected_leading_icon_color: Color,
26    pub selected_trailing_icon_color: Color,
27    pub disabled_selected_container_color: Color,
28}
29
30impl ChipColors {
31    pub fn container(&self, enabled: bool, selected: bool) -> Color {
32        match (enabled, selected) {
33            (true, true) => self.selected_container_color,
34            (true, false) => self.container_color,
35            (false, true) => self.disabled_selected_container_color,
36            (false, false) => self.disabled_container_color,
37        }
38    }
39    pub fn label(&self, enabled: bool, selected: bool) -> Color {
40        if !enabled {
41            self.disabled_label_color
42        } else if selected {
43            self.selected_label_color
44        } else {
45            self.label_color
46        }
47    }
48    pub fn leading_icon(&self, enabled: bool, selected: bool) -> Color {
49        if !enabled {
50            self.disabled_leading_icon_color
51        } else if selected {
52            self.selected_leading_icon_color
53        } else {
54            self.leading_icon_color
55        }
56    }
57    pub fn trailing_icon(&self, enabled: bool, selected: bool) -> Color {
58        if !enabled {
59            self.disabled_trailing_icon_color
60        } else if selected {
61            self.selected_trailing_icon_color
62        } else {
63            self.trailing_icon_color
64        }
65    }
66}
67
68/// Elevation levels for chips.
69#[derive(Clone, Copy, Debug)]
70pub struct ChipElevation {
71    pub default: f32,
72    pub hovered: f32,
73    pub focused: f32,
74    pub pressed: f32,
75    pub dragged: f32,
76    pub disabled: f32,
77}
78
79impl ChipElevation {
80    pub fn to_state_elevation(&self) -> StateElevation {
81        StateElevation {
82            default: self.default,
83            hovered: self.hovered,
84            focused: self.focused,
85            pressed: self.pressed,
86            dragged: self.dragged,
87            disabled: self.disabled,
88        }
89    }
90}
91
92impl Default for ChipElevation {
93    fn default() -> Self {
94        Self {
95            default: ChipDefaults::elevation_default(),
96            hovered: ChipDefaults::elevation_hovered(),
97            focused: ChipDefaults::elevation_focused(),
98            pressed: ChipDefaults::elevation_pressed(),
99            dragged: ChipDefaults::elevation_dragged(),
100            disabled: ChipDefaults::elevation_disabled(),
101        }
102    }
103}
104
105/// Configuration for chips.
106#[derive(Clone, Debug)]
107pub struct ChipConfig {
108    pub modifier: Modifier,
109    pub enabled: bool,
110    pub colors: ChipColors,
111    pub elevation: ChipElevation,
112    pub border_width: f32,
113    pub border_color: Color,
114    pub selected_border_color: Color,
115    pub disabled_border_color: Color,
116    pub disabled_selected_border_color: Color,
117    pub shape_radius: f32,
118    pub horizontal_padding: f32,
119    pub interaction_source: Option<MutableInteractionSource>,
120}
121
122impl Default for ChipConfig {
123    fn default() -> Self {
124        Self {
125            modifier: Modifier::new(),
126            enabled: true,
127            colors: ChipColors {
128                container_color: ChipDefaults::container_color(),
129                label_color: ChipDefaults::label_color(),
130                leading_icon_color: ChipDefaults::leading_icon_color(),
131                trailing_icon_color: ChipDefaults::trailing_icon_color(),
132                disabled_container_color: ChipDefaults::disabled_container_color(),
133                disabled_label_color: ChipDefaults::disabled_label_color(),
134                disabled_leading_icon_color: ChipDefaults::disabled_leading_icon_color(),
135                disabled_trailing_icon_color: ChipDefaults::disabled_trailing_icon_color(),
136                selected_container_color: ChipDefaults::selected_container_color(),
137                selected_label_color: ChipDefaults::selected_label_color(),
138                selected_leading_icon_color: ChipDefaults::selected_leading_icon_color(),
139                selected_trailing_icon_color: ChipDefaults::selected_trailing_icon_color(),
140                disabled_selected_container_color: ChipDefaults::disabled_selected_container_color(
141                ),
142            },
143            elevation: ChipElevation::default(),
144            border_width: ChipDefaults::BORDER_WIDTH,
145            border_color: ChipDefaults::border_color(),
146            selected_border_color: ChipDefaults::selected_border_color(),
147            disabled_border_color: ChipDefaults::disabled_border_color(),
148            disabled_selected_border_color: ChipDefaults::disabled_selected_border_color(),
149            shape_radius: ChipDefaults::SHAPE_RADIUS,
150            horizontal_padding: ChipDefaults::HORIZONTAL_PADDING,
151            interaction_source: None,
152        }
153    }
154}
155
156/// M3 Assist Chip - a chip for triggering actions.
157pub fn AssistChip(
158    on_click: impl Fn() + 'static,
159    label: View,
160    leading_icon: Option<View>,
161    trailing_icon: Option<View>,
162    config: ChipConfig,
163) -> View {
164    let th = theme();
165    let is_enabled = config.enabled;
166    let colors = &config.colors;
167    let bg = colors.container(is_enabled, false);
168    let label_color = colors.label(is_enabled, false);
169    let leading_color = colors.leading_icon(is_enabled, false);
170    let trailing_color = colors.trailing_icon(is_enabled, false);
171    let border = if is_enabled {
172        config.border_color
173    } else {
174        config.disabled_border_color
175    };
176    let shape = config.shape_radius;
177    let ch_source: Rc<MutableInteractionSource> = config
178        .interaction_source
179        .clone()
180        .map(Rc::new)
181        .unwrap_or_else(|| remember(MutableInteractionSource::new));
182
183    let mut m = Modifier::new()
184        .flex_shrink(0.0)
185        .state_colors(StateColors {
186            default: Color::TRANSPARENT,
187            hovered: th.on_surface.with_alpha_f32(0.08),
188            focused: th.on_surface.with_alpha_f32(0.12),
189            pressed: th.on_surface.with_alpha_f32(0.12),
190            dragged: th.on_surface.with_alpha_f32(0.12),
191            disabled: Color::TRANSPARENT,
192        })
193        .padding_values(PaddingValues {
194            left: config.horizontal_padding,
195            right: config.horizontal_padding,
196            top: 8.0,
197            bottom: 8.0,
198        })
199        .background(bg)
200        .clip_rounded(shape)
201        .interaction_source(&ch_source)
202        .then(config.modifier);
203
204    if config.border_width > 0.0 && border != Color::TRANSPARENT {
205        m = m.border(config.border_width, border, shape);
206    }
207
208    if is_enabled {
209        m = m.clickable().on_click(on_click);
210    }
211
212    Box(m).child(
213        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
214            leading_icon
215                .map(|v| {
216                    Box(Modifier::new().padding_values(PaddingValues {
217                        left: 0.0,
218                        right: 8.0,
219                        top: 0.0,
220                        bottom: 0.0,
221                    }))
222                    .child(with_content_color(leading_color, move || v))
223                })
224                .unwrap_or(Box(Modifier::new())),
225            with_content_color(label_color, move || label),
226            trailing_icon
227                .map(|v| {
228                    Box(Modifier::new().padding_values(PaddingValues {
229                        left: 8.0,
230                        right: 0.0,
231                        top: 0.0,
232                        bottom: 0.0,
233                    }))
234                    .child(with_content_color(trailing_color, move || v))
235                })
236                .unwrap_or(Box(Modifier::new())),
237        )),
238    )
239}
240
241/// M3 Elevated Assist Chip - like [`AssistChip`] but with elevated container.
242pub fn ElevatedAssistChip(
243    on_click: impl Fn() + 'static,
244    label: View,
245    leading_icon: Option<View>,
246    trailing_icon: Option<View>,
247    config: ChipConfig,
248) -> View {
249    let th = theme();
250    let is_enabled = config.enabled;
251    let colors = &config.colors;
252    let bg = colors.container(is_enabled, false);
253    let label_color = colors.label(is_enabled, false);
254    let leading_color = colors.leading_icon(is_enabled, false);
255    let trailing_color = colors.trailing_icon(is_enabled, false);
256    let shape = config.shape_radius;
257    let ch_source: Rc<MutableInteractionSource> = config
258        .interaction_source
259        .clone()
260        .map(Rc::new)
261        .unwrap_or_else(|| remember(MutableInteractionSource::new));
262
263    let mut m = Modifier::new()
264        .flex_shrink(0.0)
265        .state_colors(StateColors {
266            default: Color::TRANSPARENT,
267            hovered: th.on_surface.with_alpha_f32(0.08),
268            focused: th.on_surface.with_alpha_f32(0.12),
269            pressed: th.on_surface.with_alpha_f32(0.12),
270            dragged: th.on_surface.with_alpha_f32(0.12),
271            disabled: Color::TRANSPARENT,
272        })
273        .state_elevation(config.elevation.to_state_elevation())
274        .padding_values(PaddingValues {
275            left: config.horizontal_padding,
276            right: config.horizontal_padding,
277            top: 8.0,
278            bottom: 8.0,
279        })
280        .background(bg)
281        .clip_rounded(shape)
282        .interaction_source(&ch_source)
283        .then(config.modifier);
284
285    if is_enabled {
286        m = m.clickable().on_click(on_click);
287    }
288
289    Box(m).child(
290        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
291            leading_icon
292                .map(|v| {
293                    Box(Modifier::new().padding_values(PaddingValues {
294                        left: 0.0,
295                        right: 8.0,
296                        top: 0.0,
297                        bottom: 0.0,
298                    }))
299                    .child(with_content_color(leading_color, move || v))
300                })
301                .unwrap_or(Box(Modifier::new())),
302            with_content_color(label_color, move || label),
303            trailing_icon
304                .map(|v| {
305                    Box(Modifier::new().padding_values(PaddingValues {
306                        left: 8.0,
307                        right: 0.0,
308                        top: 0.0,
309                        bottom: 0.0,
310                    }))
311                    .child(with_content_color(trailing_color, move || v))
312                })
313                .unwrap_or(Box(Modifier::new())),
314        )),
315    )
316}
317
318pub fn FilterChip(
319    selected: bool,
320    on_click: impl Fn() + 'static,
321    label: View,
322    leading_icon: Option<View>,
323    trailing_icon: Option<View>,
324    config: ChipConfig,
325) -> View {
326    let th = theme();
327    let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
328    let spec = th.motion.color;
329    let is_enabled = config.enabled;
330    let colors = &config.colors;
331
332    let bg = animate_color(
333        format!("fc_bg_{}", id),
334        colors.container(is_enabled, selected),
335        spec,
336    );
337    let label_color = animate_color(
338        format!("fc_lc_{}", id),
339        colors.label(is_enabled, selected),
340        spec,
341    );
342    let leading_color = animate_color(
343        format!("fc_lic_{}", id),
344        colors.leading_icon(is_enabled, selected),
345        spec,
346    );
347    let trailing_color = animate_color(
348        format!("fc_tic_{}", id),
349        colors.trailing_icon(is_enabled, selected),
350        spec,
351    );
352    let border = if !is_enabled {
353        if selected {
354            config.disabled_selected_border_color
355        } else {
356            config.disabled_border_color
357        }
358    } else {
359        if selected {
360            config.selected_border_color
361        } else {
362            config.border_color
363        }
364    };
365    let shape = config.shape_radius;
366
367    let ch_source: Rc<MutableInteractionSource> = config
368        .interaction_source
369        .clone()
370        .map(Rc::new)
371        .unwrap_or_else(|| remember(MutableInteractionSource::new));
372
373    let mut m = Modifier::new()
374        .flex_shrink(0.0)
375        .state_colors(StateColors {
376            default: Color::TRANSPARENT,
377            hovered: th.on_surface.with_alpha_f32(0.08),
378            focused: th.on_surface.with_alpha_f32(0.12),
379            pressed: th.on_surface.with_alpha_f32(0.12),
380            dragged: th.on_surface.with_alpha_f32(0.12),
381            disabled: Color::TRANSPARENT,
382        })
383        .padding_values(PaddingValues {
384            left: config.horizontal_padding,
385            right: config.horizontal_padding,
386            top: 8.0,
387            bottom: 8.0,
388        })
389        .background(bg)
390        .clip_rounded(shape)
391        .interaction_source(&ch_source)
392        .then(config.modifier);
393
394    if config.border_width > 0.0 && border != Color::TRANSPARENT {
395        m = m.border(config.border_width, border, shape);
396    }
397    if is_enabled {
398        m = m.clickable().on_click(on_click);
399    }
400
401    Box(m).child(
402        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
403            leading_icon
404                .map(|v| {
405                    Box(Modifier::new().padding_values(PaddingValues {
406                        left: 0.0,
407                        right: 8.0,
408                        top: 0.0,
409                        bottom: 0.0,
410                    }))
411                    .child(with_content_color(leading_color, move || v))
412                })
413                .unwrap_or(Box(Modifier::new())),
414            with_content_color(label_color, move || label),
415            trailing_icon
416                .map(|v| {
417                    Box(Modifier::new().padding_values(PaddingValues {
418                        left: 8.0,
419                        right: 0.0,
420                        top: 0.0,
421                        bottom: 0.0,
422                    }))
423                    .child(with_content_color(trailing_color, move || v))
424                })
425                .unwrap_or(Box(Modifier::new())),
426        )),
427    )
428}
429
430/// M3 Elevated Filter Chip - like [`FilterChip`] but with elevation and filled container.
431pub fn ElevatedFilterChip(
432    selected: bool,
433    on_click: impl Fn() + 'static,
434    label: View,
435    leading_icon: Option<View>,
436    trailing_icon: Option<View>,
437    config: ChipConfig,
438) -> View {
439    let th = theme();
440    let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
441    let spec = th.motion.color;
442    let is_enabled = config.enabled;
443    let colors = &config.colors;
444
445    let bg = animate_color(
446        format!("efc_bg_{}", id),
447        colors.container(is_enabled, selected),
448        spec,
449    );
450    let label_color = animate_color(
451        format!("efc_lc_{}", id),
452        colors.label(is_enabled, selected),
453        spec,
454    );
455    let leading_color = animate_color(
456        format!("efc_lic_{}", id),
457        colors.leading_icon(is_enabled, selected),
458        spec,
459    );
460    let trailing_color = animate_color(
461        format!("efc_tic_{}", id),
462        colors.trailing_icon(is_enabled, selected),
463        spec,
464    );
465    let shape = config.shape_radius;
466
467    let ch_source: Rc<MutableInteractionSource> = config
468        .interaction_source
469        .clone()
470        .map(Rc::new)
471        .unwrap_or_else(|| remember(MutableInteractionSource::new));
472
473    let mut m = Modifier::new()
474        .flex_shrink(0.0)
475        .state_colors(StateColors {
476            default: Color::TRANSPARENT,
477            hovered: th.on_surface.with_alpha_f32(0.08),
478            focused: th.on_surface.with_alpha_f32(0.12),
479            pressed: th.on_surface.with_alpha_f32(0.12),
480            dragged: th.on_surface.with_alpha_f32(0.12),
481            disabled: Color::TRANSPARENT,
482        })
483        .state_elevation(config.elevation.to_state_elevation())
484        .padding_values(PaddingValues {
485            left: config.horizontal_padding,
486            right: config.horizontal_padding,
487            top: 8.0,
488            bottom: 8.0,
489        })
490        .background(bg)
491        .clip_rounded(shape)
492        .interaction_source(&ch_source)
493        .then(config.modifier);
494
495    if is_enabled {
496        m = m.clickable().on_click(on_click);
497    }
498
499    Box(m).child(
500        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
501            leading_icon
502                .map(|v| {
503                    Box(Modifier::new().padding_values(PaddingValues {
504                        left: 0.0,
505                        right: 8.0,
506                        top: 0.0,
507                        bottom: 0.0,
508                    }))
509                    .child(with_content_color(leading_color, move || v))
510                })
511                .unwrap_or(Box(Modifier::new())),
512            with_content_color(label_color, move || label),
513            trailing_icon
514                .map(|v| {
515                    Box(Modifier::new().padding_values(PaddingValues {
516                        left: 8.0,
517                        right: 0.0,
518                        top: 0.0,
519                        bottom: 0.0,
520                    }))
521                    .child(with_content_color(trailing_color, move || v))
522                })
523                .unwrap_or(Box(Modifier::new())),
524        )),
525    )
526}
527
528pub fn SuggestionChip(
529    on_click: impl Fn() + 'static,
530    label: View,
531    icon: Option<View>,
532    config: ChipConfig,
533) -> View {
534    let th = theme();
535    let is_enabled = config.enabled;
536    let colors = &config.colors;
537    let bg = colors.container(is_enabled, false);
538    let label_color = colors.label(is_enabled, false);
539    let leading_color = colors.leading_icon(is_enabled, false);
540    let border = if is_enabled {
541        config.border_color
542    } else {
543        config.disabled_border_color
544    };
545    let shape = config.shape_radius;
546
547    let ch_source: Rc<MutableInteractionSource> = config
548        .interaction_source
549        .clone()
550        .map(Rc::new)
551        .unwrap_or_else(|| remember(MutableInteractionSource::new));
552
553    let mut m = Modifier::new()
554        .flex_shrink(0.0)
555        .state_colors(StateColors {
556            default: Color::TRANSPARENT,
557            hovered: th.on_surface.with_alpha_f32(0.08),
558            focused: th.on_surface.with_alpha_f32(0.12),
559            pressed: th.on_surface.with_alpha_f32(0.12),
560            dragged: th.on_surface.with_alpha_f32(0.12),
561            disabled: Color::TRANSPARENT,
562        })
563        .padding_values(PaddingValues {
564            left: config.horizontal_padding,
565            right: config.horizontal_padding,
566            top: 8.0,
567            bottom: 8.0,
568        })
569        .background(bg)
570        .clip_rounded(shape)
571        .interaction_source(&ch_source)
572        .then(config.modifier);
573
574    if config.border_width > 0.0 && border != Color::TRANSPARENT {
575        m = m.border(config.border_width, border, shape);
576    }
577    if is_enabled {
578        m = m.clickable().on_click(on_click);
579    }
580
581    Box(m).child(
582        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
583            icon.map(|v| {
584                Box(Modifier::new().padding_values(PaddingValues {
585                    left: 0.0,
586                    right: 8.0,
587                    top: 0.0,
588                    bottom: 0.0,
589                }))
590                .child(with_content_color(leading_color, move || v))
591            })
592            .unwrap_or(Box(Modifier::new())),
593            with_content_color(label_color, move || label),
594        )),
595    )
596}
597
598/// M3 Elevated Suggestion Chip - like [`SuggestionChip`] but with elevation and filled bg.
599pub fn ElevatedSuggestionChip(
600    on_click: impl Fn() + 'static,
601    label: View,
602    icon: Option<View>,
603    config: ChipConfig,
604) -> View {
605    let th = theme();
606    let is_enabled = config.enabled;
607    let colors = &config.colors;
608    let bg = colors.container(is_enabled, false);
609    let label_color = colors.label(is_enabled, false);
610    let leading_color = colors.leading_icon(is_enabled, false);
611    let shape = config.shape_radius;
612
613    let ch_source: Rc<MutableInteractionSource> = config
614        .interaction_source
615        .clone()
616        .map(Rc::new)
617        .unwrap_or_else(|| remember(MutableInteractionSource::new));
618
619    let mut m = Modifier::new()
620        .flex_shrink(0.0)
621        .state_colors(StateColors {
622            default: Color::TRANSPARENT,
623            hovered: th.on_surface.with_alpha_f32(0.08),
624            focused: th.on_surface.with_alpha_f32(0.12),
625            pressed: th.on_surface.with_alpha_f32(0.12),
626            dragged: th.on_surface.with_alpha_f32(0.12),
627            disabled: Color::TRANSPARENT,
628        })
629        .state_elevation(config.elevation.to_state_elevation())
630        .padding_values(PaddingValues {
631            left: config.horizontal_padding,
632            right: config.horizontal_padding,
633            top: 8.0,
634            bottom: 8.0,
635        })
636        .background(bg)
637        .clip_rounded(shape)
638        .interaction_source(&ch_source)
639        .then(config.modifier);
640
641    if is_enabled {
642        m = m.clickable().on_click(on_click);
643    }
644
645    Box(m).child(
646        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
647            icon.map(|v| {
648                Box(Modifier::new().padding_values(PaddingValues {
649                    left: 0.0,
650                    right: 8.0,
651                    top: 0.0,
652                    bottom: 0.0,
653                }))
654                .child(with_content_color(leading_color, move || v))
655            })
656            .unwrap_or(Box(Modifier::new())),
657            with_content_color(label_color, move || label),
658        )),
659    )
660}
661
662pub fn InputChip(
663    selected: bool,
664    on_click: impl Fn() + 'static,
665    label: View,
666    leading_icon: Option<View>,
667    avatar: Option<View>,
668    trailing_icon: Option<View>,
669    config: ChipConfig,
670) -> View {
671    let th = theme();
672    let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
673    let spec = th.motion.color;
674    let is_enabled = config.enabled;
675    let colors = &config.colors;
676
677    let bg = animate_color(
678        format!("ic_bg_{}", id),
679        colors.container(is_enabled, selected),
680        spec,
681    );
682    let label_color = animate_color(
683        format!("ic_lc_{}", id),
684        colors.label(is_enabled, selected),
685        spec,
686    );
687    let leading_color = animate_color(
688        format!("ic_lic_{}", id),
689        colors.leading_icon(is_enabled, selected),
690        spec,
691    );
692    let trailing_color = animate_color(
693        format!("ic_tic_{}", id),
694        colors.trailing_icon(is_enabled, selected),
695        spec,
696    );
697    let border = if !is_enabled {
698        if selected {
699            config.disabled_selected_border_color
700        } else {
701            config.disabled_border_color
702        }
703    } else {
704        if selected {
705            config.selected_border_color
706        } else {
707            config.border_color
708        }
709    };
710    let shape = config.shape_radius;
711
712    let ch_source: Rc<MutableInteractionSource> = config
713        .interaction_source
714        .clone()
715        .map(Rc::new)
716        .unwrap_or_else(|| remember(MutableInteractionSource::new));
717
718    let mut m = Modifier::new()
719        .flex_shrink(0.0)
720        .state_colors(StateColors {
721            default: Color::TRANSPARENT,
722            hovered: th.on_surface.with_alpha_f32(0.08),
723            focused: th.on_surface.with_alpha_f32(0.12),
724            pressed: th.on_surface.with_alpha_f32(0.12),
725            dragged: th.on_surface.with_alpha_f32(0.12),
726            disabled: Color::TRANSPARENT,
727        })
728        .padding_values(PaddingValues {
729            left: config.horizontal_padding,
730            right: config.horizontal_padding,
731            top: 8.0,
732            bottom: 8.0,
733        })
734        .background(bg)
735        .clip_rounded(shape)
736        .interaction_source(&ch_source)
737        .then(config.modifier);
738
739    if config.border_width > 0.0 && border != Color::TRANSPARENT {
740        m = m.border(config.border_width, border, shape);
741    }
742    if is_enabled {
743        m = m.clickable().on_click(on_click);
744    }
745
746    Box(m).child(
747        Row(Modifier::new().align_items(AlignItems::CENTER)).child((
748            avatar
749                .or(leading_icon)
750                .map(|v| {
751                    Box(Modifier::new().padding_values(PaddingValues {
752                        left: 0.0,
753                        right: 8.0,
754                        top: 0.0,
755                        bottom: 0.0,
756                    }))
757                    .child(with_content_color(leading_color, move || v))
758                })
759                .unwrap_or(Box(Modifier::new())),
760            with_content_color(label_color, move || label),
761            trailing_icon
762                .map(|v| {
763                    Box(Modifier::new().padding_values(PaddingValues {
764                        left: 8.0,
765                        right: 0.0,
766                        top: 0.0,
767                        bottom: 0.0,
768                    }))
769                    .child(with_content_color(trailing_color, move || v))
770                })
771                .unwrap_or(Box(Modifier::new())),
772        )),
773    )
774}
775
776/// Shared layout for the M3 chip group composables: a full-width wrapping
777/// `FlowRow` whose chips keep their intrinsic width (via `flex_shrink(0)`)
778/// instead of shrinking under constrained/centered parents.
779pub fn chip_group_flow(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
780    FlowRow(
781        Modifier::new()
782            .fill_max_width()
783            .gap(8.0)
784            .align_items(AlignItems::CENTER)
785            .then(modifier),
786    )
787    .child(children)
788}
789
790/// M3 Filter Chip Group.
791pub fn FilterChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
792    chip_group_flow(modifier, children)
793}
794
795/// M3 Elevated Filter Chip Group.
796pub fn ElevatedFilterChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
797    chip_group_flow(modifier, children)
798}
799
800/// M3 Assist Chip Group.
801pub fn AssistChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
802    chip_group_flow(modifier, children)
803}
804
805/// M3 Elevated Assist Chip Group.
806/// [`ElevatedAssistChip`]s.
807pub fn ElevatedAssistChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
808    chip_group_flow(modifier, children)
809}
810
811/// M3 Suggestion Chip Group.
812pub fn SuggestionChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
813    chip_group_flow(modifier, children)
814}
815
816/// M3 Elevated Suggestion Chip Group.
817pub fn ElevatedSuggestionChipGroup(
818    modifier: Modifier,
819    children: impl repose_ui::IntoChildren,
820) -> View {
821    chip_group_flow(modifier, children)
822}
823
824/// M3 Input Chip Group.
825pub fn InputChipGroup(modifier: Modifier, children: impl repose_ui::IntoChildren) -> View {
826    chip_group_flow(modifier, children)
827}