AkurAI Build
Menu

Public repository / master

AkurAI-Framework / crates/css/src/lib.rs

Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson

//! AkurAI-Framework atomic/utility CSS engine — pure `std`, zero dependencies.
//!
//! Tailwind-grade atomic CSS, but the utility grammar is ours, so the output is
//! bounded and deterministic. The engine [`scan`]s source for the class tokens
//! actually used, then [`generate`]s **only** the CSS for the utilities among
//! them — unknown classes (hand-written component classes, identifiers in
//! code) are ignored.
//!
//! ```
//! use akurai_css::build;
//!
//! let html = r#"<div class="flex p-2 text-center unknown-thing">hi</div>"#;
//! let css = build(&[html]);
//! assert_eq!(
//!     css,
//!     ".flex{display:flex}.p-2{padding:8px}.text-center{text-align:center}"
//! );
//! ```
//!
//! Two entry points:
//! - [`build`] scans HTML `class="…"` attributes and keeps first-seen order.
//! - [`build_any`] scans **any** source (Rust string literals, JS, HTML) for
//!   candidate tokens and emits them in canonical (Tailwind-like) order, so a
//!   pure-Rust UI written as `class("inline-flex items-center gap-2 …")` can be
//!   styled.
//!
//! ```
//! let rust = r#"el("button").class("px-4 hover:bg-accent/90 md:w-[220px]")"#;
//! let css = akurai_css::build_any(&[rust]);
//! assert_eq!(
//!     css,
//!     concat!(
//!         ".px-4{padding-left:16px;padding-right:16px}",
//!         r".hover\:bg-accent\/90:hover{background-color:color-mix(in srgb, var(--accent) 90%, transparent)}",
//!         r"@media (min-width:768px){.md\:w-\[220px\]{width:220px}}",
//!     )
//! );
//! ```
//!
//! Grammar summary (details in [`utilities`] and [`variants`]):
//! - **spacing** (4px scale, plus `px`, `0.5`/`1.5`/`2.5`/`3.5`, `[arbitrary]`):
//!   `p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml-{n}` (margins take `auto`),
//!   `gap|gap-x|gap-y`, `space-x|space-y` → e.g. `p-2` ⇒ `padding:8px`.
//! - **sizing**: `w|h|size|min-w|min-h|max-w|max-h` with the scale, fractions
//!   (`1/2` ⇒ `50%`), `full`, `screen`, `dvh`, `min`/`max`/`fit`, `auto`, named
//!   `max-w-{xs…7xl,prose,screen-*}`.
//! - **layout**: display keywords, `sr-only`, position, `inset|top|right|bottom|
//!   left`, `z-*`, `overflow-*`, visibility, `isolate`, `box-*`, `aspect-*`,
//!   `object-*`, table layout.
//! - **flex/grid**: direction/wrap, `flex-1|auto|none|initial`, `grow`,
//!   `shrink`, `basis-*`, `order-*`, `items|justify|content|self|place-*`,
//!   `grid-cols|rows-{n|none|[…]}`, `col|row-span|start|end-*`,
//!   `grid-flow-*`, `auto-cols|rows-*`.
//! - **typography**: `text-{xs…9xl}` (font-size only; `text-sm/6` adds a line
//!   height), `font-{weight|sans|mono}`, `tracking-*`, `leading-*`, alignment,
//!   case, decoration, `truncate`, `whitespace-*`, `break-*`, `line-clamp-*`,
//!   `align-*`, `list-*`.
//! - **colour** (theme tokens ⇒ `var(--token)`, `/N` opacity, `[arbitrary]`):
//!   `text|bg|border(-side)|ring|ring-offset|outline|fill|stroke|accent|caret|
//!   divide|placeholder|decoration-{colour}`; gradients `bg-gradient-to-*` with
//!   `from-|via-|to-`.
//! - **borders**: widths per side, styles, `divide-x|y`, `rounded-*` — **flat
//!   corners: every radius clamps to ≤ 6px** (`rounded-full` ⇒ 6px).
//! - **effects**: `shadow-*` + `ring-*` composed via `--tw-*`, `opacity-*`,
//!   `blur-*`, `backdrop-blur-*`, `translate|scale|rotate-*`.
//! - **interactivity**: cursor, pointer events, select, resize, outline,
//!   transitions + `duration|ease|delay`, `animate-spin|pulse|ping`, snap,
//!   touch, `will-change-*`.
//! - **arbitrary properties**: `[mask-type:luminance]`.
//! - **modifiers**: `!` important (`!p-0`), `-` negative (`-mt-2`,
//!   `-translate-x-1/2`).
//! - **variants** (chainable, `md:hover:bg-accent`): state pseudo-classes,
//!   `placeholder:`, `group-*:`/`peer-*:`, `aria-*:`, `data-[k=v]:`, `sm|md|
//!   lg|xl|2xl:`, `dark:`, `motion-safe|reduce:`, `print:`.

#![forbid(unsafe_code)]

pub mod escape;
pub mod generate;
pub mod scan;
pub mod theme;
pub mod utilities;
pub mod variants;

/// Scan every source for class tokens used in HTML `class` attributes and
/// return a minimal stylesheet containing only the recognised utility classes.
///
/// Sources are scanned left-to-right; a class is emitted once, at the position
/// of its first occurrence across all sources (variant and media rules follow
/// the base rules; see [`generate`]). Unknown classes are omitted.
pub fn build(sources: &[&str]) -> String {
    generate::stylesheet(&collect(sources, scan::classes))
}

/// Scan **any** source text (Rust, JS, HTML, …) for candidate class tokens via
/// [`scan::candidates`] and return a minimal stylesheet of the recognised
/// utilities, in canonical order ([`generate::stylesheet_canonical`]). Tokens
/// that are not utilities are silently ignored.
pub fn build_any(sources: &[&str]) -> String {
    generate::stylesheet_canonical(&collect(sources, scan::candidates))
}

/// Run `scanner` over every source, de-duplicating in first-seen order.
fn collect(sources: &[&str], scanner: fn(&str) -> Vec<String>) -> Vec<String> {
    let mut seen = std::collections::HashSet::new();
    let mut classes: Vec<String> = Vec::new();
    for source in sources {
        for class in scanner(source) {
            if seen.insert(class.clone()) {
                classes.push(class);
            }
        }
    }
    classes
}