card can be vertical or horizontal
This commit is contained in:
@@ -307,6 +307,8 @@ confirm-delete = Delete this for good?
|
|||||||
shop-title = Shop
|
shop-title = Shop
|
||||||
shop-subtitle = browse our products.
|
shop-subtitle = browse our products.
|
||||||
shop-empty = There are no products here yet.
|
shop-empty = There are no products here yet.
|
||||||
|
view-grid = Grid view
|
||||||
|
view-list = List view
|
||||||
categories = Categories
|
categories = Categories
|
||||||
all-products = All products
|
all-products = All products
|
||||||
uncategorized = Uncategorized
|
uncategorized = Uncategorized
|
||||||
|
|||||||
@@ -307,6 +307,8 @@ confirm-delete = Naozaj zmazať?
|
|||||||
shop-title = Obchod
|
shop-title = Obchod
|
||||||
shop-subtitle = prezrite si našu ponuku produktov.
|
shop-subtitle = prezrite si našu ponuku produktov.
|
||||||
shop-empty = Zatiaľ tu nie sú žiadne produkty.
|
shop-empty = Zatiaľ tu nie sú žiadne produkty.
|
||||||
|
view-grid = Zobrazenie v mriežke
|
||||||
|
view-list = Zobrazenie v zozname
|
||||||
categories = Kategórie
|
categories = Kategórie
|
||||||
all-products = Všetky produkty
|
all-products = Všetky produkty
|
||||||
uncategorized = Bez kategórie
|
uncategorized = Bez kategórie
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,17 +3,24 @@
|
|||||||
wired to our product data + i18n + htmx add-to-cart + toast. The demo rating
|
wired to our product data + i18n + htmx add-to-cart + toast. The demo rating
|
||||||
stars, hardcoded title/price/description/image and the `max-w-sm` (which fights
|
stars, hardcoded title/price/description/image and the `max-w-sm` (which fights
|
||||||
the shop grid) are dropped; the whole card links to the product page. #}
|
the shop grid) are dropped; the whole card links to the product page. #}
|
||||||
|
{# Layout adapts to the `view` Alpine state set by _product_grid.html:
|
||||||
|
'grid' (default) → vertical card; 'list' → horizontal row. On pages without
|
||||||
|
that state (e.g. home) `view` is undefined, so the grid layout applies. #}
|
||||||
<article
|
<article
|
||||||
class="group flex flex-col overflow-hidden rounded-radius border border-outline bg-surface-alt text-on-surface transition hover:border-primary dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:border-primary-dark">
|
class="group flex overflow-hidden rounded-radius border border-outline bg-surface-alt text-on-surface transition hover:border-primary dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:border-primary-dark"
|
||||||
<a href="/shop/{{ product.slug }}" class="flex flex-1 flex-col">
|
:class="view === 'list' ? 'flex-row flex-wrap' : 'flex-col'">
|
||||||
|
<a href="/shop/{{ product.slug }}" class="flex min-w-0 flex-1"
|
||||||
|
:class="view === 'list' ? 'flex-row' : 'flex-col'">
|
||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<div class="h-44 overflow-hidden bg-surface-alt md:h-64 dark:bg-surface-dark">
|
<div class="overflow-hidden bg-surface-alt dark:bg-surface-dark"
|
||||||
|
:class="view === 'list' ? 'size-28 shrink-0 sm:size-40' : 'h-44 md:h-64'">
|
||||||
{% if product.image %}
|
{% if product.image %}
|
||||||
<img src="/images/{{ product.image }}" alt="{{ product.name }}" class="size-full object-cover transition duration-700 ease-out group-hover:scale-105">
|
<img src="/images/{{ product.image }}" alt="{{ product.name }}" class="size-full object-cover transition duration-700 ease-out group-hover:scale-105">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="flex flex-1 flex-col gap-1 p-6 pb-2">
|
<div class="flex min-w-0 flex-1 flex-col gap-1"
|
||||||
|
:class="view === 'list' ? 'p-4 sm:p-5' : 'p-6 pb-2'">
|
||||||
<!-- Header: Title & Price (stacked so neither overflows the narrow card) -->
|
<!-- Header: Title & Price (stacked so neither overflows the narrow card) -->
|
||||||
<h3 class="break-words text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</h3>
|
<h3 class="break-words text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</h3>
|
||||||
{% if product.on_sale %}
|
{% if product.on_sale %}
|
||||||
@@ -26,7 +33,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex flex-col gap-2 p-6 pt-0">
|
<div class="flex flex-col gap-2"
|
||||||
|
:class="view === 'list' ? 'w-full justify-center p-4 sm:w-56 sm:p-5' : 'p-6 pt-0'">
|
||||||
{% if product.has_options %}
|
{% if product.has_options %}
|
||||||
{# Multiple variants: customer must pick on the product page. #}
|
{# Multiple variants: customer must pick on the product page. #}
|
||||||
{{ ui::button(label=t(key="choose-option", lang=lang | default(value='sk')), href="/shop/" ~ product.slug, extra="w-full") }}
|
{{ ui::button(label=t(key="choose-option", lang=lang | default(value='sk')), href="/shop/" ~ product.slug, extra="w-full") }}
|
||||||
|
|||||||
39
assets/views/shop/_product_grid.html
Normal file
39
assets/views/shop/_product_grid.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{# Product collection with a grid / list view toggle.
|
||||||
|
The chosen view is held in Alpine and persisted to localStorage so it
|
||||||
|
survives navigation; `_card.html` reads the same `view` state to switch
|
||||||
|
its own layout between a vertical card and a horizontal row. #}
|
||||||
|
<div x-data="{ view: localStorage.getItem('shopView') === 'list' ? 'list' : 'grid' }"
|
||||||
|
x-init="$watch('view', v => localStorage.setItem('shopView', v))"
|
||||||
|
class="space-y-4">
|
||||||
|
<!-- View toggle -->
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="inline-flex gap-0.5 rounded-radius border border-outline p-0.5 dark:border-outline-dark" role="group"
|
||||||
|
aria-label="{{ t(key='view-grid', lang=lang | default(value='sk')) }} / {{ t(key='view-list', lang=lang | default(value='sk')) }}">
|
||||||
|
<button type="button" @click="view = 'grid'" :aria-pressed="view === 'grid'"
|
||||||
|
class="inline-flex size-8 items-center justify-center rounded-radius transition"
|
||||||
|
:class="view === 'grid' ? 'bg-primary text-on-primary dark:bg-primary-dark dark:text-on-primary-dark' : 'text-on-surface hover:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark'"
|
||||||
|
aria-label="{{ t(key='view-grid', lang=lang | default(value='sk')) }}"
|
||||||
|
title="{{ t(key='view-grid', lang=lang | default(value='sk')) }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4">
|
||||||
|
<path d="M3 3h6v6H3V3Zm8 0h6v6h-6V3ZM3 11h6v6H3v-6Zm8 0h6v6h-6v-6Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" @click="view = 'list'" :aria-pressed="view === 'list'"
|
||||||
|
class="inline-flex size-8 items-center justify-center rounded-radius transition"
|
||||||
|
:class="view === 'list' ? 'bg-primary text-on-primary dark:bg-primary-dark dark:text-on-primary-dark' : 'text-on-surface hover:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark'"
|
||||||
|
aria-label="{{ t(key='view-list', lang=lang | default(value='sk')) }}"
|
||||||
|
title="{{ t(key='view-list', lang=lang | default(value='sk')) }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4">
|
||||||
|
<path d="M3 4h14v2.5H3V4Zm0 4.75h14v2.5H3v-2.5ZM3 13.5h14V16H3v-2.5Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Products -->
|
||||||
|
<div :class="view === 'list' ? 'flex flex-col gap-5' : 'grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4'">
|
||||||
|
{% for product in products %}
|
||||||
|
{% include "shop/_card.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -29,11 +29,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if products | length > 0 %}
|
{% if products | length > 0 %}
|
||||||
<div class="grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4">
|
{% include "shop/_product_grid.html" %}
|
||||||
{% for product in products %}
|
|
||||||
{% include "shop/_card.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="rounded-radius border border-outline px-6 py-16 text-center text-on-surface/70 dark:border-outline-dark dark:text-on-surface-dark/70">
|
<div class="rounded-radius border border-outline px-6 py-16 text-center text-on-surface/70 dark:border-outline-dark dark:text-on-surface-dark/70">
|
||||||
{{ t(key="shop-empty", lang=lang | default(value='sk')) }}
|
{{ t(key="shop-empty", lang=lang | default(value='sk')) }}
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if products | length > 0 %}
|
{% if products | length > 0 %}
|
||||||
<div class="grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4">
|
{% include "shop/_product_grid.html" %}
|
||||||
{% for product in products %}
|
|
||||||
{% include "shop/_card.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="rounded-radius border border-outline px-6 py-16 text-center text-on-surface/70 dark:border-outline-dark dark:text-on-surface-dark/70">
|
<div class="rounded-radius border border-outline px-6 py-16 text-center text-on-surface/70 dark:border-outline-dark dark:text-on-surface-dark/70">
|
||||||
{{ t(key="shop-empty", lang=lang | default(value='sk')) }}
|
{{ t(key="shop-empty", lang=lang | default(value='sk')) }}
|
||||||
|
|||||||
Reference in New Issue
Block a user