//! Profile card example — demonstrates el-ui styling, layout, i18n, config, and secrets. //! //! This example shows what building with el-ui looks like: //! //! - Styling via semantic tokens and the StyleModifier trait //! - Responsive layout: VStack/HStack that wrap automatically //! - Localization via LocaleContext and t()/t_plural() //! - Configuration from el.toml / env vars //! - Secrets that never appear in logs use std::collections::HashMap; use el_config::prelude::*; use el_i18n::prelude::*; use el_layout::prelude::*; use el_secrets::prelude::*; use el_style::prelude::*; // --- Domain model --- struct UserProfile { handle: String, display_name: String, bio: String, follower_count: i64, following_count: i64, is_verified: bool, is_following: bool, } // --- Profile card component --- /// A profile card component. /// /// In a real el-ui app, this would be a .el component file compiled by /// el-ui-compiler. Here we show the same patterns in pure Rust so the /// example is self-contained and runnable. struct ProfileCard { profile: UserProfile, theme: Theme, style: StyleSet, locale: LocaleContext, } impl StyleModifier for ProfileCard { fn style_mut(&mut self) -> &mut StyleSet { &mut self.style } } impl ProfileCard { fn new(profile: UserProfile, theme: Theme, locale: LocaleContext) -> Self { Self { profile, theme, style: StyleSet::default(), locale, } } /// "Render" the card — in a real app the backend converts this to /// native views. Here we produce a human-readable description. fn render(&self) -> String { let t = &self.locale; let theme = &self.theme; // --- Header HStack (avatar + name + verified badge) --- // HStack wraps automatically if the container is narrow (mobile-first) let header = HStack::new() .spacing(12) .alignment(VAlign::Center) .wrap(true); // --- Name text: Title style --- let name_style = theme.typography.resolve(&TextStyle::Title); // --- Body text: Body style --- let body_style = theme.typography.resolve(&TextStyle::Body); // --- Stats HStack (followers / following) --- let _stats_layout = HStack::new().spacing(24).wrap(true); // --- Follow button --- // VStack wraps children that don't fit, so this works on any screen width let _card_layout = VStack::new() .spacing(16) .alignment(HAlign::Leading) .wrap(true); // --- Localized strings --- let follow_label = if self.profile.is_following { t.t("profile.following") } else { t.t("profile.follow") }; let followers_label = t.t_plural("profile.followers", self.profile.follower_count); let following_label = t.t_plural("profile.following_count", self.profile.following_count); // --- Color resolution --- let (bg_r, bg_g, bg_b, _) = theme.colors.resolve(&Color::Surface); let (text_r, text_g, text_b, _) = theme.colors.resolve(&Color::OnSurface); let (primary_r, primary_g, primary_b, _) = theme.colors.resolve(&Color::Primary); // --- Shadow --- let shadow_css = theme .shadows .resolve(&Shadow::Md) .map(|s| s.to_css()) .unwrap_or_default(); // --- Formatted stats (locale-aware numbers) --- let follower_count_fmt = format_integer(self.profile.follower_count, &self.locale.locale); let following_count_fmt = format_integer(self.profile.following_count, &self.locale.locale); // --- RTL layout signal --- let layout_direction = if self.locale.is_rtl() { "rtl" } else { "ltr" }; // --- Build the output --- format!( r#" ┌─ ProfileCard ───────────────────────────────────────────── │ Layout direction: {} │ Theme mode: {:?} │ │ [Card background: rgb({},{},{}), shadow: {}] │ │ {} {} [Header HStack, spacing=12, wrap=true] │ Avatar [44×44pt, radius=Full — meets touch target] │ {} [font: {}pt weight={}, color: rgb({},{},{})] │ {} [verified badge] │ │ {} [Body style, {}pt, color: rgb({},{},{})] │ │ [Stats HStack, spacing=24, wrap=true] │ {} {} — follower count (locale-formatted) │ {} {} — following count │ │ [Button: Primary variant] │ {} [color: rgb({},{},{})] │ │ [Card ends] └──────────────────────────────────────────────────────────── "#, layout_direction, theme.mode, bg_r, bg_g, bg_b, shadow_css, header.spacing, if header.wrap { "wrap=true" } else { "wrap=false" }, self.profile.display_name, name_style.size, name_style.weight.value(), text_r, text_g, text_b, if self.profile.is_verified { "✓" } else { "" }, self.profile.bio, body_style.size, text_r, text_g, text_b, follower_count_fmt, followers_label, following_count_fmt, following_label, follow_label, primary_r, primary_g, primary_b, ) } } // --- Application entry point --- fn main() { // 1. Configuration — layered, typed let el_toml = r#" [config] app.name = "ProfileCard Example" app.version = "1.0.0" profile.max_bio_length = "160" [env.development] app.debug = "true" "#; let toml_source = load_from_toml(el_toml, &Environment::Development) .expect("el.toml should be valid"); let mut config = Config::new(Environment::Development); config.push_source(Box::new(toml_source)); let app_name = config.get::("app.name").unwrap_or_default(); let app_version = config.get::("app.version").unwrap_or_default(); let max_bio: u32 = config.get_or("profile.max_bio_length", 160u32); let debug: bool = config.get_or("app.debug", false); println!("=== {} v{} ===", app_name, app_version); println!("Environment: {}", config.environment); println!("Debug mode: {}", debug); println!("Max bio: {} chars", max_bio); // 2. Secrets — loaded at startup, never logged let mut secret_src = InMemorySource::new(); secret_src.insert("analytics.key", "ana_abc123xyz"); let secrets = SecretsResolver::new() .source(Box::new(secret_src)) .require("analytics.key") .resolve() .expect("required secrets must be present at startup"); let analytics_key = secrets.require("analytics.key"); // This will always print [REDACTED] — never the actual key println!("Analytics key: {} (safely [REDACTED] in logs)", analytics_key); // To actually use it: let _actual_key: &str = analytics_key.expose(); // 3. Localization — English let mut en_bundle = LocaleBundle::new(Locale::en_us()); en_bundle.insert("profile.follow", "Follow"); en_bundle.insert("profile.following", "Following"); let mut forms = HashMap::new(); forms.insert("one".to_string(), "{n} Follower".to_string()); forms.insert("other".to_string(), "{n} Followers".to_string()); en_bundle.insert_plural("profile.followers", forms); let mut following_forms = HashMap::new(); following_forms.insert("one".to_string(), "{n} Following".to_string()); following_forms.insert("other".to_string(), "{n} Following".to_string()); en_bundle.insert_plural("profile.following_count", following_forms); let en_ctx = LocaleContext::new(Locale::en_us(), en_bundle); // 4. Theme — light, system colors let light_theme = Theme::default_light(); let dark_theme = Theme::default_dark(); // 5. Profile data let profile = UserProfile { handle: "alice".to_string(), display_name: "Alice Chen".to_string(), bio: "Building beautiful things with el-ui. Rust enthusiast.".to_string(), follower_count: 12_483, following_count: 342, is_verified: true, is_following: false, }; // 6. Render the card (light theme, English) println!("\n=== Light Theme, English ==="); let card = ProfileCard::new( UserProfile { handle: profile.handle.clone(), display_name: profile.display_name.clone(), bio: profile.bio.clone(), follower_count: profile.follower_count, following_count: profile.following_count, is_verified: profile.is_verified, is_following: profile.is_following, }, light_theme, en_ctx.clone(), ); // Apply style modifiers — fluent, zero-cost at compile time let styled_card = card .padding(16) .background(Color::Surface) .radius(Radius::Lg) .shadow(Shadow::Md) .max_width(Dimension::Fixed(480)); println!("{}", styled_card.render()); // 7. Render with dark theme println!("=== Dark Theme, English ==="); let card_dark = ProfileCard::new( UserProfile { handle: profile.handle.clone(), display_name: profile.display_name.clone(), bio: profile.bio.clone(), follower_count: 1, // test singular following_count: profile.following_count, is_verified: profile.is_verified, is_following: true, }, dark_theme, en_ctx, ); let styled_dark = card_dark .padding(16) .background(Color::Surface) .radius(Radius::Lg) .shadow(Shadow::Lg); println!("{}", styled_dark.render()); // 8. Layout demonstration println!("=== Layout Engine Demo ==="); // Grid: auto columns — picks 1, 2, 3... based on container width let grid = GridLayout::new().columns_auto(200.0).gap(16); for width in [300.0f32, 600.0, 900.0, 1200.0] { println!( " Container {}px → {} columns", width, grid.active_columns(width) ); } // Responsive value let cols: Responsive = Responsive::fixed(1).md(2).lg(3); println!("\n Responsive columns:"); for bp in [ Breakpoint::Base, Breakpoint::Sm, Breakpoint::Md, Breakpoint::Lg, Breakpoint::Xl, ] { println!(" {:?}: {} col(s)", bp, cols.resolve(bp)); } // Platform sizing let ios_sizing = PlatformSizing::for_platform(PlatformFamily::Ios); let android_sizing = PlatformSizing::for_platform(PlatformFamily::Android); println!("\n Min touch targets:"); println!(" iOS: {}pt", ios_sizing.min_touch_target); println!(" Android: {}dp", android_sizing.min_touch_target); // 9. Locale formatting println!("\n=== Locale-Aware Formatting ==="); let number = 1_234_567.89; for (tag, locale) in [ ("en-US", Locale::en_us()), ("de-DE", Locale::new("de-DE")), ("fr-FR", Locale::fr_fr()), ("ja-JP", Locale::ja()), ] { let formatted = format_number(number, &locale, 2); let currency = format_currency(1234.56, &locale, "USD"); println!(" {}: {} | {}", tag, formatted, currency); } // 10. RTL detection println!("\n=== RTL Detection ==="); for tag in ["en-US", "ar-SA", "he", "fa", "zh-TW"] { let locale = Locale::new(tag); println!(" {}: {}", tag, if locale.is_rtl() { "RTL" } else { "LTR" }); } println!("\nAll systems operational. el-ui is ready."); }