Grid Collection

Rust/UI component that displays a grid collection.

view_transition
  • Rust/UI Icons - CopyCopy Demo
Groceries
$80.00
Groceries
$80.00
Groceries
$80.00
Groceries
$80.00
Daily Needs
4 items
Electricity
$80.00
Water
$80.00
Internet
$80.00
Utilities
3 items
Streaming
$80.00
Courses
$80.00
Software
$80.00
Streaming
$80.00
Subscriptions
4 items
use leptos::prelude::*;
use leptos_meta::Stylesheet;

use crate::components::extensions::grid_collection::{Content, GridCollection, GridItem, Icon, IconGrid, ItemContent};

// TODO. Default Trait

#[derive(Clone)]
struct GridItem {
    title: String,
    subtitle: String,
    transition_index: i32,
    items: Vec<Item>,
}

#[derive(Clone)]
struct Item {
    title: String,
    price: String,
    transition_index: i32,
    chevron_index: i32,
}

#[component]
pub fn DemoGridCollection() -> impl IntoView {
    let grid_items = vec![
        GridItem {
            title: "Daily Needs".to_string(),
            subtitle: "4 items".to_string(),
            transition_index: 1,
            items: vec![
                Item {
                    title: "Groceries".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 1,
                    chevron_index: 1,
                },
                Item {
                    title: "Groceries".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 2,
                    chevron_index: 5,
                },
                Item {
                    title: "Groceries".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 3,
                    chevron_index: 6,
                },
                Item {
                    title: "Groceries".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 4,
                    chevron_index: 7,
                },
            ],
        },
        GridItem {
            title: "Utilities".to_string(),
            subtitle: "3 items".to_string(),
            transition_index: 2,
            items: vec![
                Item {
                    title: "Electricity".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 5,
                    chevron_index: 2,
                },
                Item { title: "Water".to_string(), price: "$80.00".to_string(), transition_index: 6, chevron_index: 9 },
                Item {
                    title: "Internet".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 7,
                    chevron_index: 10,
                },
            ],
        },
        GridItem {
            title: "Subscriptions".to_string(),
            subtitle: "4 items".to_string(),
            transition_index: 3,
            items: vec![
                Item {
                    title: "Streaming".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 8,
                    chevron_index: 3,
                },
                Item {
                    title: "Courses".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 9,
                    chevron_index: 12,
                },
                Item {
                    title: "Software".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 10,
                    chevron_index: 13,
                },
                Item {
                    title: "Streaming".to_string(),
                    price: "$80.00".to_string(),
                    transition_index: 11,
                    chevron_index: 14,
                },
            ],
        },
    ];

    view! {
        <Stylesheet href="/components/grid_collection.css" />
        <script src="/components/grid_collection.js" />

        <div class="p-0 px-5 my-10 mx-auto w-full font-sans bg-white max-w-[500px] h-[600px]">
            <GridCollection>
                {grid_items
                    .into_iter()
                    .map(|item| {
                        view! {
                            <SharedGridItem
                                title=item.title
                                subtitle=item.subtitle
                                transition_index=item.transition_index
                            >
                                {item
                                    .items
                                    .into_iter()
                                    .map(|shared_item| {
                                        view! {
                                            <SharedItem
                                                title=shared_item.title
                                                price=shared_item.price
                                                transition_index=shared_item.transition_index
                                                chevron_index=shared_item.chevron_index
                                            />
                                        }
                                    })
                                    .collect_view()}
                            </SharedGridItem>
                        }
                    })
                    .collect_view()}
            </GridCollection>
        </div>
    }
}

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[component]
pub fn SharedGridItem(children: Children, title: String, subtitle: String, transition_index: i32) -> impl IntoView {
    const CLASS_CLOSE_ICON: &str = "close-icon flex items-center justify-center rounded-full size-6 bg-accent hidden";

    let transition_grid_item = format!("view-transition-name: grid-item-{transition_index}");
    let transition_grid_icon = format!("view-transition-name: icon-grid-{transition_index}");

    let transition_name_content = format!("view-transition-name: content-{transition_index}");
    let transition_name_title = format!("view-transition-name: title-{transition_index}");
    let transition_name_close_icon = format!("view-transition-name: close-icon-{transition_index}");

    let transition_name_count = format!("view-transition-name: count-{transition_index}");
    let transition_name_chevron = format!("view-transition-name: chevron-{transition_index}");

    view! {
        <GridItem attr:style=transition_grid_item>
            <IconGrid attr:style=transition_grid_icon>{children()}</IconGrid>

            <Content attr:style=transition_name_content>
                <div class="text-base font-medium text-zinc-800 w-fit h-fit" style=transition_name_title>
                    {title}
                </div>
                <div class=CLASS_CLOSE_ICON style=transition_name_close_icon>
                    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
                        <path d="M12 4L4 12M4 4l8 8" stroke-width="2" stroke-linecap="round" />
                    </svg>
                </div>
                <div class="displayNoneWhenExpanded text-muted-foreground" style=transition_name_count>
                    {subtitle}
                </div>
            </Content>
            <div class="chevron" style=transition_name_chevron>
                <SvgChevron />
            </div>
        </GridItem>
    }
}

#[component]
pub fn SharedItem(title: String, price: String, transition_index: i32, chevron_index: i32) -> impl IntoView {
    let transition_title = format!("view-transition-name: item-title-{transition_index}");
    let transition_price = format!("view-transition-name: item-price-{transition_index}");

    let transition_item = format!("view-transition-name: item-{transition_index}");
    let transition_icon = format!("view-transition-name: icon-{transition_index}");

    let transition_chevron = format!("view-transition-name: chevron-{chevron_index}");

    view! {
        <div class="item" style=transition_item>
            <Icon attr:style=transition_icon>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <circle cx="12" cy="12" r="10" stroke-width="2" />
                    <path d="M10 8l6 4-6 4V8z" stroke-width="2" />
                </svg>
            </Icon>

            <ItemContent>
                <div class="item-title" style=transition_title>
                    {title}
                </div>
                <div class="item-price" style=transition_price>
                    {price}
                </div>
            </ItemContent>

            <div class="chevron" style=transition_chevron>
                <SvgChevron />
            </div>
        </div>
    }
}

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[component]
pub fn SvgChevron() -> impl IntoView {
    view! {
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <path d="M9 18l6-6-6-6" stroke-width="2" stroke-linecap="round" />
        </svg>
    }
}

Installation

# Coming soon :)

Usage

// Coming soon 🦀