Image source

Inspect and copy the reviewed source included with this Free component.

Free source · 4 files

recipe.rs

// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Image recipe — typed fit/layout axes and a named placeholder slot.

use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};

/// Object-fit mode for the image.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, proa_macros::ProaVariant)]
#[variant(name = "fit")]
pub enum ImageFit {
    #[default]
    None,
    Cover,
    Contain,
    Fill,
}

impl ImageFit {
    pub const fn classes(self) -> &'static str {
        match self {
            Self::None => "",
            Self::Cover => "object-cover",
            Self::Contain => "object-contain",
            Self::Fill => "object-fill",
        }
    }

    /// Compatibility form of the former component-local helper.
    pub const fn class(self) -> Option<&'static str> {
        match self {
            Self::None => None,
            _ => Some(self.classes()),
        }
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::None => "none",
            Self::Cover => "cover",
            Self::Contain => "contain",
            Self::Fill => "fill",
        }
    }
}

/// Optional inputs accepted by [`ImageRecipe::resolve`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ImageRecipeProps {
    pub fit: Option<ImageFit>,
    pub fill: Option<bool>,
    pub rounded: Option<bool>,
}

/// Static Image slots, modifiers, and defaults.
#[derive(Debug, Clone, Copy)]
pub struct ImageRecipe {
    pub root: &'static str,
    pub placeholder: &'static str,
    pub fill: &'static str,
    pub rounded: &'static str,
    fit_default: ImageFit,
    fill_default: bool,
    rounded_default: bool,
}

impl ImageRecipe {
    pub const fn resolve(&'static self, props: ImageRecipeProps) -> ImageStyle {
        let fit = match props.fit {
            Some(fit) => fit,
            None => self.fit_default,
        };
        let fill = match props.fill {
            Some(fill) => fill,
            None => self.fill_default,
        };
        let rounded = match props.rounded {
            Some(rounded) => rounded,
            None => self.rounded_default,
        };
        ImageStyle {
            recipe: self,
            fit,
            fill,
            rounded,
        }
    }
}

/// Fully resolved, streamable Image style.
#[must_use]
#[derive(Debug, Clone, Copy)]
pub struct ImageStyle {
    recipe: &'static ImageRecipe,
    fit: ImageFit,
    fill: bool,
    rounded: bool,
}

impl ImageStyle {
    pub const fn fit(self) -> ImageFit {
        self.fit
    }

    pub const fn fill(self) -> bool {
        self.fill
    }

    pub const fn rounded(self) -> bool {
        self.rounded
    }
}

impl AttrValue for ImageStyle {
    #[inline(always)]
    fn should_render(&self) -> bool {
        true
    }

    #[inline(always)]
    fn render_attr_value<B: WriteBuf, L: DataLoader>(
        &self,
        cx: &mut WebContext<L, B>,
    ) -> Result<(), WriteError> {
        cx.out.extend_static(self.recipe.root.as_bytes())?;
        if !self.fit.classes().is_empty() {
            cx.out.extend_static(b" ")?;
            cx.out.extend_static(self.fit.classes().as_bytes())?;
        }
        if self.fill {
            cx.out.extend_static(b" ")?;
            cx.out.extend_static(self.recipe.fill.as_bytes())?;
        }
        if self.rounded {
            cx.out.extend_static(b" ")?;
            cx.out.extend_static(self.recipe.rounded.as_bytes())?;
        }
        Ok(())
    }
}

pub const IMAGE_RECIPE: ImageRecipe = ImageRecipe {
    root: "max-w-full",
    placeholder: tw_join!("relative", "overflow-hidden"),
    fill: tw_join!("absolute", "inset-0", "w-full", "h-full"),
    rounded: "rounded-lg",
    fit_default: ImageFit::None,
    fill_default: false,
    rounded_default: false,
};

pub const fn image_recipe() -> &'static ImageRecipe {
    &IMAGE_RECIPE
}

impl crate::variant_spec::ComponentSpec for ImageRecipe {
    const NAME: &'static str = "Image";

    fn base_classes(&self) -> &'static str {
        self.root
    }

    fn variant_metadata(&self) -> Vec<crate::variant_spec::VariantMetadata> {
        vec![crate::variant_spec::variant_metadata::<ImageFit>()]
    }
}

/// Compatibility accessor for the former base helper.
pub const fn image_base() -> &'static str {
    IMAGE_RECIPE.root
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn recipe_exposes_image_and_placeholder_parts() {
        let recipe = image_recipe();
        assert!(recipe.root.contains("max-w-full"));
        assert!(recipe.placeholder.contains("overflow-hidden"));
        assert!(recipe.fill.contains("absolute"));
        assert_eq!(recipe.rounded, "rounded-lg");
    }

    #[test]
    fn recipe_resolves_defaults_and_overrides() {
        let default = IMAGE_RECIPE.resolve(ImageRecipeProps::default());
        assert_eq!(default.fit(), ImageFit::None);
        assert!(!default.fill());
        assert!(!default.rounded());

        let custom = IMAGE_RECIPE.resolve(ImageRecipeProps {
            fit: Some(ImageFit::Cover),
            fill: Some(true),
            rounded: Some(true),
        });
        assert_eq!(custom.fit(), ImageFit::Cover);
        assert!(custom.fill());
        assert!(custom.rounded());
    }

    #[test]
    fn fit_variants_have_stable_values() {
        assert_eq!(ImageFit::None.as_str(), "none");
        assert_eq!(ImageFit::Cover.classes(), "object-cover");
        assert_eq!(ImageFit::Contain.classes(), "object-contain");
        assert_eq!(ImageFit::Fill.classes(), "object-fill");
    }

    #[test]
    fn recipe_metadata_exposes_the_fit_axis() {
        let metadata = crate::variant_spec::component_metadata(image_recipe());
        assert_eq!(metadata.name, "Image");
        assert!(metadata.anatomy.is_none());
        assert_eq!(metadata.variants.len(), 1);
        let fit = &metadata.variants[0];
        assert_eq!(fit.name, "fit");
        assert_eq!(fit.default_value, "none");
        assert_eq!(
            fit.options
                .iter()
                .map(|option| option.value)
                .collect::<Vec<_>>(),
            ["none", "cover", "contain", "fill"]
        );
        metadata.validate().unwrap();
    }
}

Copy this source with the Free Proa UI components it imports; those component files remain available from their own source pages.

Licensed under MIT OR Apache-2.0. Third-party notices remain attached to the installed payload.

Open Markdown
FreeImage

Responsive image output.

Install with the Proa CLI
proa ui add image

Installs the reviewed component source, dependencies, shared support files, and required legal notices.

CLI and registry setup
recipe.rs
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Image recipe — typed fit/layout axes and a named placeholder slot.

use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};

/// Object-fit mode for the image.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, proa_macros::ProaVariant)]
#[variant(name = "fit")]
pub enum ImageFit {
    #[default]
    None,
    Cover,
    Contain,
    Fill,
}

impl ImageFit {
    pub const fn classes(self) -> &'static str {

Search

Type at least 2 characters

Customize

Make it yours

Palette

Radius

Overview
Components
Settings

Build your own

Themed Proa UI surface

Preview
Components52
RecipesTyped
customer-portal