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
}