Current section

Files

Jump to
mishka_chelekom priv components gallery.eex
Raw

priv/components/gallery.eex

defmodule <%= @module %> do
@moduledoc """
The `<%= @module %>` module provides a customizable gallery component for displaying
media content in a structured and visually appealing layout.
It supports various styles, including default, masonry, and featured galleries,
with options to control the number of columns, gaps, and additional styling.
### Features:
- **Gallery Types:** Choose between "default", "masonry", and "featured" gallery layouts.
- **Customizable Columns and Gaps:** Configure the number of columns and spacing between gallery items.
- **Flexible Media Display:** Includes a `gallery_media` component for displaying individual
media items with options for styling, shadow, and border radius.
This component is ideal for showcasing images, videos, or other media content in a grid
or masonry layout, offering a clean and flexible way to present visual elements on a web page.
**Documentation:** https://mishka.tools/chelekom/docs/gallery
"""
use Phoenix.Component
use Gettext, backend: <%= inspect(@web_module) %>.Gettext
<%= if is_nil(@type) or "filterable_gallery" in @type do %>
import Phoenix.LiveView.Utils, only: [random_id: 0]
<% end %>
<%= if is_nil(@type) or "gallery" in @type do %>
@doc """
Renders a `gallery` component that supports various layout types including default grid,
masonry, and featured styles.
You can customize the number of columns and gaps between items to achieve the desired layout.
## Examples
```elixir
<.<%= @component_prefix %>gallery type="masonry" cols="four" gap="large">
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-2.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-3.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-4.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-5.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-6.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-7.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-8.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-1.jpg" />
</.<%= @component_prefix %>gallery>
```
"""
@doc type: :component
attr :id, :string,
default: nil,
doc: "A unique identifier is used to manage state and interaction"
attr :type, :string, values: ["default", "masonry", "featured"], default: "default", doc: ""
attr :class, :any, default: nil, doc: "Custom CSS class for additional styling"
attr :cols, :string, default: "", doc: "Determines cols of elements"
attr :gap, :string, default: "", doc: "Determines gap between elements"
attr :animation, :string, default: "", doc: "Determines animation type for gallery items"
attr :animation_size, :string, default: "extra_small", doc: "Determines animation intensity/size"
attr :rest, :global,
doc:
"Global attributes can define defaults which are merged with attributes provided by the caller"
slot :inner_block, required: false, doc: "Inner block that renders HEEx content"
def <%= @component_prefix %>gallery(assigns) do
~H"""
<div
id={@id}
role="region"
class={[
(@type == "masonry" && "gallery-masonry") || "grid",
grid_gap(@gap),
@type == "masonry" && column_class(@cols),
grid_cols(@cols) != "masonry" && grid_cols(@cols),
animation(@animation, @animation_size),
@class
]}
{@rest}
>
{render_slot(@inner_block)}
</div>
"""
end
<% end %>
@doc """
Renders a `gallery_media` component within a gallery, which typically includes images.
You can customize the border radius and shadow style of the media element.
## Examples
```elixir
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image.jpg" />
<.<%= @component_prefix %>gallery_media src="https://example.com/gallery/masonry/image-2.jpg" rounded="large" shadow="shadow-lg" />
```
"""
@doc type: :component
attr :id, :string,
default: nil,
doc: "A unique identifier is used to manage state and interaction"
attr :class, :any, default: nil, doc: "Custom CSS class for additional styling"
attr :wrapper_class, :string, default: nil, doc: "Custom CSS class for additional styling"
attr :src, :string, default: nil, doc: "Media link"
attr :alt, :string, default: nil, doc: "Media link description"
attr :rounded, :string, default: "none", doc: "Determines the border radius"
attr :shadow, :string, default: "shadow-none", doc: "Determines shadow style"
attr :rest, :global,
doc:
"Global attributes can define defaults which are merged with attributes provided by the caller"
slot :inner_block, required: false, doc: "Inner block that renders HEEx content"
def <%= @component_prefix %>gallery_media(assigns) do
~H"""
<div id={@id} class={["relative gallery-media overflow-hidden transition-all duration-300", @wrapper_class]} {@rest}>
<img
:if={@src}
class={[
"gallery-media-img h-auto max-w-full transition-all duration-300",
rounded_size(@rounded),
shadow_class(@shadow),
@class
]}
src={@src}
alt={@alt}
{@rest}
/>
{render_slot(@inner_block)}
</div>
"""
end
<%= if is_nil(@type) or "filterable_gallery" in @type do %>
@doc """
Renders a filterable gallery.
## Attributes:
- `:id` - A unique DOM ID for the root element.
- `:filters` - A list of category names (e.g., ["all", "nature"]).
- `:media` - A list of media items (each should have `:src`, `:alt`, and `:category` keys).
## Slots:
- `:filter` - Optional slot for rendering custom filter buttons. You will receive the current filter value with `let={filter}`.
"""
@doc type: :component
attr :id, :string, doc: "Unique DOM ID"
attr :filters, :list, default: [], doc: "List of filter categories"
attr :media, :list, default: [], doc: "List of media items with :src, :alt, and :category"
attr :cols, :string, default: "three", doc: "Determines cols of elements"
attr :gap, :string, default: "small", doc: "Determines gap between elements"
attr :class, :any, default: nil, doc: "Add custom class"
attr :default_filter, :string, default: "All", doc: "Default Button to show all images"
slot :filter, required: false, doc: "Slot for rendering each filter button with let={filter}"
attr :animation, :string, default: "", doc: "Determines animation type for gallery items"
attr :animation_size, :string, default: "extra_small", doc: "Determines animation intensity/size"
attr :content_class, :string, default: nil, doc: "Add custom classe to content wrapper"
attr :filters_wrapper_class, :string,
default: nil,
doc: "Add custom classe to filter button wrapper"
slot :media_block,
required: false,
doc: "Optional slot for rendering each media item with let={media}"
attr :rest, :global,
doc:
"Global attributes can define defaults which are merged with attributes provided by the caller"
def <%= @component_prefix %>filterable_gallery(assigns) do
assigns = assign_new(assigns, :id, fn -> random_id() end)
~H"""
<div
id={@id}
phx-hook="GalleryFilter"
phx-update="ignore"
role="region"
data-default-filter={@default_filter}
class={["filterable-gallery", animation(@animation, @animation_size), @class]}
{@rest}
>
<div class={["filter-buttons mb-4 flex flex-wrap gap-2", @filters_wrapper_class]} role="group" aria-label={gettext("Filter options")}>
<div :if={not Enum.member?(@filters, @default_filter)}>
<div :if={@filter != []}>
{render_slot(@filter, @default_filter)}
</div>
<button
:if={@filter == []}
data-gallery-filter
data-category={@default_filter}
class="filter-btn bg-white border rounded py-1.5 px-3 leading-5 text-base-text-light border-base-border-light hover:bg-base-hover-light dark:bg-base-bg-dark dark:text-base-text-dark dark:border-base-border-dark dark:hover:bg-base-hover-dark disabled:bg-base-disabled-bg-light disabled:text-base-disabled-text-light dark:disabled:bg-base-disabled-bg-dark dark:disabled:text-base-disabled-text-dark disabled:border-base-disabled-border-light dark:disabled:border-base-disabled-border-dark shadow-sm"
>
{@default_filter}
</button>
</div>
<div :for={filter <- @filters}>
<div :for={entry <- @filter}>
{render_slot(entry, filter)}
</div>
<button
:if={@filter == []}
data-gallery-filter
data-category={filter}
class="filter-btn bg-white border rounded py-1.5 px-3 leading-5 text-base-text-light border-base-border-light hover:bg-base-hover-light dark:bg-base-bg-dark dark:text-base-text-dark dark:border-base-border-dark dark:hover:bg-base-hover-dark disabled:bg-base-disabled-bg-light disabled:text-base-disabled-text-light dark:disabled:bg-base-disabled-bg-dark dark:disabled:text-base-disabled-text-dark disabled:border-base-disabled-border-light dark:disabled:border-base-disabled-border-dark shadow-sm"
>
{filter}
</button>
</div>
</div>
<div class={["grid", grid_gap(@gap), grid_cols(@cols), @content_class]}>
<div
:for={media <- @media}
data-gallery-item
data-category={
if is_list(media.category), do: Enum.join(media.category, ","), else: media.category
}
class="transition-all duration-300 ease-in-out transform opacity-100 scale-100"
>
<div :if={@media_block != []}>
{render_slot(@media_block, media)}
</div>
<.<%= @component_prefix %>gallery_media :if={@media_block == []} src={media.src} alt={media.alt} />
</div>
</div>
</div>
"""
end
<% end %>
<%= if is_nil(@rounded) or "extra_small" in @rounded do %>
defp rounded_size("extra_small"), do: "rounded-sm"
<% end %>
<%= if is_nil(@rounded) or "small" in @rounded do %>
defp rounded_size("small"), do: "rounded"
<% end %>
<%= if is_nil(@rounded) or "medium" in @rounded do %>
defp rounded_size("medium"), do: "rounded-md"
<% end %>
<%= if is_nil(@rounded) or "large" in @rounded do %>
defp rounded_size("large"), do: "rounded-lg"
<% end %>
<%= if is_nil(@rounded) or "extra_large" in @rounded do %>
defp rounded_size("extra_large"), do: "rounded-xl"
<% end %>
<%= if is_nil(@rounded) or "full" in @rounded do %>
defp rounded_size("full"), do: "rounded-full"
<% end %>
defp rounded_size(params) when is_binary(params), do: params
defp shadow_class("extra_small"), do: "shadow-sm"
defp shadow_class("small"), do: "shadow"
defp shadow_class("medium"), do: "shadow-md"
defp shadow_class("large"), do: "shadow-lg"
defp shadow_class("extra_large"), do: "shadow-xl"
defp shadow_class("none"), do: "shadow-none"
defp shadow_class(params) when is_binary(params), do: params
defp animation("scale-down", "extra_small"), do: "[&_.gallery-media-img]:hover:scale-[0.99]"
defp animation("scale-down", "small"), do: "[&_.gallery-media-img]:hover:scale-[0.97]"
defp animation("scale-down", "medium"), do: "[&_.gallery-media-img]:hover:scale-[0.95]"
defp animation("scale-down", "large"), do: "[&_.gallery-media-img]:hover:scale-[0.93]"
defp animation("scale-down", "extra_large"), do: "[&_.gallery-media-img]:hover:scale-[0.91]"
defp animation("scale-up", "extra_small"), do: "[&_.gallery-media-img]:hover:scale-[1.02]"
defp animation("scale-up", "small"), do: "[&_.gallery-media-img]:hover:scale-[1.04]"
defp animation("scale-up", "medium"), do: "[&_.gallery-media-img]:hover:scale-[1.06]"
defp animation("scale-up", "large"), do: "[&_.gallery-media-img]:hover:scale-[1.08]"
defp animation("scale-up", "extra_large"), do: "[&_.gallery-media-img]:hover:scale-[1.1]"
defp animation("blur", "extra_small"), do: "[&_.gallery-media-img]:hover:blur-[0.05rem]"
defp animation("blur", "small"), do: "[&_.gallery-media-img]:hover:blur-[0.07rem]"
defp animation("blur", "medium"), do: "[&_.gallery-media-img]:hover:blur-[0.09rem]"
defp animation("blur", "large"), do: "[&_.gallery-media-img]:hover:blur-[0.1rem]"
defp animation("blur", "extra_large"), do: "[&_.gallery-media-img]:hover:blur-[0.12rem]"
defp animation("backdrop", "extra_small") do
[
"[&_.gallery-media]:hover:after:bg-natural-light/30 dark:[&_.gallery-media]:hover:after:bg-natural-dark/30",
"[&_.gallery-media]:after:absolute [&_.gallery-media]:after:inset-0 [&_.gallery-media]:hover:after:backdrop-blur-[0.02rem]"
]
end
defp animation("backdrop", "small") do
[
"[&_.gallery-media]:hover:after:bg-natural-light/30 dark:[&_.gallery-media]:hover:after:bg-natural-dark/30",
"[&_.gallery-media]:after:absolute [&_.gallery-media]:after:inset-0 [&_.gallery-media]:hover:after:backdrop-blur-[0.04rem]"
]
end
defp animation("backdrop", "medium") do
[
"[&_.gallery-media]:hover:after:bg-natural-light/30 dark:[&_.gallery-media]:hover:after:bg-natural-dark/30",
"[&_.gallery-media]:after:absolute [&_.gallery-media]:after:inset-0 [&_.gallery-media]:hover:after:backdrop-blur-[0.06rem]"
]
end
defp animation("backdrop", "large") do
[
"[&_.gallery-media]:hover:after:bg-natural-light/30 dark:[&_.gallery-media]:hover:after:bg-natural-dark/30",
"[&_.gallery-media]:after:absolute [&_.gallery-media]:after:inset-0 [&_.gallery-media]:hover:after:backdrop-blur-[0.08rem]"
]
end
defp animation("backdrop", "extra_large") do
[
"[&_.gallery-media]:hover:after:bg-natural-light/30 dark:[&_.gallery-media]:hover:after:bg-natural-dark/30",
"[&_.gallery-media]:after:absolute [&_.gallery-media]:after:inset-0 [&_.gallery-media]:hover:after:backdrop-blur-[0.1rem]"
]
end
defp animation(params, _) when is_binary(params), do: params
defp grid_cols("one"), do: "grid-cols-1"
defp grid_cols("two"), do: "grid-cols-2"
defp grid_cols("three"), do: "grid-cols-2 md:grid-cols-3"
defp grid_cols("four"), do: "grid-cols-2 md:grid-cols-4"
defp grid_cols("five"), do: "grid-cols-2 md:grid-cols-5"
defp grid_cols("six"), do: "grid-cols-2 md:grid-cols-6"
defp grid_cols("seven"), do: "grid-cols-2 md:grid-cols-7"
defp grid_cols("eight"), do: "grid-cols-2 md:grid-cols-8"
defp grid_cols("nine"), do: "grid-cols-2 md:grid-cols-9"
defp grid_cols("ten"), do: "grid-cols-2 md:grid-cols-10"
defp grid_cols("eleven"), do: "grid-cols-2 md:grid-cols-11"
defp grid_cols("twelve"), do: "grid-cols-2 md:grid-cols-12"
defp grid_cols(params) when is_binary(params), do: params
<%= if is_nil(@type) or "gallery" in @type do %>
defp column_class("one"), do: "columns-1"
defp column_class("two"), do: "columns-2"
defp column_class("three"), do: "columns-2 md:columns-3"
defp column_class("four"), do: "columns-2 md:columns-4"
defp column_class("five"), do: "columns-2 md:columns-5"
defp column_class("six"), do: "columns-2 md:columns-6"
defp column_class("seven"), do: "columns-2 md:columns-7"
defp column_class("eight"), do: "columns-2 md:columns-8"
defp column_class("nine"), do: "columns-2 md:columns-9"
defp column_class("ten"), do: "columns-2 md:columns-10"
defp column_class("eleven"), do: "columns-2 md:columns-11"
defp column_class("twelve"), do: "columns-2 md:columns-12"
defp column_class(params) when is_binary(params), do: params
<% end %>
defp grid_gap("extra_small"), do: "gap-1 [&.gallery-masonry_.gallery-media]:mb-1"
defp grid_gap("small"), do: "gap-2 [&.gallery-masonry_.gallery-media]:mb-2"
defp grid_gap("medium"), do: "gap-3 [&.gallery-masonry_.gallery-media]:mb-3"
defp grid_gap("large"), do: "gap-4 [&.gallery-masonry_.gallery-media]:mb-4"
defp grid_gap("extra_large"), do: "gap-5 [&.gallery-masonry_.gallery-media]:mb-5"
defp grid_gap("double_large"), do: "gap-6 [&.gallery-masonry_.gallery-media]:mb-6"
defp grid_gap("triple_large"), do: "gap-7 [&.gallery-masonry_.gallery-media]:mb-7"
defp grid_gap("quadruple_large"), do: "gap-8 [&.gallery-masonry_.gallery-media]:mb-8"
defp grid_gap(params) when is_binary(params), do: params
end