Source code for animaid.css_types

"""CSS value types for type-safe styling.

This module provides classes and enums for representing CSS values with
validation, IDE autocomplete support, and type safety.
"""

from __future__ import annotations

import re
from abc import ABC, abstractmethod
from enum import Enum
from typing import ClassVar

# =============================================================================
# Base Protocol
# =============================================================================


[docs] class CSSValue(ABC): """Base class for all CSS value types. All CSS value classes must implement to_css() which returns the CSS string representation. They also support str() conversion for backward compatibility. """
[docs] @abstractmethod def to_css(self) -> str: """Return the CSS string representation of this value.""" ...
def __str__(self) -> str: """Convert to string (same as to_css for compatibility).""" return self.to_css() def __repr__(self) -> str: return f"{self.__class__.__name__}({self.to_css()!r})"
# ============================================================================= # Size Class # =============================================================================
[docs] class Size(CSSValue): """Represents a CSS size/length value with units. Examples: >>> Size.px(10) Size('10px') >>> Size.em(1.5) Size('1.5em') >>> Size.percent(50) Size('50%') >>> Size.auto() Size('auto') """ # Valid CSS length units VALID_UNITS: ClassVar[set[str]] = { "px", "em", "rem", "%", "vh", "vw", "vmin", "vmax", "pt", "pc", "in", "cm", "mm", "ex", "ch", "fr", } # Special keyword values KEYWORDS: ClassVar[set[str]] = { "auto", "inherit", "initial", "unset", "none", "min-content", "max-content", "fit-content", } def __init__(self, value: float | str, unit: str = "") -> None: """Create a Size from a value and optional unit. Args: value: Numeric value or keyword string (e.g., "auto") unit: CSS unit (e.g., "px", "em", "%"). Not needed for keywords. Raises: ValueError: If the value or unit is invalid. """ if isinstance(value, str): # Parse string like "10px" or keyword like "auto" self._value, self._unit = self._parse_string(value) else: if unit and unit not in self.VALID_UNITS: valid = ", ".join(sorted(self.VALID_UNITS)) raise ValueError(f"Invalid unit '{unit}'. Valid units: {valid}") self._value = value self._unit = unit def _parse_string(self, s: str) -> tuple[float | None, str]: """Parse a CSS size string into value and unit.""" s = s.strip().lower() # Check for keywords if s in self.KEYWORDS: return None, s # Try to parse numeric value with unit match = re.match(r"^(-?\d*\.?\d+)\s*([a-z%]+)?$", s) if match: value = float(match.group(1)) unit = match.group(2) or "px" # Default to px if no unit if unit not in self.VALID_UNITS: raise ValueError(f"Invalid unit '{unit}' in '{s}'") return value, unit raise ValueError(f"Cannot parse size value: '{s}'")
[docs] def to_css(self) -> str: """Return the CSS string representation.""" if self._value is None: return self._unit # Keyword like "auto" if self._unit: # Format without trailing zeros if self._value == int(self._value): return f"{int(self._value)}{self._unit}" return f"{self._value}{self._unit}" return str(int(self._value) if self._value == int(self._value) else self._value)
@property def value(self) -> float | None: """The numeric value, or None for keywords.""" return self._value @property def unit(self) -> str: """The unit string.""" return self._unit @property def is_keyword(self) -> bool: """True if this is a keyword value like 'auto'.""" return self._value is None # Factory methods for common units
[docs] @classmethod def px(cls, value: float) -> Size: """Create a pixel size.""" return cls(value, "px")
[docs] @classmethod def em(cls, value: float) -> Size: """Create an em size.""" return cls(value, "em")
[docs] @classmethod def rem(cls, value: float) -> Size: """Create a rem size.""" return cls(value, "rem")
[docs] @classmethod def percent(cls, value: float) -> Size: """Create a percentage size.""" return cls(value, "%")
[docs] @classmethod def vh(cls, value: float) -> Size: """Create a viewport height size.""" return cls(value, "vh")
[docs] @classmethod def vw(cls, value: float) -> Size: """Create a viewport width size.""" return cls(value, "vw")
[docs] @classmethod def fr(cls, value: float) -> Size: """Create a fractional unit size (for CSS grid).""" return cls(value, "fr")
[docs] @classmethod def auto(cls) -> Size: """Create an 'auto' size.""" return cls("auto")
[docs] @classmethod def none(cls) -> Size: """Create a 'none' size.""" return cls("none")
[docs] @classmethod def inherit(cls) -> Size: """Create an 'inherit' size.""" return cls("inherit")
# ------------------------------------------------------------------------- # Named Size Presets (beginner-friendly) # -------------------------------------------------------------------------
[docs] @classmethod def zero(cls) -> Size: """Create a zero size (0px).""" return cls(0, "px")
[docs] @classmethod def xs(cls) -> Size: """Create an extra-small size (4px).""" return cls(4, "px")
[docs] @classmethod def sm(cls) -> Size: """Create a small size (8px).""" return cls(8, "px")
[docs] @classmethod def md(cls) -> Size: """Create a medium size (16px).""" return cls(16, "px")
[docs] @classmethod def lg(cls) -> Size: """Create a large size (24px).""" return cls(24, "px")
[docs] @classmethod def xl(cls) -> Size: """Create an extra-large size (32px).""" return cls(32, "px")
[docs] @classmethod def xxl(cls) -> Size: """Create a 2x extra-large size (48px).""" return cls(48, "px")
[docs] @classmethod def full(cls) -> Size: """Create a full width/height (100%).""" return cls(100, "%")
[docs] @classmethod def half(cls) -> Size: """Create a half width/height (50%).""" return cls(50, "%")
[docs] @classmethod def third(cls) -> Size: """Create a third width/height (33.333%).""" return cls(33.333, "%")
[docs] @classmethod def quarter(cls) -> Size: """Create a quarter width/height (25%).""" return cls(25, "%")
def __eq__(self, other: object) -> bool: if isinstance(other, Size): return self._value == other._value and self._unit == other._unit if isinstance(other, str): return self.to_css() == other return NotImplemented def __hash__(self) -> int: return hash((self._value, self._unit))
# ============================================================================= # Color Class # =============================================================================
[docs] class Color(CSSValue): """Represents a CSS color value. Supports named colors, hex codes, rgb(), rgba(), hsl(), and hsla(). Examples: >>> Color.red Color('red') >>> Color.hex("#2563eb") Color('#2563eb') >>> Color.rgb(255, 128, 0) Color('rgb(255, 128, 0)') >>> Color.rgba(255, 128, 0, 0.5) Color('rgba(255, 128, 0, 0.5)') """ # Named CSS colors (subset of most common) NAMED_COLORS: ClassVar[set[str]] = { "black", "white", "red", "green", "blue", "yellow", "cyan", "magenta", "gray", "grey", "orange", "pink", "purple", "brown", "navy", "teal", "olive", "maroon", "aqua", "fuchsia", "lime", "silver", "transparent", "currentcolor", "inherit", "initial", "unset", } # Class-level color instances (set after class definition) transparent: ClassVar[Color] black: ClassVar[Color] white: ClassVar[Color] red: ClassVar[Color] green: ClassVar[Color] blue: ClassVar[Color] yellow: ClassVar[Color] cyan: ClassVar[Color] magenta: ClassVar[Color] gray: ClassVar[Color] grey: ClassVar[Color] orange: ClassVar[Color] pink: ClassVar[Color] purple: ClassVar[Color] brown: ClassVar[Color] navy: ClassVar[Color] teal: ClassVar[Color] olive: ClassVar[Color] maroon: ClassVar[Color] aqua: ClassVar[Color] lime: ClassVar[Color] silver: ClassVar[Color] # Semantic colors success: ClassVar[Color] warning: ClassVar[Color] error: ClassVar[Color] info: ClassVar[Color] muted: ClassVar[Color] light_gray: ClassVar[Color] dark_gray: ClassVar[Color] def __init__(self, value: str) -> None: """Create a Color from a CSS color string. Args: value: A valid CSS color (name, hex, rgb, rgba, hsl, hsla) Raises: ValueError: If the color format is invalid. """ self._value = self._validate(value.strip()) def _validate(self, value: str) -> str: """Validate and normalize a color value.""" lower = value.lower() # Named colors if lower in self.NAMED_COLORS: return lower # Hex colors if value.startswith("#"): hex_part = value[1:] valid_hex = all(c in "0123456789abcdefABCDEF" for c in hex_part) if len(hex_part) in (3, 4, 6, 8) and valid_hex: return value raise ValueError(f"Invalid hex color: '{value}'") # rgb/rgba/hsl/hsla functions func_match = re.match(r"^(rgba?|hsla?)\s*\((.+)\)$", lower) if func_match: return value # Accept as-is, browser will validate raise ValueError( f"Invalid color: '{value}'. Use named color, hex (#rgb or #rrggbb), " "or function (rgb, rgba, hsl, hsla)" )
[docs] def to_css(self) -> str: """Return the CSS string representation.""" return self._value
# Factory methods
[docs] @classmethod def hex(cls, code: str) -> Color: """Create a color from a hex code. Args: code: Hex code with or without '#' prefix """ if not code.startswith("#"): code = f"#{code}" return cls(code)
[docs] @classmethod def rgb(cls, r: int, g: int, b: int) -> Color: """Create an RGB color. Args: r: Red component (0-255) g: Green component (0-255) b: Blue component (0-255) """ for name, val in [("r", r), ("g", g), ("b", b)]: if not 0 <= val <= 255: raise ValueError(f"{name} must be 0-255, got {val}") return cls(f"rgb({r}, {g}, {b})")
[docs] @classmethod def rgba(cls, r: int, g: int, b: int, a: float) -> Color: """Create an RGBA color with alpha. Args: r: Red component (0-255) g: Green component (0-255) b: Blue component (0-255) a: Alpha component (0.0-1.0) """ for name, val in [("r", r), ("g", g), ("b", b)]: if not 0 <= val <= 255: raise ValueError(f"{name} must be 0-255, got {val}") if not 0.0 <= a <= 1.0: raise ValueError(f"alpha must be 0.0-1.0, got {a}") return cls(f"rgba({r}, {g}, {b}, {a})")
[docs] @classmethod def hsl(cls, h: int, s: int, lightness: int) -> Color: """Create an HSL color. Args: h: Hue (0-360) s: Saturation (0-100) lightness: Lightness (0-100) """ if not 0 <= h <= 360: raise ValueError(f"hue must be 0-360, got {h}") if not 0 <= s <= 100: raise ValueError(f"saturation must be 0-100, got {s}") if not 0 <= lightness <= 100: raise ValueError(f"lightness must be 0-100, got {lightness}") return cls(f"hsl({h}, {s}%, {lightness}%)")
[docs] @classmethod def hsla(cls, h: int, s: int, lightness: int, a: float) -> Color: """Create an HSLA color with alpha. Args: h: Hue (0-360) s: Saturation (0-100) lightness: Lightness (0-100) a: Alpha (0.0-1.0) """ if not 0 <= h <= 360: raise ValueError(f"hue must be 0-360, got {h}") if not 0 <= s <= 100: raise ValueError(f"saturation must be 0-100, got {s}") if not 0 <= lightness <= 100: raise ValueError(f"lightness must be 0-100, got {lightness}") if not 0.0 <= a <= 1.0: raise ValueError(f"alpha must be 0.0-1.0, got {a}") return cls(f"hsla({h}, {s}%, {lightness}%, {a})")
def __eq__(self, other: object) -> bool: if isinstance(other, Color): return self._value.lower() == other._value.lower() if isinstance(other, str): return self._value.lower() == other.lower() return NotImplemented def __hash__(self) -> int: return hash(self._value.lower())
# Named color class attributes (for Color.red, Color.blue, etc.) # These are defined after the class to avoid forward reference issues Color.transparent = Color("transparent") Color.black = Color("black") Color.white = Color("white") Color.red = Color("red") Color.green = Color("green") Color.blue = Color("blue") Color.yellow = Color("yellow") Color.cyan = Color("cyan") Color.magenta = Color("magenta") Color.gray = Color("gray") Color.grey = Color("grey") Color.orange = Color("orange") Color.pink = Color("pink") Color.purple = Color("purple") Color.brown = Color("brown") Color.navy = Color("navy") Color.teal = Color("teal") Color.olive = Color("olive") Color.maroon = Color("maroon") Color.aqua = Color("aqua") Color.lime = Color("lime") Color.silver = Color("silver") # Semantic colors for UI (beginner-friendly) Color.success = Color("#22c55e") Color.warning = Color("#f59e0b") Color.error = Color("#ef4444") Color.info = Color("#3b82f6") Color.muted = Color("#6b7280") Color.light_gray = Color("#f5f5f5") Color.dark_gray = Color("#374151") # ============================================================================= # Text Enums # =============================================================================
[docs] class FontWeight(Enum): """CSS font-weight values.""" NORMAL = "normal" BOLD = "bold" LIGHTER = "lighter" BOLDER = "bolder" W100 = "100" W200 = "200" W300 = "300" W400 = "400" W500 = "500" W600 = "600" W700 = "700" W800 = "800" W900 = "900"
[docs] def to_css(self) -> str: """Return the CSS value.""" return self.value
def __str__(self) -> str: return self.value
[docs] class FontStyle(Enum): """CSS font-style values.""" NORMAL = "normal" ITALIC = "italic" OBLIQUE = "oblique"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class TextTransform(Enum): """CSS text-transform values.""" NONE = "none" UPPERCASE = "uppercase" LOWERCASE = "lowercase" CAPITALIZE = "capitalize"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class TextDecoration(Enum): """CSS text-decoration values.""" NONE = "none" UNDERLINE = "underline" OVERLINE = "overline" LINE_THROUGH = "line-through"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class TextAlign(Enum): """CSS text-align values.""" LEFT = "left" RIGHT = "right" CENTER = "center" JUSTIFY = "justify" START = "start" END = "end"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
# ============================================================================= # Layout Enums # =============================================================================
[docs] class Display(Enum): """CSS display values.""" BLOCK = "block" INLINE = "inline" INLINE_BLOCK = "inline-block" FLEX = "flex" INLINE_FLEX = "inline-flex" GRID = "grid" INLINE_GRID = "inline-grid" NONE = "none" CONTENTS = "contents"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class FlexDirection(Enum): """CSS flex-direction values.""" ROW = "row" ROW_REVERSE = "row-reverse" COLUMN = "column" COLUMN_REVERSE = "column-reverse"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class FlexWrap(Enum): """CSS flex-wrap values.""" NOWRAP = "nowrap" WRAP = "wrap" WRAP_REVERSE = "wrap-reverse"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class AlignItems(Enum): """CSS align-items values.""" START = "start" END = "end" CENTER = "center" STRETCH = "stretch" BASELINE = "baseline" FLEX_START = "flex-start" FLEX_END = "flex-end"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class JustifyContent(Enum): """CSS justify-content values.""" START = "start" END = "end" CENTER = "center" STRETCH = "stretch" SPACE_BETWEEN = "space-between" SPACE_AROUND = "space-around" SPACE_EVENLY = "space-evenly" FLEX_START = "flex-start" FLEX_END = "flex-end"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class Position(Enum): """CSS position values.""" STATIC = "static" RELATIVE = "relative" ABSOLUTE = "absolute" FIXED = "fixed" STICKY = "sticky"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class Overflow(Enum): """CSS overflow values.""" VISIBLE = "visible" HIDDEN = "hidden" SCROLL = "scroll" AUTO = "auto" CLIP = "clip"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class JustifyItems(Enum): """CSS justify-items values for grid containers.""" START = "start" END = "end" CENTER = "center" STRETCH = "stretch" BASELINE = "baseline"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class AlignContent(Enum): """CSS align-content values for multi-line flex/grid containers.""" START = "start" END = "end" CENTER = "center" STRETCH = "stretch" SPACE_BETWEEN = "space-between" SPACE_AROUND = "space-around" SPACE_EVENLY = "space-evenly"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class GridAutoFlow(Enum): """CSS grid-auto-flow values.""" ROW = "row" COLUMN = "column" ROW_DENSE = "row dense" COLUMN_DENSE = "column dense"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class PlaceItems(Enum): """CSS place-items shorthand (align + justify).""" START = "start" END = "end" CENTER = "center" STRETCH = "stretch"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class ShadowSize(Enum): """Predefined box-shadow sizes for cards/containers.""" NONE = "none" SM = "0 1px 2px 0 rgba(0, 0, 0, 0.05)" DEFAULT = "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)" MD = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)" LG = "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)" XL = "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class RadiusSize(Enum): """Predefined border-radius sizes.""" NONE = "0" SM = "2px" DEFAULT = "4px" MD = "6px" LG = "8px" XL = "12px" XXL = "16px" FULL = "9999px" # Fully rounded (pill shape)
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class DividerStyle(Enum): """Divider line styles (maps to border-style).""" SOLID = "solid" DASHED = "dashed" DOTTED = "dotted" DOUBLE = "double"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
# ============================================================================= # Border Enum and Class # =============================================================================
[docs] class BorderStyle(Enum): """CSS border-style values.""" NONE = "none" SOLID = "solid" DASHED = "dashed" DOTTED = "dotted" DOUBLE = "double" GROOVE = "groove" RIDGE = "ridge" INSET = "inset" OUTSET = "outset" HIDDEN = "hidden"
[docs] def to_css(self) -> str: return self.value
def __str__(self) -> str: return self.value
[docs] class Border(CSSValue): """Represents a CSS border value (width + style + color). Examples: >>> Border(Size.px(1), BorderStyle.SOLID, Color.black) Border('1px solid black') >>> Border.solid(2, "red") Border('2px solid red') >>> Border().width(Size.px(2)).dashed().color(Color.blue) Border('2px dashed blue') """ def __init__( self, width: Size | str | int | float = "1px", style: BorderStyle | str = BorderStyle.SOLID, color: Color | str = "black", ) -> None: """Create a Border. Args: width: Border width (Size, string, or number in pixels) style: Border style (BorderStyle enum or string) color: Border color (Color or string) """ # Normalize width if isinstance(width, (int, float)): self._width = Size.px(width) elif isinstance(width, str): self._width = Size(width) else: self._width = width # Normalize style if isinstance(style, str): try: self._style = BorderStyle(style) except ValueError: raise ValueError(f"Invalid border style: '{style}'") else: self._style = style # Normalize color if isinstance(color, str): self._color = Color(color) else: self._color = color
[docs] def to_css(self) -> str: """Return the CSS border shorthand.""" return f"{self._width.to_css()} {self._style.to_css()} {self._color.to_css()}"
@property def width_value(self) -> Size: """The border width.""" return self._width @property def style_value(self) -> BorderStyle: """The border style.""" return self._style @property def color_value(self) -> Color: """The border color.""" return self._color # Fluent builder methods (return new instances)
[docs] def width(self, w: Size | str | int | float) -> Border: """Return a new Border with the specified width.""" return Border(w, self._style, self._color)
[docs] def style(self, s: BorderStyle | str) -> Border: """Return a new Border with the specified style.""" return Border(self._width, s, self._color)
[docs] def color(self, c: Color | str) -> Border: """Return a new Border with the specified color.""" return Border(self._width, self._style, c)
# Instance methods for changing style (fluent API)
[docs] def as_solid(self) -> Border: """Return a new Border with solid style.""" return self.style(BorderStyle.SOLID)
[docs] def as_dashed(self) -> Border: """Return a new Border with dashed style.""" return self.style(BorderStyle.DASHED)
[docs] def as_dotted(self) -> Border: """Return a new Border with dotted style.""" return self.style(BorderStyle.DOTTED)
[docs] def as_double(self) -> Border: """Return a new Border with double style.""" return self.style(BorderStyle.DOUBLE)
[docs] def as_none(self) -> Border: """Return a new Border with no style.""" return self.style(BorderStyle.NONE)
# ------------------------------------------------------------------------- # Class Methods - Clean Factory Methods (beginner-friendly) # -------------------------------------------------------------------------
[docs] @classmethod def solid( cls, width: Size | str | int | float = 1, color: Color | str = "black" ) -> Border: """Create a solid border. Args: width: Border width (default 1px) color: Border color (default black) Examples: >>> Border.solid() Border('1px solid black') >>> Border.solid(2, "red") Border('2px solid red') """ return cls(width, BorderStyle.SOLID, color)
[docs] @classmethod def dashed( cls, width: Size | str | int | float = 1, color: Color | str = "black" ) -> Border: """Create a dashed border. Args: width: Border width (default 1px) color: Border color (default black) Examples: >>> Border.dashed() Border('1px dashed black') >>> Border.dashed(2, "blue") Border('2px dashed blue') """ return cls(width, BorderStyle.DASHED, color)
[docs] @classmethod def dotted( cls, width: Size | str | int | float = 1, color: Color | str = "black" ) -> Border: """Create a dotted border. Args: width: Border width (default 1px) color: Border color (default black) Examples: >>> Border.dotted() Border('1px dotted black') """ return cls(width, BorderStyle.DOTTED, color)
[docs] @classmethod def double( cls, width: Size | str | int | float = 3, color: Color | str = "black" ) -> Border: """Create a double border. Args: width: Border width (default 3px - minimum for double to show) color: Border color (default black) Examples: >>> Border.double() Border('3px double black') """ return cls(width, BorderStyle.DOUBLE, color)
[docs] @classmethod def none(cls) -> Border: """Create a border with no visible style. Examples: >>> Border.none() Border('1px none black') """ return cls(0, BorderStyle.NONE, "transparent")
# ------------------------------------------------------------------------- # Width Presets (beginner-friendly) # -------------------------------------------------------------------------
[docs] @classmethod def thin( cls, color: Color | str = "black", style: BorderStyle | str = BorderStyle.SOLID ) -> Border: """Create a thin border (1px). Args: color: Border color (default black) style: Border style (default solid) Examples: >>> Border.thin() Border('1px solid black') >>> Border.thin("red") Border('1px solid red') """ return cls(1, style, color)
[docs] @classmethod def medium( cls, color: Color | str = "black", style: BorderStyle | str = BorderStyle.SOLID ) -> Border: """Create a medium border (2px). Args: color: Border color (default black) style: Border style (default solid) Examples: >>> Border.medium() Border('2px solid black') """ return cls(2, style, color)
[docs] @classmethod def thick( cls, color: Color | str = "black", style: BorderStyle | str = BorderStyle.SOLID ) -> Border: """Create a thick border (4px). Args: color: Border color (default black) style: Border style (default solid) Examples: >>> Border.thick() Border('4px solid black') >>> Border.thick("navy") Border('4px solid navy') """ return cls(4, style, color)
def __eq__(self, other: object) -> bool: if isinstance(other, Border): return ( self._width == other._width and self._style == other._style and self._color == other._color ) if isinstance(other, str): return self.to_css() == other return NotImplemented def __hash__(self) -> int: return hash((self._width, self._style, self._color))
# ============================================================================= # Spacing Class # =============================================================================
[docs] class Spacing(CSSValue): """Represents CSS spacing (padding/margin) with 1-4 values. Examples: >>> Spacing.all(Size.px(10)) Spacing('10px') >>> Spacing.symmetric(Size.px(10), Size.px(20)) Spacing('10px 20px') >>> Spacing.edges(Size.px(1), Size.px(2), Size.px(3), Size.px(4)) Spacing('1px 2px 3px 4px') """ def __init__( self, top: Size | str | int | float, right: Size | str | int | float | None = None, bottom: Size | str | int | float | None = None, left: Size | str | int | float | None = None, ) -> None: """Create a Spacing value. Args: top: Top value (or all sides if others are None) right: Right value (or horizontal if bottom/left are None) bottom: Bottom value left: Left value """ self._top = self._normalize(top) self._right = self._normalize(right) if right is not None else None self._bottom = self._normalize(bottom) if bottom is not None else None self._left = self._normalize(left) if left is not None else None def _normalize(self, value: Size | str | int | float) -> Size: """Normalize a value to Size.""" if isinstance(value, Size): return value if isinstance(value, (int, float)): return Size.px(value) return Size(value)
[docs] def to_css(self) -> str: """Return the CSS spacing value.""" if self._right is None: # Single value: all sides return self._top.to_css() if self._bottom is None: # Two values: vertical horizontal return f"{self._top.to_css()} {self._right.to_css()}" if self._left is None: # Three values: top horizontal bottom top = self._top.to_css() right = self._right.to_css() bottom = self._bottom.to_css() return f"{top} {right} {bottom}" # Four values: top right bottom left top = self._top.to_css() right = self._right.to_css() bottom = self._bottom.to_css() left = self._left.to_css() return f"{top} {right} {bottom} {left}"
@property def top(self) -> Size: """Top spacing.""" return self._top @property def right(self) -> Size: """Right spacing (same as top if not specified).""" return self._right if self._right is not None else self._top @property def bottom(self) -> Size: """Bottom spacing (same as top if not specified).""" return self._bottom if self._bottom is not None else self._top @property def left(self) -> Size: """Left spacing (same as right if not specified).""" if self._left is not None: return self._left return self._right if self._right is not None else self._top # Factory methods
[docs] @classmethod def all(cls, value: Size | str | int | float) -> Spacing: """Create uniform spacing on all sides.""" return cls(value)
[docs] @classmethod def symmetric( cls, vertical: Size | str | int | float, horizontal: Size | str | int | float, ) -> Spacing: """Create symmetric spacing (vertical and horizontal).""" return cls(vertical, horizontal)
[docs] @classmethod def edges( cls, top: Size | str | int | float, right: Size | str | int | float, bottom: Size | str | int | float, left: Size | str | int | float, ) -> Spacing: """Create spacing with explicit values for all four edges.""" return cls(top, right, bottom, left)
[docs] @classmethod def horizontal(cls, value: Size | str | int | float) -> Spacing: """Create horizontal-only spacing (left and right).""" return cls(0, value)
[docs] @classmethod def vertical(cls, value: Size | str | int | float) -> Spacing: """Create vertical-only spacing (top and bottom).""" return cls(value, 0)
# ------------------------------------------------------------------------- # Named Presets (beginner-friendly) # -------------------------------------------------------------------------
[docs] @classmethod def zero(cls) -> Spacing: """Create zero spacing.""" return cls(0)
[docs] @classmethod def xs(cls) -> Spacing: """Create extra-small spacing (4px).""" return cls(4)
[docs] @classmethod def sm(cls) -> Spacing: """Create small spacing (8px).""" return cls(8)
[docs] @classmethod def md(cls) -> Spacing: """Create medium spacing (16px).""" return cls(16)
[docs] @classmethod def lg(cls) -> Spacing: """Create large spacing (24px).""" return cls(24)
[docs] @classmethod def xl(cls) -> Spacing: """Create extra-large spacing (32px).""" return cls(32)
# ------------------------------------------------------------------------- # Common UI Patterns (beginner-friendly) # -------------------------------------------------------------------------
[docs] @classmethod def button(cls) -> Spacing: """Create typical button padding (8px 16px).""" return cls(8, 16)
[docs] @classmethod def card(cls) -> Spacing: """Create typical card padding (16px).""" return cls(16)
[docs] @classmethod def input(cls) -> Spacing: """Create typical input field padding (8px 12px).""" return cls(8, 12)
[docs] @classmethod def section(cls) -> Spacing: """Create typical section margins (24px top/bottom, 0 left/right).""" return cls(24, 0)
[docs] @classmethod def compact(cls) -> Spacing: """Create compact spacing (4px 8px).""" return cls(4, 8)
[docs] @classmethod def relaxed(cls) -> Spacing: """Create relaxed/generous spacing (16px 24px).""" return cls(16, 24)
def __eq__(self, other: object) -> bool: if isinstance(other, Spacing): return self.to_css() == other.to_css() if isinstance(other, str): return self.to_css() == other return NotImplemented def __hash__(self) -> int: return hash(self.to_css())
# ============================================================================= # Type aliases for convenience # ============================================================================= # Union types for method signatures that accept both new types and strings SizeValue = Size | str | int | float ColorValue = Color | str BorderValue = Border | str SpacingValue = Spacing | str | int | float