///|
/// Hono CSS Helper
/// https://hono.dev/docs/helpers/css
///|
/// CSS class name trait - accepts String or CssClass
/// Used for jsx class attribute
pub trait CssClassName {
fn value(Self) -> @core.Any = _
}
///|
impl CssClassName with fn value(self) -> @core.Any {
@core.any(self)
}
///|
pub impl CssClassName for String
///|
/// CSS class - wraps the JS css object to preserve callbacks for Style component
#external
pub type CssClass
///|
pub fn CssClass::as_any(self : CssClass) -> @core.Any = "%identity"
///|
pub impl CssClassName for CssClass with fn value(self) {
self.as_any()
}
///| FFI
///|
extern "js" fn ffi_import_hono_css() -> @js.Promise[@core.Any] =
#|() => import("hono/css")
///|
/// jsxs for Style component - uses #module for ESM compatibility
#module("hono/jsx/jsx-runtime")
extern "js" fn ffi_jsxs(component : @core.Any, props : @core.Any) -> @core.Any = "jsxs"
///|
extern "js" fn ffi_css_to_string(c : @core.Any) -> String =
#|(c) => {
#| if (typeof c === 'string') return c;
#| const symbols = Object.getOwnPropertySymbols(c);
#| for (const s of symbols) {
#| const val = c[s];
#| if (typeof val === 'string' && val.length > 0) return val;
#| }
#| return '';
#|}
///| HonoCss type - initialized instance
///|
#external
pub type HonoCss
///|
extern "js" fn ffi_hono_css_css(
m : HonoCss,
template : String,
args : FixedArray[@core.Any],
) -> @core.Any =
#|(m, template, args) => {
#| const [strings, values] = template.split(/\$\{@(\d+)\}/).reduce(
#| ([s, v], x, i) => i % 2 ? [s, [...v, args[+x]]] : [[...s, x], v], [[], []]
#| );
#| return m.css((strings.raw = strings, strings), ...values);
#|}
///|
extern "js" fn ffi_hono_css_keyframes(
m : HonoCss,
template : String,
args : FixedArray[@core.Any],
) -> @core.Any =
#|(m, template, args) => {
#| const [strings, values] = template.split(/\$\{@(\d+)\}/).reduce(
#| ([s, v], x, i) => i % 2 ? [s, [...v, args[+x]]] : [[...s, x], v], [[], []]
#| );
#| return m.keyframes((strings.raw = strings, strings), ...values);
#|}
///|
extern "js" fn ffi_hono_css_cx(m : HonoCss, classes : @core.Any) -> @core.Any =
#|(m, classes) => m.cx(...classes)
///|
extern "js" fn ffi_hono_css_style(m : HonoCss) -> @core.Any =
#|(m) => m.Style
///| Public API
///|
/// Import hono/css module
pub async fn import_hono_css() -> HonoCss {
ffi_import_hono_css().wait().cast()
}
///|
/// Get the class name string from CssClass
pub fn CssClass::to_string(self : CssClass) -> String {
ffi_css_to_string(self.as_any())
}
///|
/// Create a CSS class using tagged template syntax.
///
/// Template format uses `${@0}`, `${@1}`, etc. as placeholders for interpolation.
/// Returns a CssClass that can be used with jsx class attribute.
///
/// # Example
///
/// ```moonbit nocheck
/// // Simple usage without interpolation
/// let cls = hono_css.css("color: red")
///
/// // With interpolation
///
/// let color = "blue"
///
/// let size = 16
///
/// let cls = hono_css.css("color: ${@0}; font-size: ${@1}px", args=[
/// @core.any(color),
/// @core.any(size),
/// ])
/// ```
pub fn HonoCss::css(
self : HonoCss,
template : String,
args? : Array[@core.Any] = [],
) -> CssClass {
ffi_hono_css_css(self, template, FixedArray::from_array(args[:])).cast()
}
///|
/// Create keyframes animation using tagged template syntax.
///
/// Template format uses `${@0}`, `${@1}`, etc. as placeholders for interpolation.
/// Returns a CssClass for the animation.
///
/// # Example
///
/// ```moonbit nocheck
/// // Simple usage
/// let anim = hono_css.keyframes("from { opacity: 0 } to { opacity: 1 }")
///
/// // With interpolation
///
/// let start = 0.5
///
/// let end = 1.0
///
/// let anim = hono_css.keyframes(
/// "from { transform: scale(${@0}) } to { transform: scale(${@1}) }",
/// args=[@core.any(start), @core.any(end)],
/// )
/// ```
pub fn HonoCss::keyframes(
self : HonoCss,
template : String,
args? : Array[@core.Any] = [],
) -> CssClass {
ffi_hono_css_keyframes(self, template, FixedArray::from_array(args[:])).cast()
}
///|
/// Combine multiple CSS classes
pub fn HonoCss::cx(self : HonoCss, classes : Array[CssClass]) -> CssClass {
ffi_hono_css_cx(self, @core.any(classes.map(fn(c) { c.as_any() }))).cast()
}
///|
/// Style component - renders collected CSS in head
/// Must be included in the document head for CSS to work
pub fn HonoCss::style_component(self : HonoCss, nonce? : String) -> JSXNode {
let props = @core.new_object()
if nonce is Some(n) {
props["nonce"] = @core.any(n)
}
ffi_jsxs(ffi_hono_css_style(self), props).cast()
}