repose_material/material3/
mod.rs

1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{
7    Box, Column, Row, Spacer, Stack, Surface, Text, TextStyle, ViewExt, anim::animate_f32,
8};
9
10pub fn AlertDialog(
11    visible: bool,
12    on_dismiss: impl Fn() + 'static,
13    title: View,
14    text: View,
15    confirm_button: View,
16    dismiss_button: Option<View>,
17) -> View {
18    if !visible {
19        return Box(Modifier::new());
20    }
21
22    Stack(Modifier::new().fill_max_size()).child((
23        // Scrim
24        Box(Modifier::new()
25            .fill_max_size()
26            .background(Color::from_hex("#000000AA"))
27            .clickable()
28            .on_pointer_down(move |_| on_dismiss())),
29        // Dialog content
30        Surface(
31            Modifier::new()
32                .size(280.0, 200.0)
33                .background(theme().surface)
34                .clip_rounded(28.0)
35                .padding(24.0),
36            Column(Modifier::new()).child((
37                title,
38                Box(Modifier::new().size(1.0, 16.0)),
39                text,
40                Spacer(),
41                Row(Modifier::new()).child((
42                    dismiss_button.unwrap_or(Box(Modifier::new())),
43                    Spacer(),
44                    confirm_button,
45                )),
46            )),
47        ),
48    ))
49}
50
51pub fn BottomSheet(
52    visible: bool,
53    on_dismiss: impl Fn() + 'static,
54    modifier: Modifier,
55    content: View,
56) -> View {
57    let offset = animate_f32(
58        "sheet_offset",
59        if visible { 0.0 } else { 800.0 },
60        AnimationSpec::spring_gentle(),
61    );
62
63    Stack(Modifier::new().fill_max_size()).child((
64        // Scrim
65        if visible {
66            Box(Modifier::new()
67                .fill_max_size()
68                .background(Color::from_hex("#00000055"))
69                .on_pointer_down(move |_| on_dismiss()))
70        } else {
71            Box(Modifier::new())
72        },
73        // Sheet
74        Box(modifier
75            .absolute()
76            .offset(None, Some(offset), Some(0.0), Some(0.0)))
77        .child(content),
78    ))
79}
80
81pub fn NavigationBar(selected_index: usize, items: Vec<NavItem>) -> View {
82    Row(Modifier::new()
83        .fill_max_size()
84        .background(theme().surface)
85        .padding(8.0))
86    .child(
87        items
88            .into_iter()
89            .enumerate()
90            .map(|(i, item)| NavigationBarItem(item, i == selected_index))
91            .collect::<Vec<_>>(),
92    )
93}
94
95pub struct NavItem {
96    pub icon: View,
97    pub label: String,
98    pub on_click: Rc<dyn Fn()>,
99}
100
101fn NavigationBarItem(item: NavItem, selected: bool) -> View {
102    let color = if selected {
103        theme().primary
104    } else {
105        theme().on_surface
106    };
107
108    Column(
109        Modifier::new()
110            .flex_grow(1.0)
111            .clickable()
112            .on_pointer_down(move |_| (item.on_click)()),
113    )
114    .child((
115        item.icon, // Tint with color
116        Text(item.label).color(color),
117    ))
118}
119
120pub fn Card(modifier: Modifier, elevated: bool, content: View) -> View {
121    Surface(
122        modifier
123            .background(theme().surface)
124            .border(1.0, Color::from_hex("#22222222"), 12.0)
125            .clip_rounded(12.0)
126            .padding(16.0),
127        content,
128    )
129}
130
131pub fn OutlinedCard(modifier: Modifier, content: View) -> View {
132    Surface(
133        modifier
134            .border(1.0, Color::from_hex("#444444"), 12.0)
135            .clip_rounded(12.0)
136            .padding(16.0),
137        content,
138    )
139}
140
141pub fn FilterChip(
142    selected: bool,
143    on_click: impl Fn() + 'static,
144    label: View,
145    leading_icon: Option<View>,
146) -> View {
147    let bg = if selected {
148        theme().primary
149    } else {
150        theme().surface
151    };
152    let fg = if selected {
153        theme().on_primary
154    } else {
155        theme().on_surface
156    };
157
158    Surface(
159        Modifier::new()
160            .background(bg)
161            .border(1.0, Color::from_hex("#444444"), 8.0)
162            .clip_rounded(8.0)
163            .padding(12.0)
164            .clickable()
165            .on_pointer_down(move |_| on_click()),
166        Row(Modifier::new()).child((leading_icon.unwrap_or(Box(Modifier::new())), label)),
167    )
168}
169
170pub fn Scaffold(
171    top_bar: Option<View>,
172    bottom_bar: Option<View>,
173    floating_action_button: Option<View>,
174    content: impl Fn(PaddingValues) -> View,
175) -> View {
176    Stack(Modifier::new().fill_max_size()).child((
177        // Main content with padding
178        Box(Modifier::new()
179            .fill_max_size()
180            .padding_values(PaddingValues {
181                top: if top_bar.is_some() { 64.0 } else { 0.0 },
182                bottom: if bottom_bar.is_some() { 80.0 } else { 0.0 },
183                ..Default::default()
184            }))
185        .child(content(PaddingValues::default())),
186        // Top bar
187        if let Some(bar) = top_bar {
188            Box(Modifier::new()
189                .absolute()
190                .offset(Some(0.0), Some(0.0), Some(0.0), None))
191            .child(bar)
192        } else {
193            Box(Modifier::new())
194        },
195        // Bottom bar
196        if let Some(bar) = bottom_bar {
197            Box(Modifier::new()
198                .absolute()
199                .offset(Some(0.0), None, Some(0.0), Some(0.0)))
200            .child(bar)
201        } else {
202            Box(Modifier::new())
203        },
204        // FAB
205        if let Some(fab) = floating_action_button {
206            Box(Modifier::new()
207                .absolute()
208                .offset(None, None, Some(16.0), Some(16.0)))
209            .child(fab)
210        } else {
211            Box(Modifier::new())
212        },
213    ))
214}