adui_dioxus/components/
config_provider.rs

1use crate::theme::{Theme, ThemeProvider, ThemeTokens};
2use dioxus::prelude::*;
3
4/// Global size for components when they do not specify an explicit size.
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
6pub enum ComponentSize {
7    Small,
8    #[default]
9    Middle,
10    Large,
11}
12
13/// Simple locale flag for components that need basic language switching.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub enum Locale {
16    /// Simplified Chinese.
17    #[default]
18    ZhCN,
19    /// English (US).
20    EnUS,
21}
22
23/// Global configuration shared by components.
24///
25/// This is intentionally much smaller than Ant Design's ConfigProvider. We only
26/// keep fields that are immediately useful for the current component set.
27#[derive(Clone, Debug, PartialEq)]
28pub struct ConfigContextValue {
29    pub size: ComponentSize,
30    pub disabled: bool,
31    pub prefix_cls: String,
32    pub locale: Locale,
33}
34
35impl Default for ConfigContextValue {
36    fn default() -> Self {
37        Self {
38            size: ComponentSize::Middle,
39            disabled: false,
40            prefix_cls: "adui".to_string(),
41            locale: Locale::ZhCN,
42        }
43    }
44}
45
46/// Props for `ConfigProvider`.
47#[derive(Props, Clone, PartialEq)]
48pub struct ConfigProviderProps {
49    /// Global default size for components.
50    #[props(optional)]
51    pub size: Option<ComponentSize>,
52    /// Global disabled flag. When `true`, interactive components should treat
53    /// themselves as disabled unless explicitly overridden.
54    #[props(optional)]
55    pub disabled: Option<bool>,
56    /// Global CSS class name prefix. Defaults to `"adui"`.
57    #[props(optional)]
58    pub prefix_cls: Option<String>,
59    /// Global locale flag to control basic UI language for components that
60    /// integrate with date/time or other text-heavy features.
61    #[props(optional)]
62    pub locale: Option<Locale>,
63    /// Optional initial theme. If omitted, the current ThemeProvider behaviour
64    /// is preserved.
65    #[props(optional)]
66    pub theme: Option<Theme>,
67    pub children: Element,
68}
69
70/// Provide ConfigContext for descendant components. ConfigProvider wraps
71/// ThemeProvider so that size / disabled / prefix can live alongside tokens.
72#[component]
73pub fn ConfigProvider(props: ConfigProviderProps) -> Element {
74    let parent = try_use_context::<ConfigContextValue>().unwrap_or_default();
75
76    let value = ConfigContextValue {
77        size: props.size.unwrap_or(parent.size),
78        disabled: props.disabled.unwrap_or(parent.disabled),
79        prefix_cls: props.prefix_cls.clone().unwrap_or(parent.prefix_cls),
80        locale: props.locale.unwrap_or(parent.locale),
81    };
82
83    // We still rely on ThemeProvider for concrete tokens & CSS variables, so
84    // we forward the optional theme prop. If there is already a ThemeProvider
85    // above, it will simply override this one.
86    use_context_provider(|| value.clone());
87
88    rsx! {
89        ThemeProvider { theme: props.theme.clone(), {props.children} }
90    }
91}
92
93/// Hook for components to read global config.
94pub fn use_config() -> ConfigContextValue {
95    try_use_context::<ConfigContextValue>().unwrap_or_default()
96}
97
98/// Lightweight helper to compute control dimensions from global config and
99/// theme tokens. This does not live in the context to keep it testable and
100/// stateless.
101pub fn control_heights(config: &ConfigContextValue, tokens: &ThemeTokens) -> (f32, f32, f32) {
102    let base = tokens.control_height;
103    let small = tokens.control_height_small;
104    let large = tokens.control_height_large;
105    match config.size {
106        ComponentSize::Small => (small, small, base),
107        ComponentSize::Large => (large, large, base),
108        ComponentSize::Middle => (base, small, large),
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::theme::ThemeTokens;
116
117    #[test]
118    fn component_size_default() {
119        assert_eq!(ComponentSize::default(), ComponentSize::Middle);
120    }
121
122    #[test]
123    fn component_size_all_variants() {
124        assert_eq!(ComponentSize::Small, ComponentSize::Small);
125        assert_eq!(ComponentSize::Middle, ComponentSize::Middle);
126        assert_eq!(ComponentSize::Large, ComponentSize::Large);
127        assert_ne!(ComponentSize::Small, ComponentSize::Middle);
128        assert_ne!(ComponentSize::Small, ComponentSize::Large);
129        assert_ne!(ComponentSize::Middle, ComponentSize::Large);
130    }
131
132    #[test]
133    fn component_size_clone() {
134        let original = ComponentSize::Small;
135        let cloned = original;
136        assert_eq!(original, cloned);
137    }
138
139    #[test]
140    fn locale_default() {
141        assert_eq!(Locale::default(), Locale::ZhCN);
142    }
143
144    #[test]
145    fn locale_all_variants() {
146        assert_eq!(Locale::ZhCN, Locale::ZhCN);
147        assert_eq!(Locale::EnUS, Locale::EnUS);
148        assert_ne!(Locale::ZhCN, Locale::EnUS);
149    }
150
151    #[test]
152    fn locale_clone() {
153        let original = Locale::ZhCN;
154        let cloned = original;
155        assert_eq!(original, cloned);
156    }
157
158    #[test]
159    fn config_context_value_default() {
160        let config = ConfigContextValue::default();
161        assert_eq!(config.size, ComponentSize::Middle);
162        assert_eq!(config.disabled, false);
163        assert_eq!(config.prefix_cls, "adui");
164        assert_eq!(config.locale, Locale::ZhCN);
165    }
166
167    #[test]
168    fn config_context_value_clone() {
169        let original = ConfigContextValue {
170            size: ComponentSize::Large,
171            disabled: true,
172            prefix_cls: "custom".to_string(),
173            locale: Locale::EnUS,
174        };
175        let cloned = original.clone();
176        assert_eq!(original, cloned);
177    }
178
179    #[test]
180    fn control_heights_small() {
181        let tokens = ThemeTokens::light();
182        let config = ConfigContextValue {
183            size: ComponentSize::Small,
184            disabled: false,
185            prefix_cls: "adui".to_string(),
186            locale: Locale::ZhCN,
187        };
188        let (small, middle, large) = control_heights(&config, &tokens);
189        assert_eq!(small, tokens.control_height_small);
190        assert_eq!(middle, tokens.control_height_small);
191        assert_eq!(large, tokens.control_height);
192    }
193
194    #[test]
195    fn control_heights_middle() {
196        let tokens = ThemeTokens::light();
197        let config = ConfigContextValue {
198            size: ComponentSize::Middle,
199            disabled: false,
200            prefix_cls: "adui".to_string(),
201            locale: Locale::ZhCN,
202        };
203        let (small, middle, large) = control_heights(&config, &tokens);
204        assert_eq!(small, tokens.control_height);
205        assert_eq!(middle, tokens.control_height_small);
206        assert_eq!(large, tokens.control_height_large);
207    }
208
209    #[test]
210    fn control_heights_large() {
211        let tokens = ThemeTokens::light();
212        let config = ConfigContextValue {
213            size: ComponentSize::Large,
214            disabled: false,
215            prefix_cls: "adui".to_string(),
216            locale: Locale::ZhCN,
217        };
218        let (small, middle, large) = control_heights(&config, &tokens);
219        assert_eq!(small, tokens.control_height_large);
220        assert_eq!(middle, tokens.control_height_large);
221        assert_eq!(large, tokens.control_height);
222    }
223}