This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-ui-retired/vessels/el-style/src/shadow.rs
T
Will Anderson f4abfe6fdc feat: rename crates/ → vessels/ + add El ports per sub-vessel
Belated rename commit for foundation/el-ui — was missed in the
workspace-wide crates→vessels pass earlier today. Same structural
intent as the rename in the other repos: 'crates' is the Rust word,
'vessel' is El's, and the directory rename is the marker that this
slot holds an El buildable unit even if its current contents are
still Rust pending port.

Plus the El ports themselves — manifest.el + src/main.el per sub-
vessel (el-aop, el-auth, el-config, el-i18n, el-identity, el-layout,
el-platform, el-publish, el-secrets, el-services, el-style, el-ui-
compiler). The ui-compiler is a stub: elc only emits C right now;
generating browser-target JS/Wasm is the biggest open language gap
and gets its own project. Until then, el-ui-compiler emits a JS
module that throws elc.backend_missing so callers fail loudly.
Cross-repo path dependencies in Cargo.toml updated to vessels/.
2026-04-30 18:18:39 -05:00

191 lines
5.0 KiB
Rust

/// 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);
}
}