add el-style, el-layout, el-i18n, el-config, el-secrets: responsive by default, theme-driven, zero breakpoints

This commit is contained in:
Will Anderson
2026-04-27 20:18:47 -05:00
parent a1159eec65
commit 361c958618
43 changed files with 6384 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
/// Elevation shadow scale.
///
/// Shadows communicate visual hierarchy and z-depth. Use the named scale —
/// it maps to appropriate platform-native elevation on each backend.
/// Named shadow/elevation tokens.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Shadow {
/// No shadow — flat, on-surface elements.
None,
/// Subtle drop shadow — slightly elevated cards.
Sm,
/// Standard card shadow — interactive elements.
Md,
/// Prominent shadow — dialogs, dropdowns, popovers.
Lg,
/// Maximum elevation — toasts, context menus, tooltips.
Xl,
}
/// A fully-resolved shadow specification.
#[derive(Debug, Clone)]
pub struct ShadowSpec {
/// X offset in dp.
pub offset_x: f32,
/// Y offset in dp (positive = down).
pub offset_y: f32,
/// Blur radius in dp.
pub blur: f32,
/// Spread radius in dp.
pub spread: f32,
/// Shadow color (RGBA).
pub color: (u8, u8, u8, f32),
}
impl ShadowSpec {
/// CSS box-shadow string for this spec.
pub fn to_css(&self) -> String {
format!(
"{}px {}px {}px {}px rgba({},{},{},{})",
self.offset_x,
self.offset_y,
self.blur,
self.spread,
self.color.0,
self.color.1,
self.color.2,
self.color.3,
)
}
}
/// Maps Shadow tokens to ShadowSpecs.
#[derive(Debug, Clone)]
pub struct ShadowScale {
pub sm: ShadowSpec,
pub md: ShadowSpec,
pub lg: ShadowSpec,
pub xl: ShadowSpec,
}
impl ShadowScale {
/// Default light-mode shadow scale.
pub fn light() -> Self {
Self {
sm: ShadowSpec {
offset_x: 0.0,
offset_y: 1.0,
blur: 3.0,
spread: 0.0,
color: (0, 0, 0, 0.12),
},
md: ShadowSpec {
offset_x: 0.0,
offset_y: 4.0,
blur: 12.0,
spread: -2.0,
color: (0, 0, 0, 0.15),
},
lg: ShadowSpec {
offset_x: 0.0,
offset_y: 8.0,
blur: 24.0,
spread: -4.0,
color: (0, 0, 0, 0.18),
},
xl: ShadowSpec {
offset_x: 0.0,
offset_y: 16.0,
blur: 48.0,
spread: -8.0,
color: (0, 0, 0, 0.22),
},
}
}
/// Default dark-mode shadow scale (more subtle, less visible on dark bg).
pub fn dark() -> Self {
Self {
sm: ShadowSpec {
offset_x: 0.0,
offset_y: 1.0,
blur: 3.0,
spread: 0.0,
color: (0, 0, 0, 0.3),
},
md: ShadowSpec {
offset_x: 0.0,
offset_y: 4.0,
blur: 12.0,
spread: -2.0,
color: (0, 0, 0, 0.4),
},
lg: ShadowSpec {
offset_x: 0.0,
offset_y: 8.0,
blur: 24.0,
spread: -4.0,
color: (0, 0, 0, 0.5),
},
xl: ShadowSpec {
offset_x: 0.0,
offset_y: 16.0,
blur: 48.0,
spread: -8.0,
color: (0, 0, 0, 0.6),
},
}
}
/// Resolve a Shadow token to a ShadowSpec.
/// Returns None for Shadow::None (no spec needed).
pub fn resolve(&self, shadow: &Shadow) -> Option<&ShadowSpec> {
match shadow {
Shadow::None => None,
Shadow::Sm => Some(&self.sm),
Shadow::Md => Some(&self.md),
Shadow::Lg => Some(&self.lg),
Shadow::Xl => Some(&self.xl),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shadow_none_resolves_to_none() {
let scale = ShadowScale::light();
assert!(scale.resolve(&Shadow::None).is_none());
}
#[test]
fn shadow_sm_resolves() {
let scale = ShadowScale::light();
let spec = scale.resolve(&Shadow::Sm).unwrap();
assert!(spec.blur > 0.0);
}
#[test]
fn shadow_xl_has_larger_blur_than_sm() {
let scale = ShadowScale::light();
let sm = scale.resolve(&Shadow::Sm).unwrap();
let xl = scale.resolve(&Shadow::Xl).unwrap();
assert!(xl.blur > sm.blur);
}
#[test]
fn shadow_spec_to_css() {
let spec = ShadowSpec {
offset_x: 0.0,
offset_y: 4.0,
blur: 12.0,
spread: -2.0,
color: (0, 0, 0, 0.15),
};
let css = spec.to_css();
assert!(css.contains("rgba(0,0,0,0.15)"));
}
#[test]
fn dark_shadows_are_more_opaque() {
let light = ShadowScale::light();
let dark = ShadowScale::dark();
let l_alpha = light.resolve(&Shadow::Md).unwrap().color.3;
let d_alpha = dark.resolve(&Shadow::Md).unwrap().color.3;
assert!(d_alpha > l_alpha);
}
}