Archived
add el-style, el-layout, el-i18n, el-config, el-secrets: responsive by default, theme-driven, zero breakpoints
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/// Typography scale — named text styles with semantic meaning.
|
||||
///
|
||||
/// Don't hardcode font sizes. Use the named scale. The theme maps each
|
||||
/// style to the right size, weight, and line height for the platform.
|
||||
|
||||
/// Named text style levels.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum TextStyle {
|
||||
/// Largest — hero headings, splash screens.
|
||||
Display,
|
||||
/// Page-level headings.
|
||||
Headline,
|
||||
/// Section headings, dialog titles.
|
||||
Title,
|
||||
/// Default readable body copy.
|
||||
Body,
|
||||
/// UI labels, button text, captions.
|
||||
Label,
|
||||
/// Monospaced — code, terminal output.
|
||||
Code,
|
||||
/// Fine print, footnotes, timestamps.
|
||||
Caption,
|
||||
}
|
||||
|
||||
/// Font weight.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FontWeight {
|
||||
Thin, // 100
|
||||
Light, // 300
|
||||
Regular, // 400
|
||||
Medium, // 500
|
||||
SemiBold, // 600
|
||||
Bold, // 700
|
||||
ExtraBold, // 800
|
||||
Black, // 900
|
||||
}
|
||||
|
||||
impl FontWeight {
|
||||
/// Numeric CSS/platform font weight value.
|
||||
pub fn value(&self) -> u32 {
|
||||
match self {
|
||||
FontWeight::Thin => 100,
|
||||
FontWeight::Light => 300,
|
||||
FontWeight::Regular => 400,
|
||||
FontWeight::Medium => 500,
|
||||
FontWeight::SemiBold => 600,
|
||||
FontWeight::Bold => 700,
|
||||
FontWeight::ExtraBold => 800,
|
||||
FontWeight::Black => 900,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text alignment — logical (RTL-aware), not physical.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TextAlign {
|
||||
/// Aligns to the start of reading direction (left in LTR, right in RTL).
|
||||
Start,
|
||||
/// Center.
|
||||
Center,
|
||||
/// Aligns to the end of reading direction.
|
||||
End,
|
||||
/// Justify.
|
||||
Justify,
|
||||
}
|
||||
|
||||
/// Text decoration.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TextDecoration {
|
||||
None,
|
||||
Underline,
|
||||
LineThrough,
|
||||
Overline,
|
||||
}
|
||||
|
||||
/// Overflow behavior for text that doesn't fit.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TextOverflow {
|
||||
Clip,
|
||||
Ellipsis,
|
||||
Wrap,
|
||||
}
|
||||
|
||||
/// Fully-resolved spec for a single text style level.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextSpec {
|
||||
/// Font size in density-independent pixels.
|
||||
pub size: f32,
|
||||
/// Line height multiplier (1.5 = 150% of font size).
|
||||
pub line_height: f32,
|
||||
/// Letter spacing in em units.
|
||||
pub letter_spacing: f32,
|
||||
pub weight: FontWeight,
|
||||
}
|
||||
|
||||
/// Maps each TextStyle level to a concrete TextSpec.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypographyScheme {
|
||||
pub display: TextSpec,
|
||||
pub headline: TextSpec,
|
||||
pub title: TextSpec,
|
||||
pub body: TextSpec,
|
||||
pub label: TextSpec,
|
||||
pub code: TextSpec,
|
||||
pub caption: TextSpec,
|
||||
}
|
||||
|
||||
impl TypographyScheme {
|
||||
/// Default typography scale (4px base grid, 16px body).
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
display: TextSpec {
|
||||
size: 57.0,
|
||||
line_height: 1.12,
|
||||
letter_spacing: -0.025,
|
||||
weight: FontWeight::Regular,
|
||||
},
|
||||
headline: TextSpec {
|
||||
size: 32.0,
|
||||
line_height: 1.25,
|
||||
letter_spacing: -0.015,
|
||||
weight: FontWeight::SemiBold,
|
||||
},
|
||||
title: TextSpec {
|
||||
size: 22.0,
|
||||
line_height: 1.3,
|
||||
letter_spacing: -0.01,
|
||||
weight: FontWeight::SemiBold,
|
||||
},
|
||||
body: TextSpec {
|
||||
size: 16.0,
|
||||
line_height: 1.5,
|
||||
letter_spacing: 0.0,
|
||||
weight: FontWeight::Regular,
|
||||
},
|
||||
label: TextSpec {
|
||||
size: 14.0,
|
||||
line_height: 1.4,
|
||||
letter_spacing: 0.005,
|
||||
weight: FontWeight::Medium,
|
||||
},
|
||||
code: TextSpec {
|
||||
size: 14.0,
|
||||
line_height: 1.6,
|
||||
letter_spacing: 0.0,
|
||||
weight: FontWeight::Regular,
|
||||
},
|
||||
caption: TextSpec {
|
||||
size: 12.0,
|
||||
line_height: 1.33,
|
||||
letter_spacing: 0.01,
|
||||
weight: FontWeight::Regular,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a TextStyle variant to its TextSpec.
|
||||
pub fn resolve(&self, style: &TextStyle) -> &TextSpec {
|
||||
match style {
|
||||
TextStyle::Display => &self.display,
|
||||
TextStyle::Headline => &self.headline,
|
||||
TextStyle::Title => &self.title,
|
||||
TextStyle::Body => &self.body,
|
||||
TextStyle::Label => &self.label,
|
||||
TextStyle::Code => &self.code,
|
||||
TextStyle::Caption => &self.caption,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn typography_scheme_body_size() {
|
||||
let scheme = TypographyScheme::default();
|
||||
let spec = scheme.resolve(&TextStyle::Body);
|
||||
assert_eq!(spec.size, 16.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typography_scheme_display_largest() {
|
||||
let scheme = TypographyScheme::default();
|
||||
let display = scheme.resolve(&TextStyle::Display);
|
||||
let caption = scheme.resolve(&TextStyle::Caption);
|
||||
assert!(display.size > caption.size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_weight_values() {
|
||||
assert_eq!(FontWeight::Regular.value(), 400);
|
||||
assert_eq!(FontWeight::Bold.value(), 700);
|
||||
assert_eq!(FontWeight::SemiBold.value(), 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typography_minimum_size() {
|
||||
// All text styles must be >= 11pt (accessibility minimum)
|
||||
let scheme = TypographyScheme::default();
|
||||
for style in [
|
||||
TextStyle::Display,
|
||||
TextStyle::Headline,
|
||||
TextStyle::Title,
|
||||
TextStyle::Body,
|
||||
TextStyle::Label,
|
||||
TextStyle::Code,
|
||||
TextStyle::Caption,
|
||||
] {
|
||||
let spec = scheme.resolve(&style);
|
||||
assert!(
|
||||
spec.size >= 11.0,
|
||||
"{:?} size {} is below 11pt minimum",
|
||||
style,
|
||||
spec.size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typography_code_is_readable_size() {
|
||||
let scheme = TypographyScheme::default();
|
||||
let spec = scheme.resolve(&TextStyle::Code);
|
||||
assert!(spec.size >= 12.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typography_line_heights_positive() {
|
||||
let scheme = TypographyScheme::default();
|
||||
for style in [TextStyle::Body, TextStyle::Caption, TextStyle::Title] {
|
||||
let spec = scheme.resolve(&style);
|
||||
assert!(spec.line_height > 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user