islands-css 0.1.1

Tailwind class merging (cn) and a typed variants! macro for islands.rs, with shadcn-style conflict resolution.
Documentation
/// Declarative macro that reproduces the TypeScript `cva` (class-variance-authority) API in Rust.
///
/// Expands to:
/// - `pub enum {Name}Variant { ... }` with one variant per key in the `variant:` block
/// - `pub enum {Name}Size { ... }` with one variant per key in the `size:` block
/// - `pub struct {Name}Variants { pub variant: {Name}Variant, pub size: {Name}Size }`
/// - `impl Default for {Name}Variants` — uses the keys named in `defaults: { variant: X, size: Y }`
/// - `pub struct {Name};` — a namespace type; never instantiated
/// - `impl {Name} { pub fn class(v: {Name}Variants) -> String { ... } }`
///
/// Usage:
/// ```ignore
/// variants! {
///     Button,
///     base: "inline-flex items-center ...",
///     variant: {
///         default: "bg-primary text-primary-foreground",
///         destructive: "bg-destructive ...",
///     },
///     size: {
///         default: "h-10 px-4 py-2",
///         sm: "h-9 rounded-md px-3",
///     },
///     defaults: { variant: default, size: default },
/// }
/// ```
#[macro_export]
macro_rules! variants {
    (
        $name:ident,
        base: $base:literal,
        variant: { $( $variant_key:ident : $variant_class:literal ),+ $(,)? },
        size: { $( $size_key:ident : $size_class:literal ),+ $(,)? },
        defaults: { variant: $default_variant:ident, size: $default_size:ident } $(,)?
    ) => {
        $crate::variants!(@paste
            $name,
            $base,
            [ $( $variant_key : $variant_class ),+ ],
            [ $( $size_key : $size_class ),+ ],
            $default_variant,
            $default_size
        );
    };

    // Internal arm — uses paste! to concatenate idents
    (@paste
        $name:ident,
        $base:literal,
        [ $( $variant_key:ident : $variant_class:literal ),+ ],
        [ $( $size_key:ident : $size_class:literal ),+ ],
        $default_variant:ident,
        $default_size:ident
    ) => {
        $crate::__paste! {
            /// Variant dimension for this component.
            #[allow(dead_code)]
            pub enum [< $name Variant >] {
                $( [< $variant_key:camel >] ),+
            }

            /// Size dimension for this component.
            #[allow(dead_code)]
            pub enum [< $name Size >] {
                $( [< $size_key:camel >] ),+
            }

            /// Combined variants struct for this component.
            #[allow(dead_code)]
            pub struct [< $name Variants >] {
                pub variant: [< $name Variant >],
                pub size: [< $name Size >],
            }

            impl Default for [< $name Variants >] {
                fn default() -> Self {
                    Self {
                        variant: [< $name Variant >]::[< $default_variant:camel >],
                        size: [< $name Size >]::[< $default_size:camel >],
                    }
                }
            }

            /// Namespace type; never instantiated. Use `$name::class(variants)`.
            #[allow(dead_code)]
            pub struct $name;

            impl $name {
                /// Resolve the Tailwind class string for the given variant combination.
                pub fn class(v: [< $name Variants >]) -> String {
                    let variant_class = match v.variant {
                        $( [< $name Variant >]::[< $variant_key:camel >] => $variant_class, )+
                    };
                    let size_class = match v.size {
                        $( [< $name Size >]::[< $size_key:camel >] => $size_class, )+
                    };
                    $crate::cn(&[$base, variant_class, size_class])
                }
            }
        }
    };
}

// Re-export paste so callers of `variants!` don't need to depend on it separately.
#[doc(hidden)]
pub use paste::paste as __paste;