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