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