Rename all 1,169 components to path-based names with namespace support

Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:00:12 +00:00
parent de80d921e9
commit b0920a1121
209 changed files with 4620 additions and 4620 deletions

View File

@@ -1,40 +1,40 @@
;; Market card components — pure data, no raw! HTML injection
(defcomp ~market-label-overlay (&key (src :as string))
(defcomp ~cards/label-overlay (&key (src :as string))
(img :src src :alt ""
:class "pointer-events-none absolute inset-0 w-full h-full object-contain object-top"))
(defcomp ~market-card-image (&key (image :as string) (labels :as list?) (brand :as string) (brand-highlight :as string?))
(defcomp ~cards/image (&key (image :as string) (labels :as list?) (brand :as string) (brand-highlight :as string?))
(div :class "w-full aspect-square bg-stone-100 relative"
(figure :class "inline-block w-full h-full"
(div :class "relative w-full h-full"
(img :src image :alt "no image" :class "absolute inset-0 w-full h-full object-contain object-top" :loading "lazy" :decoding "async" :fetchpriority "low")
(when labels (map (lambda (src) (~market-label-overlay :src src)) labels)))
(when labels (map (lambda (src) (~cards/label-overlay :src src)) labels)))
(figcaption :class (str "mt-2 text-sm text-center" brand-highlight " text-stone-600") brand))))
(defcomp ~market-card-no-image (&key (labels :as list?) (brand :as string))
(defcomp ~cards/no-image (&key (labels :as list?) (brand :as string))
(div :class "w-full aspect-square bg-stone-100 relative"
(div :class "p-2 flex flex-col items-center justify-center gap-2 text-red-500 h-full relative"
(div :class "text-stone-400 text-xs" "No image")
(when labels (ul :class "flex flex-row gap-1" (map (lambda (l) (li l)) labels)))
(div :class "text-stone-900 text-center line-clamp-3 break-words [overflow-wrap:anywhere]" brand))))
(defcomp ~market-card-sticker (&key (src :as string) (name :as string) (ring-cls :as string?))
(defcomp ~cards/sticker (&key (src :as string) (name :as string) (ring-cls :as string?))
(img :src src :alt name :class (str "w-6 h-6" ring-cls)))
(defcomp ~market-card-stickers (&key (stickers :as list))
(defcomp ~cards/stickers (&key (stickers :as list))
(div :class "flex flex-row justify-center gap-2 p-2"
(map (lambda (s) (~market-card-sticker :src (get s "src") :name (get s "name") :ring-cls (get s "ring-cls"))) stickers)))
(map (lambda (s) (~cards/sticker :src (get s "src") :name (get s "name") :ring-cls (get s "ring-cls"))) stickers)))
(defcomp ~market-card-highlight (&key (pre :as string) (mid :as string) (post :as string))
(defcomp ~cards/highlight (&key (pre :as string) (mid :as string) (post :as string))
(<> pre (mark mid) post))
;; Price — delegates to shared ~price
(defcomp ~market-card-price (&key (special-price :as string?) (regular-price :as string?))
(~price :special-price special-price :regular-price regular-price))
;; Price — delegates to shared ~shared:misc/price
(defcomp ~cards/price (&key (special-price :as string?) (regular-price :as string?))
(~shared:misc/price :special-price special-price :regular-price regular-price))
;; Main product card — accepts pure data, composes sub-components
(defcomp ~market-product-card (&key (href :as string) (hx-select :as string)
(defcomp ~cards/product-card (&key (href :as string) (hx-select :as string)
(has-like :as boolean) (liked :as boolean?) (slug :as string) (csrf :as string) (like-action :as string?)
(image :as string?) (labels :as list?) (brand :as string) (brand-highlight :as string?)
(special-price :as string?) (regular-price :as string?)
@@ -43,29 +43,29 @@
(title :as string) (has-highlight :as boolean) (search-pre :as string?) (search-mid :as string?) (search-post :as string?))
(div :class "flex flex-col rounded-xl bg-white shadow hover:shadow-md transition overflow-hidden relative"
(when has-like
(~market-like-button :form-id (str "like-" slug) :action like-action :slug slug :csrf csrf
(~cards/like-button :form-id (str "like-" slug) :action like-action :slug slug :csrf csrf
:icon-cls (if liked "fa-solid fa-heart text-red-500" "fa-regular fa-heart text-stone-400")))
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
(if image
(~market-card-image :image image :labels labels :brand brand :brand-highlight brand-highlight)
(~market-card-no-image :labels labels :brand brand))
(~market-card-price :special-price special-price :regular-price regular-price))
(~cards/image :image image :labels labels :brand brand :brand-highlight brand-highlight)
(~cards/no-image :labels labels :brand brand))
(~cards/price :special-price special-price :regular-price regular-price))
(div :class "flex justify-center"
(if quantity
(~market-cart-add-quantity :cart-id (str "cart-" slug) :action cart-action :csrf csrf
(~cart/add-quantity :cart-id (str "cart-" slug) :action cart-action :csrf csrf
:minus-val (str (- quantity 1)) :plus-val (str (+ quantity 1))
:quantity (str quantity) :cart-href cart-href)
(~market-cart-add-empty :cart-id (str "cart-" slug) :action cart-action :csrf csrf)))
(~cart/add-empty :cart-id (str "cart-" slug) :action cart-action :csrf csrf)))
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
(when stickers (~market-card-stickers :stickers stickers))
(when stickers (~cards/stickers :stickers stickers))
(div :class "text-sm font-medium text-stone-800 text-center line-clamp-3 break-words [overflow-wrap:anywhere]"
(if has-highlight
(~market-card-highlight :pre search-pre :mid search-mid :post search-post)
(~cards/highlight :pre search-pre :mid search-mid :post search-post)
title)))))
(defcomp ~market-like-button (&key (form-id :as string) (action :as string) (slug :as string) (csrf :as string) (icon-cls :as string))
(defcomp ~cards/like-button (&key (form-id :as string) (action :as string) (slug :as string) (csrf :as string) (icon-cls :as string))
(div :class "absolute top-2 right-2 z-10 text-6xl md:text-xl"
(form :id form-id :action action :method "post"
:sx-post action :sx-target (str "#like-" slug) :sx-swap "outerHTML"
@@ -73,22 +73,22 @@
(button :type "submit" :class "cursor-pointer"
(i :class icon-cls :aria-hidden "true")))))
(defcomp ~market-market-card-title-link (&key (href :as string) (name :as string))
(defcomp ~cards/market-card-title-link (&key (href :as string) (name :as string))
(a :href href :class "hover:text-emerald-700"
(h2 :class "text-lg font-semibold text-stone-900" name)))
(defcomp ~market-market-card-title (&key (name :as string))
(defcomp ~cards/market-card-title (&key (name :as string))
(h2 :class "text-lg font-semibold text-stone-900" name))
(defcomp ~market-market-card-desc (&key (description :as string))
(defcomp ~cards/market-card-desc (&key (description :as string))
(p :class "text-sm text-stone-600 mt-1 line-clamp-2" description))
(defcomp ~market-market-card-badge (&key (href :as string) (title :as string))
(defcomp ~cards/market-card-badge (&key (href :as string) (title :as string))
(div :class "flex flex-wrap items-center gap-1.5 mt-3"
(a :href href :class "inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 hover:bg-amber-200"
title)))
(defcomp ~market-market-card (&key (title-content :as list?) (desc-content :as list?) (badge-content :as list?) (title :as string?) (desc :as string?) (badge :as string?))
(defcomp ~cards/market-card (&key (title-content :as list?) (desc-content :as list?) (badge-content :as list?) (title :as string?) (desc :as string?) (badge :as string?))
(article :class "rounded-xl bg-white shadow-sm border border-stone-200 p-5 flex flex-col justify-between hover:border-stone-400 transition-colors"
(div
(if title-content title-content (when title title))
@@ -101,11 +101,11 @@
;; ---------------------------------------------------------------------------
;; Product cards grid with infinite scroll sentinels
(defcomp ~market-product-cards-content (&key (products :as list) (page :as number) (total-pages :as number) (next-url :as string)
(defcomp ~cards/product-cards-content (&key (products :as list) (page :as number) (total-pages :as number) (next-url :as string)
(mobile-sentinel-hs :as string?) (desktop-sentinel-hs :as string?))
(<>
(map (lambda (p)
(~market-product-card
(~cards/product-card
:href (get p "href") :hx-select (get p "hx-select") :slug (get p "slug")
:image (get p "image") :brand (get p "brand") :brand-highlight (get p "brand-highlight")
:special-price (get p "special-price") :regular-price (get p "regular-price")
@@ -119,39 +119,39 @@
:search-post (get p "search-post")))
products)
(if (< page total-pages)
(<> (~sentinel-mobile :id (str "sentinel-" page "-m") :next-url next-url
(<> (~shared:misc/sentinel-mobile :id (str "sentinel-" page "-m") :next-url next-url
:hyperscript mobile-sentinel-hs)
(~sentinel-desktop :id (str "sentinel-" page "-d") :next-url next-url
(~shared:misc/sentinel-desktop :id (str "sentinel-" page "-d") :next-url next-url
:hyperscript desktop-sentinel-hs))
(~end-of-results))))
(~shared:misc/end-of-results))))
;; Single market card from data (handles conditional title/desc/badge)
(defcomp ~market-card-from-data (&key (name :as string) (description :as string?) (href :as string?) (show-badge :as boolean) (badge-href :as string?) (badge-title :as string?))
(~market-market-card
(defcomp ~cards/from-data (&key (name :as string) (description :as string?) (href :as string?) (show-badge :as boolean) (badge-href :as string?) (badge-title :as string?))
(~cards/market-card
:title-content (if href
(~market-market-card-title-link :href href :name name)
(~market-market-card-title :name name))
(~cards/market-card-title-link :href href :name name)
(~cards/market-card-title :name name))
:desc-content (when description
(~market-market-card-desc :description description))
(~cards/market-card-desc :description description))
:badge-content (when (and show-badge badge-title)
(~market-market-card-badge :href badge-href :title badge-title))))
(~cards/market-card-badge :href badge-href :title badge-title))))
;; Market cards list with infinite scroll sentinel
(defcomp ~market-cards-content (&key (markets :as list) (page :as number) (has-more :as boolean) (next-url :as string))
(defcomp ~cards/content (&key (markets :as list) (page :as number) (has-more :as boolean) (next-url :as string))
(<>
(map (lambda (m)
(~market-card-from-data
(~cards/from-data
:name (get m "name") :description (get m "description")
:href (get m "href") :show-badge (get m "show-badge")
:badge-href (get m "badge-href") :badge-title (get m "badge-title")))
markets)
(when has-more
(~sentinel-simple :id (str "sentinel-" page) :next-url next-url))))
(~shared:misc/sentinel-simple :id (str "sentinel-" page) :next-url next-url))))
;; Market landing page content from data
(defcomp ~market-landing-from-data (&key (excerpt :as string?) (feature-image :as string?) (html :as string?))
(~market-landing-content :inner
(<> (when excerpt (~market-landing-excerpt :text excerpt))
(when feature-image (~market-landing-image :src feature-image))
(when html (~market-landing-html :html html)))))
(defcomp ~cards/landing-from-data (&key (excerpt :as string?) (feature-image :as string?) (html :as string?))
(~detail/landing-content :inner
(<> (when excerpt (~detail/landing-excerpt :text excerpt))
(when feature-image (~detail/landing-image :src feature-image))
(when html (~detail/landing-html :html html)))))

View File

@@ -1,6 +1,6 @@
;; Market cart components
(defcomp ~market-cart-add-empty (&key cart-id action csrf)
(defcomp ~cart/add-empty (&key cart-id action csrf)
(div :id cart-id
(form :action action :method "post" :sx-post action :sx-target "#cart-mini" :sx-swap "outerHTML" :class "rounded flex items-center"
(input :type "hidden" :name "csrf_token" :value csrf)
@@ -9,7 +9,7 @@
(span :class "relative inline-flex items-center justify-center"
(i :class "fa fa-cart-plus text-4xl" :aria-hidden "true"))))))
(defcomp ~market-cart-add-quantity (&key cart-id action csrf minus-val plus-val quantity cart-href)
(defcomp ~cart/add-quantity (&key cart-id action csrf minus-val plus-val quantity cart-href)
(div :id cart-id
(div :class "rounded flex items-center gap-2"
(form :action action :method "post" :sx-post action :sx-target "#cart-mini" :sx-swap "outerHTML"
@@ -26,7 +26,7 @@
(input :type "hidden" :name "count" :value plus-val)
(button :type "submit" :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl" "+")))))
(defcomp ~market-cart-mini-count (&key href count)
(defcomp ~cart/mini-count (&key href count)
(div :id "cart-mini" :sx-swap-oob "outerHTML"
(a :href href :class "relative inline-flex items-center justify-center"
(span :class "relative inline-flex items-center justify-center"
@@ -35,25 +35,25 @@
(span :class "flex items-center justify-center bg-emerald-500 text-white rounded-full min-w-[1.25rem] h-5 text-xs font-bold px-1"
count))))))
(defcomp ~market-cart-mini-empty (&key href logo)
(defcomp ~cart/mini-empty (&key href logo)
(div :id "cart-mini" :sx-swap-oob "outerHTML"
(a :href href :class "relative inline-flex items-center justify-center"
(img :src logo :class "h-8 w-8 rounded-full object-cover border border-stone-300" :alt ""))))
(defcomp ~market-cart-add-oob (&key id content inner)
(defcomp ~cart/add-oob (&key id content inner)
(div :id id :sx-swap-oob "outerHTML"
(if content content (when inner inner))))
;; Cart added response — composes cart mini + add/remove OOB in sx
(defcomp ~market-cart-added-response (&key has-count cart-href blog-href logo
(defcomp ~cart/added-response (&key has-count cart-href blog-href logo
slug action csrf quantity minus-val plus-val)
(<>
(if has-count
(~market-cart-mini-count :href cart-href :count (str has-count))
(~market-cart-mini-empty :href blog-href :logo logo))
(~market-cart-add-oob :id (str "cart-add-" slug)
(~cart/mini-count :href cart-href :count (str has-count))
(~cart/mini-empty :href blog-href :logo logo))
(~cart/add-oob :id (str "cart-add-" slug)
:inner (if (= (or quantity "0") "0")
(~market-cart-add-empty :cart-id (str "cart-" slug) :action action :csrf csrf)
(~market-cart-add-quantity :cart-id (str "cart-" slug) :action action :csrf csrf
(~cart/add-empty :cart-id (str "cart-" slug) :action action :csrf csrf)
(~cart/add-quantity :cart-id (str "cart-" slug) :action action :csrf csrf
:minus-val minus-val :plus-val plus-val
:quantity quantity :cart-href cart-href)))))

View File

@@ -1,6 +1,6 @@
;; Market product detail components
(defcomp ~market-detail-gallery-inner (&key (like :as list?) (image :as string) (alt :as string) (labels :as list?) (brand :as string))
(defcomp ~detail/gallery-inner (&key (like :as list?) (image :as string) (alt :as string) (labels :as list?) (brand :as string))
(<> like
(figure :class "inline-block"
(div :class "relative w-full aspect-square"
@@ -9,7 +9,7 @@
labels)
(figcaption :class "mt-2 text-sm text-stone-600 text-center" brand))))
(defcomp ~market-detail-nav-buttons ()
(defcomp ~detail/nav-buttons ()
(<>
(button :type "button" :data-prev ""
:class "absolute left-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg text-3xl md:text-4xl"
@@ -18,79 +18,79 @@
:class "absolute right-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg text-3xl md:text-4xl"
:title "Next" "\u203a")))
(defcomp ~market-detail-gallery (&key (inner :as list) (nav :as list?))
(defcomp ~detail/gallery (&key (inner :as list) (nav :as list?))
(div :class "relative rounded-xl overflow-hidden bg-stone-100"
inner nav))
(defcomp ~market-detail-thumb (&key (title :as string) (src :as string) (alt :as string))
(defcomp ~detail/thumb (&key (title :as string) (src :as string) (alt :as string))
(<> (button :type "button" :data-thumb ""
:class "shrink-0 rounded-lg overflow-hidden bg-stone-100 hover:opacity-90 ring-offset-2"
:title title
(img :src src :class "h-16 w-16 object-contain" :alt alt :loading "lazy" :decoding "async"))
(span :data-image-src src :class "hidden")))
(defcomp ~market-detail-thumbs (&key (thumbs :as list))
(defcomp ~detail/thumbs (&key (thumbs :as list))
(div :class "flex flex-row justify-center"
(div :class "mt-3 flex gap-2 overflow-x-auto no-scrollbar" thumbs)))
(defcomp ~market-detail-no-image (&key (like :as list?))
(defcomp ~detail/no-image (&key (like :as list?))
(div :class "relative aspect-square bg-stone-100 rounded-xl flex items-center justify-center text-stone-400"
like "No image"))
(defcomp ~market-detail-sticker (&key (src :as string) (name :as string))
(defcomp ~detail/sticker (&key (src :as string) (name :as string))
(img :src src :alt name :class "w-10 h-10"))
(defcomp ~market-detail-stickers (&key (items :as list))
(defcomp ~detail/stickers (&key (items :as list))
(div :class "p-2 flex flex-row justify-center gap-2" items))
(defcomp ~market-detail-unit-price (&key (price :as string))
(defcomp ~detail/unit-price (&key (price :as string))
(div (str "Unit price: " price)))
(defcomp ~market-detail-case-size (&key (size :as string))
(defcomp ~detail/case-size (&key (size :as string))
(div (str "Case size: " size)))
(defcomp ~market-detail-extras (&key (inner :as list))
(defcomp ~detail/extras (&key (inner :as list))
(div :class "mt-2 space-y-1 text-sm text-stone-600" inner))
(defcomp ~market-detail-desc-short (&key (text :as string))
(defcomp ~detail/desc-short (&key (text :as string))
(p :class "leading-relaxed text-lg" text))
(defcomp ~market-detail-desc-html (&key (html :as string))
(defcomp ~detail/desc-html (&key (html :as string))
(div :class "max-w-none text-sm leading-relaxed" (~rich-text :html html)))
(defcomp ~market-detail-desc-wrapper (&key (inner :as list))
(defcomp ~detail/desc-wrapper (&key (inner :as list))
(div :class "mt-4 text-stone-800 space-y-3" inner))
(defcomp ~market-detail-section (&key (title :as string) (html :as string))
(defcomp ~detail/section (&key (title :as string) (html :as string))
(details :class "group rounded-xl border bg-white shadow-sm open:shadow p-0"
(summary :class "cursor-pointer select-none px-4 py-3 flex items-center justify-between"
(span :class "font-medium" title)
(span :class "ml-2 text-xl transition-transform group-open:rotate-180" "\u2304"))
(div :class "px-4 pb-4 max-w-none text-sm leading-relaxed" (~rich-text :html html))))
(defcomp ~market-detail-sections (&key (items :as list))
(defcomp ~detail/sections (&key (items :as list))
(div :class "mt-8 space-y-3" items))
(defcomp ~market-detail-right-col (&key (inner :as list))
(defcomp ~detail/right-col (&key (inner :as list))
(div :class "md:col-span-3" inner))
(defcomp ~market-detail-layout (&key (gallery :as list) (stickers :as list?) (details :as list))
(defcomp ~detail/layout (&key (gallery :as list) (stickers :as list?) (details :as list))
(<> (div :class "mt-3 grid grid-cols-1 md:grid-cols-5 gap-6" :data-gallery-root ""
(div :class "md:col-span-2" gallery stickers)
details)
(div :class "pb-8")))
(defcomp ~market-landing-excerpt (&key (text :as string))
(defcomp ~detail/landing-excerpt (&key (text :as string))
(div :class "w-full text-center italic text-3xl p-2" text))
(defcomp ~market-landing-image (&key (src :as string))
(defcomp ~detail/landing-image (&key (src :as string))
(div :class "mb-3 flex justify-center"
(img :src src :alt "" :class "rounded-lg w-full md:w-3/4 object-cover")))
(defcomp ~market-landing-html (&key (html :as string))
(defcomp ~detail/landing-html (&key (html :as string))
(div :class "blog-content p-2" (~rich-text :html html)))
(defcomp ~market-landing-content (&key (inner :as list))
(defcomp ~detail/landing-content (&key (inner :as list))
(<> (article :class "relative w-full" inner) (div :class "pb-8")))
@@ -99,64 +99,64 @@
;; ---------------------------------------------------------------------------
;; Gallery section from pre-computed data
(defcomp ~market-detail-gallery-from-data (&key (images :as list?) (labels :as list?) (brand :as string) (like-data :as dict?) (has-nav-buttons :as boolean) (thumbs :as list?))
(defcomp ~detail/gallery-from-data (&key (images :as list?) (labels :as list?) (brand :as string) (like-data :as dict?) (has-nav-buttons :as boolean) (thumbs :as list?))
(let ((like-sx (when like-data
(~market-like-button
(~cards/like-button
:form-id (get like-data "form-id") :action (get like-data "action")
:slug (get like-data "slug") :csrf (get like-data "csrf")
:icon-cls (get like-data "icon-cls")))))
(if images
(<>
(~market-detail-gallery
:inner (~market-detail-gallery-inner
(~detail/gallery
:inner (~detail/gallery-inner
:like like-sx
:image (get (first images) "src") :alt (get (first images) "alt")
:labels (when labels
(<> (map (lambda (src) (~market-label-overlay :src src)) labels)))
(<> (map (lambda (src) (~cards/label-overlay :src src)) labels)))
:brand brand)
:nav (when has-nav-buttons (~market-detail-nav-buttons)))
:nav (when has-nav-buttons (~detail/nav-buttons)))
(when thumbs
(~market-detail-thumbs :thumbs
(~detail/thumbs :thumbs
(<> (map (lambda (t)
(~market-detail-thumb
(~detail/thumb
:title (get t "title") :src (get t "src") :alt (get t "alt")))
thumbs)))))
(~market-detail-no-image :like like-sx))))
(~detail/no-image :like like-sx))))
;; Right column details from data
(defcomp ~market-detail-info-from-data (&key (extras :as list?) (desc-short :as string?) (desc-html :as string?) (sections :as list?))
(~market-detail-right-col :inner
(defcomp ~detail/info-from-data (&key (extras :as list?) (desc-short :as string?) (desc-html :as string?) (sections :as list?))
(~detail/right-col :inner
(<>
(when extras
(~market-detail-extras :inner
(~detail/extras :inner
(<> (map (lambda (e)
(if (= (get e "type") "unit-price")
(~market-detail-unit-price :price (get e "value"))
(~market-detail-case-size :size (get e "value"))))
(~detail/unit-price :price (get e "value"))
(~detail/case-size :size (get e "value"))))
extras))))
(when (or desc-short desc-html)
(~market-detail-desc-wrapper :inner
(<> (when desc-short (~market-detail-desc-short :text desc-short))
(when desc-html (~market-detail-desc-html :html desc-html)))))
(~detail/desc-wrapper :inner
(<> (when desc-short (~detail/desc-short :text desc-short))
(when desc-html (~detail/desc-html :html desc-html)))))
(when sections
(~market-detail-sections :items
(~detail/sections :items
(<> (map (lambda (s)
(~market-detail-section :title (get s "title") :html (get s "html")))
(~detail/section :title (get s "title") :html (get s "html")))
sections)))))))
;; Full product detail layout from data
(defcomp ~market-product-detail-from-data (&key (images :as list?) (labels :as list?) (brand :as string) (like-data :as dict?)
(defcomp ~detail/product-detail-from-data (&key (images :as list?) (labels :as list?) (brand :as string) (like-data :as dict?)
(has-nav-buttons :as boolean) (thumbs :as list?) (sticker-items :as list?)
(extras :as list?) (desc-short :as string?) (desc-html :as string?) (sections :as list?))
(~market-detail-layout
:gallery (~market-detail-gallery-from-data
(~detail/layout
:gallery (~detail/gallery-from-data
:images images :labels labels :brand brand :like-data like-data
:has-nav-buttons has-nav-buttons :thumbs thumbs)
:stickers (when sticker-items
(~market-detail-stickers :items
(~detail/stickers :items
(<> (map (lambda (s)
(~market-detail-sticker :src (get s "src") :name (get s "name")))
(~detail/sticker :src (get s "src") :name (get s "name")))
sticker-items))))
:details (~market-detail-info-from-data
:details (~detail/info-from-data
:extras extras :desc-short desc-short :desc-html desc-html
:sections sections)))

View File

@@ -1,73 +1,73 @@
;; Market filter components
(defcomp ~market-filter-sort-item (&key href hx-select ring-cls src label)
(defcomp ~filters/sort-item (&key href hx-select ring-cls src label)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class (str "flex flex-col items-center gap-1 p-1 cursor-pointer" ring-cls)
(img :src src :alt label :class "w-10 h-10")
(span :class "text-xs" label)))
(defcomp ~market-filter-sort-row (&key items)
(defcomp ~filters/sort-row (&key items)
(div :class "flex flex-row gap-2 justify-center p-1"
items))
(defcomp ~market-filter-like (&key href hx-select icon-cls size-cls)
(defcomp ~filters/like (&key href hx-select icon-cls size-cls)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class "flex flex-col items-center gap-1 p-1 cursor-pointer"
(i :aria-hidden "true" :class (str icon-cls " " size-cls " leading-none"))))
(defcomp ~market-filter-label-item (&key href hx-select ring-cls src name)
(defcomp ~filters/label-item (&key href hx-select ring-cls src name)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class (str "flex flex-col items-center gap-1 p-1 cursor-pointer" ring-cls)
(img :src src :alt name :class "w-10 h-10")))
(defcomp ~market-filter-sticker-item (&key href hx-select ring-cls src name count-cls count)
(defcomp ~filters/sticker-item (&key href hx-select ring-cls src name count-cls count)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class (str "flex flex-col items-center gap-1 p-1 cursor-pointer" ring-cls)
(img :src src :alt name :class "w-6 h-6")
(span :class count-cls count)))
(defcomp ~market-filter-stickers-row (&key items)
(defcomp ~filters/stickers-row (&key items)
(div :class "flex flex-wrap gap-2 justify-center p-1"
items))
(defcomp ~market-filter-brand-item (&key href hx-select bg-cls name-cls name count)
(defcomp ~filters/brand-item (&key href hx-select bg-cls name-cls name count)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class (str "flex flex-row items-center gap-2 px-2 py-1 rounded hover:bg-stone-100" bg-cls)
(div :class name-cls name) (div :class name-cls count)))
(defcomp ~market-filter-brands-panel (&key items)
(defcomp ~filters/brands-panel (&key items)
(div :class "space-y-1 p-2"
items))
(defcomp ~market-filter-category-label (&key label)
(defcomp ~filters/category-label (&key label)
(div :class "mb-4" (div :class "text-2xl uppercase tracking-wide text-black-500" label)))
(defcomp ~market-filter-like-labels-nav (&key content inner)
(defcomp ~filters/like-labels-nav (&key content inner)
(nav :aria-label "like" :class "flex flex-row justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1"
(if content content (when inner inner))))
(defcomp ~market-desktop-category-summary (&key content inner)
(defcomp ~filters/desktop-category-summary (&key content inner)
(div :id "category-summary-desktop" :hxx-swap-oob "outerHTML"
(if content content (when inner inner))))
(defcomp ~market-desktop-brand-summary (&key inner)
(defcomp ~filters/desktop-brand-summary (&key inner)
(div :id "filter-summary-desktop" :hxx-swap-oob "outerHTML" inner))
(defcomp ~market-filter-subcategory-item (&key href hx-select active-cls name)
(defcomp ~filters/subcategory-item (&key href hx-select active-cls name)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class (str "block px-2 py-1 rounded hover:bg-stone-100" active-cls)
name))
(defcomp ~market-filter-subcategory-panel (&key items)
(defcomp ~filters/subcategory-panel (&key items)
(div :class "mt-4 space-y-1" items))
(defcomp ~market-mobile-clear-filters (&key href hx-select)
(defcomp ~filters/mobile-clear-filters (&key href hx-select)
(div :class "flex flex-row justify-center"
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
@@ -75,10 +75,10 @@
:class "flex flex-col items-center justify-start p-1 rounded bg-stone-200 text-black cursor-pointer"
(span :class "mt-1 leading-none tabular-nums" "clear filters"))))
(defcomp ~market-mobile-like-labels-row (&key inner)
(defcomp ~filters/mobile-like-labels-row (&key inner)
(div :class "flex flex-row gap-2 justify-center items-center" inner))
(defcomp ~market-mobile-filter-summary (&key search-bar chips filter)
(defcomp ~filters/mobile-filter-summary (&key search-bar chips filter)
(details :class "md:hidden group" :id "/filter"
(summary :class "cursor-pointer select-none" :id "filter-summary-mobile"
search-bar
@@ -87,40 +87,40 @@
(div :id "filter-details-mobile" :style "display:contents"
filter)))
(defcomp ~market-mobile-chips-row (&key inner)
(defcomp ~filters/mobile-chips-row (&key inner)
(div :class "flex flex-row items-start gap-2" inner))
(defcomp ~market-mobile-chip-sort (&key src label)
(defcomp ~filters/mobile-chip-sort (&key src label)
(ul :class "relative inline-flex items-center justify-center gap-2"
(li :role "listitem" (img :src src :alt label :class "w-10 h-10"))))
(defcomp ~market-mobile-chip-liked-icon ()
(defcomp ~filters/mobile-chip-liked-icon ()
(i :aria-hidden "true" :class "fa-solid fa-heart text-red-500 text-[40px] leading-none"))
(defcomp ~market-mobile-chip-count (&key cls count)
(defcomp ~filters/mobile-chip-count (&key cls count)
(div :class (str cls " mt-1 leading-none tabular-nums") count))
(defcomp ~market-mobile-chip-liked (&key inner)
(defcomp ~filters/mobile-chip-liked (&key inner)
(div :class "flex flex-col items-center gap-1 pb-1" inner))
(defcomp ~market-mobile-chip-image (&key src name)
(defcomp ~filters/mobile-chip-image (&key src name)
(img :src src :alt name :class "w-10 h-10"))
(defcomp ~market-mobile-chip-item (&key inner)
(defcomp ~filters/mobile-chip-item (&key inner)
(li :role "listitem" :class "flex flex-col items-center gap-1 pb-1" inner))
(defcomp ~market-mobile-chip-list (&key items)
(defcomp ~filters/mobile-chip-list (&key items)
(ul :class "relative inline-flex items-center justify-center gap-2" items))
(defcomp ~market-mobile-chip-brand (&key name count)
(defcomp ~filters/mobile-chip-brand (&key name count)
(li :role "listitem" :class "flex flex-row items-center gap-2"
(div :class "text-md" name) (div :class "text-md" count)))
(defcomp ~market-mobile-chip-brand-zero (&key name)
(defcomp ~filters/mobile-chip-brand-zero (&key name)
(li :role "listitem" :class "flex flex-row items-center gap-2"
(div :class "text-md text-red-500" name) (div :class "text-xl text-red-500" "0")))
(defcomp ~market-mobile-chip-brand-list (&key items)
(defcomp ~filters/mobile-chip-brand-list (&key items)
(ul items))
@@ -129,160 +129,160 @@
;; ---------------------------------------------------------------------------
;; Sort option stickers from data
(defcomp ~market-filter-sort-from-data (&key items)
(~market-filter-sort-row :items
(defcomp ~filters/sort-from-data (&key items)
(~filters/sort-row :items
(<> (map (lambda (s)
(~market-filter-sort-item
(~filters/sort-item
:href (get s "href") :hx-select (get s "hx-select")
:ring-cls (get s "ring-cls") :src (get s "src") :label (get s "label")))
items))))
;; Like filter from data
(defcomp ~market-filter-like-from-data (&key href hx-select liked mobile)
(~market-filter-like
(defcomp ~filters/like-from-data (&key href hx-select liked mobile)
(~filters/like
:href href :hx-select hx-select
:icon-cls (if liked "fa-solid fa-heart text-red-500" "fa-regular fa-heart text-stone-400")
:size-cls (if mobile "text-[40px]" "text-2xl")))
;; Label filter items from data
(defcomp ~market-filter-labels-from-data (&key items hx-select)
(defcomp ~filters/labels-from-data (&key items hx-select)
(<> (map (lambda (lb)
(~market-filter-label-item
(~filters/label-item
:href (get lb "href") :hx-select hx-select
:ring-cls (get lb "ring-cls") :src (get lb "src") :name (get lb "name")))
items)))
;; Sticker filter items from data
(defcomp ~market-filter-stickers-from-data (&key items hx-select)
(~market-filter-stickers-row :items
(defcomp ~filters/stickers-from-data (&key items hx-select)
(~filters/stickers-row :items
(<> (map (lambda (st)
(~market-filter-sticker-item
(~filters/sticker-item
:href (get st "href") :hx-select hx-select
:ring-cls (get st "ring-cls") :src (get st "src") :name (get st "name")
:count-cls (get st "count-cls") :count (get st "count")))
items))))
;; Brand filter items from data
(defcomp ~market-filter-brands-from-data (&key items hx-select)
(~market-filter-brands-panel :items
(defcomp ~filters/brands-from-data (&key items hx-select)
(~filters/brands-panel :items
(<> (map (lambda (br)
(~market-filter-brand-item
(~filters/brand-item
:href (get br "href") :hx-select hx-select
:bg-cls (get br "bg-cls") :name-cls (get br "name-cls")
:name (get br "name") :count (get br "count")))
items))))
;; Subcategory selector from data
(defcomp ~market-filter-subcategories-from-data (&key items hx-select all-href current-sub)
(~market-filter-subcategory-panel :items
(defcomp ~filters/subcategories-from-data (&key items hx-select all-href current-sub)
(~filters/subcategory-panel :items
(<>
(~market-filter-subcategory-item
(~filters/subcategory-item
:href all-href :hx-select hx-select
:active-cls (if (not current-sub) " bg-stone-200 font-medium" "")
:name "All")
(map (lambda (sub)
(~market-filter-subcategory-item
(~filters/subcategory-item
:href (get sub "href") :hx-select hx-select
:active-cls (get sub "active-cls") :name (get sub "name")))
items))))
;; Desktop filter panel from data
(defcomp ~market-desktop-filter-from-data (&key search-sx category-label
(defcomp ~filters/desktop-filter-from-data (&key search-sx category-label
sort-data like-data label-data
sticker-data brand-data sub-data hx-select)
(<>
search-sx
(~market-desktop-category-summary :inner
(~filters/desktop-category-summary :inner
(<>
(~market-filter-category-label :label category-label)
(when sort-data (~market-filter-sort-from-data :items sort-data))
(~market-filter-like-labels-nav :inner
(~filters/category-label :label category-label)
(when sort-data (~filters/sort-from-data :items sort-data))
(~filters/like-labels-nav :inner
(<>
(~market-filter-like-from-data
(~filters/like-from-data
:href (get like-data "href") :hx-select hx-select
:liked (get like-data "liked") :mobile false)
(when label-data
(~market-filter-labels-from-data :items label-data :hx-select hx-select))))
(~filters/labels-from-data :items label-data :hx-select hx-select))))
(when sticker-data
(~market-filter-stickers-from-data :items sticker-data :hx-select hx-select))
(~filters/stickers-from-data :items sticker-data :hx-select hx-select))
(when sub-data
(~market-filter-subcategories-from-data
(~filters/subcategories-from-data
:items (get sub-data "items") :hx-select hx-select
:all-href (get sub-data "all-href")
:current-sub (get sub-data "current-sub")))))
(~market-desktop-brand-summary
(~filters/desktop-brand-summary
:inner (when brand-data
(~market-filter-brands-from-data :items brand-data :hx-select hx-select)))))
(~filters/brands-from-data :items brand-data :hx-select hx-select)))))
;; Mobile filter chips from active filter data
(defcomp ~market-mobile-chips-from-data (&key sort-chip liked-chip label-chips sticker-chips brand-chips)
(~market-mobile-chips-row :inner
(defcomp ~filters/mobile-chips-from-data (&key sort-chip liked-chip label-chips sticker-chips brand-chips)
(~filters/mobile-chips-row :inner
(<>
(when sort-chip
(~market-mobile-chip-sort :src (get sort-chip "src") :label (get sort-chip "label")))
(~filters/mobile-chip-sort :src (get sort-chip "src") :label (get sort-chip "label")))
(when liked-chip
(~market-mobile-chip-liked :inner
(~filters/mobile-chip-liked :inner
(<>
(~market-mobile-chip-liked-icon)
(~filters/mobile-chip-liked-icon)
(when (get liked-chip "count")
(~market-mobile-chip-count
(~filters/mobile-chip-count
:cls (get liked-chip "count-cls") :count (get liked-chip "count"))))))
(when label-chips
(~market-mobile-chip-list :items
(~filters/mobile-chip-list :items
(<> (map (lambda (lc)
(~market-mobile-chip-item :inner
(~filters/mobile-chip-item :inner
(<>
(~market-mobile-chip-image :src (get lc "src") :name (get lc "name"))
(~filters/mobile-chip-image :src (get lc "src") :name (get lc "name"))
(when (get lc "count")
(~market-mobile-chip-count :cls (get lc "count-cls") :count (get lc "count"))))))
(~filters/mobile-chip-count :cls (get lc "count-cls") :count (get lc "count"))))))
label-chips))))
(when sticker-chips
(~market-mobile-chip-list :items
(~filters/mobile-chip-list :items
(<> (map (lambda (sc)
(~market-mobile-chip-item :inner
(~filters/mobile-chip-item :inner
(<>
(~market-mobile-chip-image :src (get sc "src") :name (get sc "name"))
(~filters/mobile-chip-image :src (get sc "src") :name (get sc "name"))
(when (get sc "count")
(~market-mobile-chip-count :cls (get sc "count-cls") :count (get sc "count"))))))
(~filters/mobile-chip-count :cls (get sc "count-cls") :count (get sc "count"))))))
sticker-chips))))
(when brand-chips
(~market-mobile-chip-brand-list :items
(~filters/mobile-chip-brand-list :items
(<> (map (lambda (bc)
(if (get bc "has-count")
(~market-mobile-chip-brand :name (get bc "name") :count (get bc "count"))
(~market-mobile-chip-brand-zero :name (get bc "name"))))
(~filters/mobile-chip-brand :name (get bc "name") :count (get bc "count"))
(~filters/mobile-chip-brand-zero :name (get bc "name"))))
brand-chips)))))))
;; Mobile filter content (expanded panel) from data
(defcomp ~market-mobile-filter-content-from-data (&key sort-data like-data label-data
(defcomp ~filters/mobile-filter-content-from-data (&key sort-data like-data label-data
sticker-data brand-data clear-href hx-select)
(<>
(when sort-data (~market-filter-sort-from-data :items sort-data))
(when sort-data (~filters/sort-from-data :items sort-data))
(when clear-href
(~market-mobile-clear-filters :href clear-href :hx-select hx-select))
(~market-mobile-like-labels-row :inner
(~filters/mobile-clear-filters :href clear-href :hx-select hx-select))
(~filters/mobile-like-labels-row :inner
(<>
(~market-filter-like-from-data
(~filters/like-from-data
:href (get like-data "href") :hx-select hx-select
:liked (get like-data "liked") :mobile true)
(when label-data
(~market-filter-labels-from-data :items label-data :hx-select hx-select))))
(~filters/labels-from-data :items label-data :hx-select hx-select))))
(when sticker-data
(~market-filter-stickers-from-data :items sticker-data :hx-select hx-select))
(~filters/stickers-from-data :items sticker-data :hx-select hx-select))
(when brand-data
(~market-filter-brands-from-data :items brand-data :hx-select hx-select))))
(~filters/brands-from-data :items brand-data :hx-select hx-select))))
;; Composite mobile filter — eliminates SxExpr nesting in Python (M2)
(defcomp ~market-mobile-filter-from-data (&key search-bar
(defcomp ~filters/mobile-filter-from-data (&key search-bar
sort-chip liked-chip label-chips sticker-chips brand-chips
sort-data like-data label-data sticker-data brand-data
clear-href hx-select)
(~market-mobile-filter-summary
(~filters/mobile-filter-summary
:search-bar search-bar
:chips (~market-mobile-chips-from-data
:chips (~filters/mobile-chips-from-data
:sort-chip sort-chip :liked-chip liked-chip
:label-chips label-chips :sticker-chips sticker-chips :brand-chips brand-chips)
:filter (~market-mobile-filter-content-from-data
:filter (~filters/mobile-filter-content-from-data
:sort-data sort-data :like-data like-data
:label-data label-data :sticker-data sticker-data :brand-data brand-data
:clear-href clear-href :hx-select hx-select)))

View File

@@ -1,15 +1,15 @@
;; Market grid and layout components
(defcomp ~market-markets-grid (&key cards)
(defcomp ~grids/markets-grid (&key cards)
(<> (div :class "max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4" cards) (div :class "pb-8")))
(defcomp ~market-product-grid (&key cards)
(defcomp ~grids/product-grid (&key cards)
(<> (div :class "grid grid-cols-1 sm:grid-cols-3 md:grid-cols-6 gap-3" cards) (div :class "pb-8")))
(defcomp ~market-admin-content-wrap (&key inner)
(defcomp ~grids/admin-content-wrap (&key inner)
(div :id "main-panel" inner))
(defcomp ~market-like-toggle-button (&key colour action hx-headers label icon-cls)
(defcomp ~grids/like-toggle-button (&key colour action hx-headers label icon-cls)
(button :class (str "flex items-center gap-1 " colour " hover:text-red-600 transition-colors w-[1em] h-[1em]")
:sx-post action :sx-target "this" :sx-swap "outerHTML" :sx-push-url "false"
:sx-headers hx-headers

View File

@@ -15,7 +15,7 @@
(sel-colours (or (jinja-global "select_colours") "")))
(<> (map (fn (m)
(let ((href (app-url "market" (str "/" slug "/" (get m "slug") "/"))))
(~market-link-nav
(~shared:navigation/market-link-nav
:href href
:name (get m "name")
:nav-class nav-class

View File

@@ -19,7 +19,7 @@
(if (get product "regular_price")
(str (get product "regular_price"))
""))))
(~link-card
(~shared:fragments/link-card
:title (get product "title")
:image (get product "image")
:subtitle subtitle
@@ -35,7 +35,7 @@
(if (get product "regular_price")
(str (get product "regular_price"))
""))))
(~link-card
(~shared:fragments/link-card
:title (get product "title")
:image (get product "image")
:subtitle subtitle

View File

@@ -1,15 +1,15 @@
;; Market header components
(defcomp ~market-shop-label (&key title top-slug sub-div)
(defcomp ~headers/shop-label (&key title top-slug sub-div)
(div :class "font-bold text-xl flex-shrink-0 flex gap-2 items-center"
(div (i :class "fa fa-shop") " " title)
(div :class "flex flex-col md:flex-row md:gap-2 text-xs"
(div top-slug) (when sub-div (div sub-div)))))
(defcomp ~market-product-label (&key title)
(defcomp ~headers/product-label (&key title)
(<> (i :class "fa fa-shopping-bag" :aria-hidden "true") (div title)))
(defcomp ~market-admin-link (&key href hx-select)
(defcomp ~headers/admin-link (&key href hx-select)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:class "px-2 py-1 text-stone-500 hover:text-stone-700"
@@ -21,42 +21,42 @@
;; ---------------------------------------------------------------------------
;; Desktop category nav from pre-computed category data
(defcomp ~market-desktop-nav-from-data (&key categories hx-select select-colours
(defcomp ~headers/desktop-nav-from-data (&key categories hx-select select-colours
all-href all-active admin-href)
(~market-desktop-category-nav
(~navigation/desktop-category-nav
:links (<>
(~market-category-link :href all-href :hx-select hx-select
(~navigation/category-link :href all-href :hx-select hx-select
:active all-active :select-colours select-colours :label "All")
(map (lambda (cat)
(~market-category-link
(~navigation/category-link
:href (get cat "href") :hx-select hx-select
:active (get cat "active") :select-colours select-colours
:label (get cat "label"))) categories))
:admin (when admin-href
(~market-admin-link :href admin-href :hx-select hx-select))))
(~headers/admin-link :href admin-href :hx-select hx-select))))
;; Market-level header row from data
(defcomp ~market-header-from-data (&key market-title top-slug sub-slug link-href
(defcomp ~headers/from-data (&key market-title top-slug sub-slug link-href
categories hx-select select-colours
all-href all-active admin-href oob)
(~menu-row-sx :id "market-row" :level 2
(~shared:layout/menu-row-sx :id "market-row" :level 2
:link-href link-href
:link-label-content (~market-shop-label
:link-label-content (~headers/shop-label
:title market-title :top-slug (or top-slug "") :sub-div sub-slug)
:nav (~market-desktop-nav-from-data
:nav (~headers/desktop-nav-from-data
:categories categories :hx-select hx-select :select-colours select-colours
:all-href all-href :all-active all-active :admin-href admin-href)
:child-id "market-header-child"
:oob oob))
;; Product-level header row from data
(defcomp ~market-product-header-from-data (&key title link-href hx-select
(defcomp ~headers/product-header-from-data (&key title link-href hx-select
price-data admin-href oob)
(~menu-row-sx :id "product-row" :level 3
(~shared:layout/menu-row-sx :id "product-row" :level 3
:link-href link-href
:link-label-content (~market-product-label :title title)
:link-label-content (~headers/product-label :title title)
:nav (<>
(~market-prices-header-from-data
(~prices/header-from-data
:cart-id (get price-data "cart-id")
:cart-action (get price-data "cart-action")
:csrf (get price-data "csrf")
@@ -66,13 +66,13 @@
:rp-val (get price-data "rp-val") :rp-str (get price-data "rp-str")
:rrp-str (get price-data "rrp-str"))
(when admin-href
(~market-admin-link :href admin-href :hx-select hx-select)))
(~headers/admin-link :href admin-href :hx-select hx-select)))
:child-id "product-header-child"
:oob oob))
;; Product admin header row from data
(defcomp ~market-product-admin-header-from-data (&key link-href oob)
(~menu-row-sx :id "product-admin-row" :level 4
(defcomp ~headers/product-admin-header-from-data (&key link-href oob)
(~shared:layout/menu-row-sx :id "product-admin-row" :level 4
:link-href link-href :link-label "admin!!" :icon "fa fa-cog"
:child-id "product-admin-header-child" :oob oob))

View File

@@ -9,13 +9,13 @@
"Market header row using (market-header-ctx)."
(quasiquote
(let ((__mctx (market-header-ctx)))
(~menu-row-sx :id "market-row" :level 2
(~shared:layout/menu-row-sx :id "market-row" :level 2
:link-href (get __mctx "link-href")
:link-label-content (~market-shop-label
:link-label-content (~headers/shop-label
:title (get __mctx "market-title")
:top-slug (get __mctx "top-slug")
:sub-div (get __mctx "sub-slug"))
:nav (~market-desktop-nav-from-data
:nav (~headers/desktop-nav-from-data
:categories (get __mctx "categories")
:hx-select (get __mctx "hx-select")
:select-colours (get __mctx "select-colours")
@@ -29,44 +29,44 @@
;; OOB clear helpers
;; ---------------------------------------------------------------------------
(defcomp ~market-clear-oob ()
(defcomp ~layouts/clear-oob ()
"Clear OOB divs for browse level."
(<>
(~clear-oob-div :id "product-admin-row")
(~clear-oob-div :id "product-admin-header-child")
(~clear-oob-div :id "product-row")
(~clear-oob-div :id "product-header-child")
(~clear-oob-div :id "market-admin-row")
(~clear-oob-div :id "market-admin-header-child")
(~clear-oob-div :id "post-admin-row")
(~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "product-admin-row")
(~shared:layout/clear-oob-div :id "product-admin-header-child")
(~shared:layout/clear-oob-div :id "product-row")
(~shared:layout/clear-oob-div :id "product-header-child")
(~shared:layout/clear-oob-div :id "market-admin-row")
(~shared:layout/clear-oob-div :id "market-admin-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row")
(~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~market-clear-oob-admin ()
(defcomp ~layouts/clear-oob-admin ()
"Clear OOB divs for admin level."
(<>
(~clear-oob-div :id "product-admin-row")
(~clear-oob-div :id "product-admin-header-child")
(~clear-oob-div :id "product-row")
(~clear-oob-div :id "product-header-child")))
(~shared:layout/clear-oob-div :id "product-admin-row")
(~shared:layout/clear-oob-div :id "product-admin-header-child")
(~shared:layout/clear-oob-div :id "product-row")
(~shared:layout/clear-oob-div :id "product-header-child")))
;; ---------------------------------------------------------------------------
;; Browse layout: root + post + market (self-contained)
;; ---------------------------------------------------------------------------
(defcomp ~market-browse-layout-full ()
(defcomp ~layouts/browse-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~market-header-auto nil)))))
(defcomp ~market-browse-layout-oob ()
(defcomp ~layouts/browse-layout-oob ()
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child"
(~shared:layout/oob-header-sx :parent-id "post-header-child"
:row (~market-header-auto nil))
(~market-clear-oob)
(~layouts/clear-oob)
(~root-header-auto true)))
(defcomp ~market-browse-layout-mobile ()
(defcomp ~layouts/browse-layout-mobile ()
(let ((__mctx (market-header-ctx)))
(get __mctx "mobile-nav")))
@@ -74,18 +74,18 @@
;; Market admin layout: root + post + market + post-admin (self-contained)
;; ---------------------------------------------------------------------------
(defcomp ~market-admin-layout-full (&key selected)
(defcomp ~layouts/admin-layout-full (&key selected)
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~market-header-auto nil)
(~post-admin-header-auto nil selected)))))
(defcomp ~market-admin-layout-oob (&key selected)
(defcomp ~layouts/admin-layout-oob (&key selected)
(<> (~market-header-auto true)
(~oob-header-sx :parent-id "market-header-child"
(~shared:layout/oob-header-sx :parent-id "market-header-child"
:row (~post-admin-header-auto nil selected))
(~market-clear-oob-admin)
(~layouts/clear-oob-admin)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
@@ -93,46 +93,46 @@
;; ---------------------------------------------------------------------------
;; Product layout: root + post + market + product
(defcomp ~market-product-layout-full (&key post-header market-header product-header)
(defcomp ~layouts/product-layout-full (&key post-header market-header product-header)
(<> (~root-header-auto)
(~header-child-sx :inner (<> post-header market-header product-header))))
(~shared:layout/header-child-sx :inner (<> post-header market-header product-header))))
;; Product admin layout: root + post + market + product + admin
(defcomp ~market-product-admin-layout-full (&key post-header market-header product-header admin-header)
(defcomp ~layouts/product-admin-layout-full (&key post-header market-header product-header admin-header)
(<> (~root-header-auto)
(~header-child-sx :inner (<> post-header market-header product-header admin-header))))
(~shared:layout/header-child-sx :inner (<> post-header market-header product-header admin-header))))
;; OOB wrappers — compose headers + clear divs in sx (no Python concatenation)
(defcomp ~market-oob-wrap (&key parts)
(defcomp ~layouts/oob-wrap (&key parts)
(<> parts))
(defcomp ~market-clear-product-oob ()
(defcomp ~layouts/clear-product-oob ()
"Clear admin-level OOB divs when rendering product detail."
(<>
(~clear-oob-div :id "product-admin-row")
(~clear-oob-div :id "product-admin-header-child")
(~clear-oob-div :id "market-admin-row")
(~clear-oob-div :id "market-admin-header-child")
(~clear-oob-div :id "post-admin-row")
(~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "product-admin-row")
(~shared:layout/clear-oob-div :id "product-admin-header-child")
(~shared:layout/clear-oob-div :id "market-admin-row")
(~shared:layout/clear-oob-div :id "market-admin-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row")
(~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~market-clear-product-admin-oob ()
(defcomp ~layouts/clear-product-admin-oob ()
"Clear deeper OOB divs when rendering product admin."
(<>
(~clear-oob-div :id "market-admin-row")
(~clear-oob-div :id "market-admin-header-child")
(~clear-oob-div :id "post-admin-row")
(~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "market-admin-row")
(~shared:layout/clear-oob-div :id "market-admin-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row")
(~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~market-product-oob (&key market-header oob-header)
(defcomp ~layouts/product-oob (&key market-header oob-header)
"Product detail OOB: market header + product header + clear deeper."
(<> market-header oob-header (~market-clear-product-oob)))
(<> market-header oob-header (~layouts/clear-product-oob)))
(defcomp ~market-product-admin-oob (&key product-header oob-header)
(defcomp ~layouts/product-admin-oob (&key product-header oob-header)
"Product admin OOB: product header + admin header + clear deeper."
(<> product-header oob-header (~market-clear-product-admin-oob)))
(<> product-header oob-header (~layouts/clear-product-admin-oob)))
;; Content wrappers
(defcomp ~market-content-padded (&key content)
(defcomp ~layouts/content-padded (&key content)
(<> content (div :class "pb-8")))

View File

@@ -1,21 +1,21 @@
;; Market meta/SEO components
(defcomp ~market-meta-title (&key (title :as string))
(defcomp ~meta/title (&key (title :as string))
(title title))
(defcomp ~market-meta-description (&key (description :as string))
(defcomp ~meta/description (&key (description :as string))
(meta :name "description" :content description))
(defcomp ~market-meta-canonical (&key (href :as string))
(defcomp ~meta/canonical (&key (href :as string))
(link :rel "canonical" :href href))
(defcomp ~market-meta-og (&key (property :as string) (content :as string))
(defcomp ~meta/og (&key (property :as string) (content :as string))
(meta :property property :content content))
(defcomp ~market-meta-twitter (&key (name :as string) (content :as string))
(defcomp ~meta/twitter (&key (name :as string) (content :as string))
(meta :name name :content content))
(defcomp ~market-meta-jsonld (&key (json :as string))
(defcomp ~meta/jsonld (&key (json :as string))
(script :type "application/ld+json" (~rich-text :html json)))
@@ -23,30 +23,30 @@
;; Composition: all product meta tags from data
;; ---------------------------------------------------------------------------
(defcomp ~market-product-meta-from-data (&key (title :as string) (description :as string) (canonical :as string?)
(defcomp ~meta/product-meta-from-data (&key (title :as string) (description :as string) (canonical :as string?)
(image-url :as string?)
(site-title :as string) (brand :as string?) (price :as string?) (price-currency :as string?)
(jsonld-json :as string))
(<>
(~market-meta-title :title title)
(~market-meta-description :description description)
(when canonical (~market-meta-canonical :href canonical))
(~meta/title :title title)
(~meta/description :description description)
(when canonical (~meta/canonical :href canonical))
;; OpenGraph
(~market-meta-og :property "og:site_name" :content site-title)
(~market-meta-og :property "og:type" :content "product")
(~market-meta-og :property "og:title" :content title)
(~market-meta-og :property "og:description" :content description)
(when canonical (~market-meta-og :property "og:url" :content canonical))
(when image-url (~market-meta-og :property "og:image" :content image-url))
(~meta/og :property "og:site_name" :content site-title)
(~meta/og :property "og:type" :content "product")
(~meta/og :property "og:title" :content title)
(~meta/og :property "og:description" :content description)
(when canonical (~meta/og :property "og:url" :content canonical))
(when image-url (~meta/og :property "og:image" :content image-url))
(when (and price price-currency)
(<> (~market-meta-og :property "product:price:amount" :content price)
(~market-meta-og :property "product:price:currency" :content price-currency)))
(when brand (~market-meta-og :property "product:brand" :content brand))
(<> (~meta/og :property "product:price:amount" :content price)
(~meta/og :property "product:price:currency" :content price-currency)))
(when brand (~meta/og :property "product:brand" :content brand))
;; Twitter
(~market-meta-twitter :name "twitter:card"
(~meta/twitter :name "twitter:card"
:content (if image-url "summary_large_image" "summary"))
(~market-meta-twitter :name "twitter:title" :content title)
(~market-meta-twitter :name "twitter:description" :content description)
(when image-url (~market-meta-twitter :name "twitter:image" :content image-url))
(~meta/twitter :name "twitter:title" :content title)
(~meta/twitter :name "twitter:description" :content description)
(when image-url (~meta/twitter :name "twitter:image" :content image-url))
;; JSON-LD
(~market-meta-jsonld :json jsonld-json)))
(~meta/jsonld :json jsonld-json)))

View File

@@ -1,6 +1,6 @@
;; Market navigation components
(defcomp ~market-category-link (&key (href :as string) (hx-select :as string) (active :as boolean) (select-colours :as string) (label :as string))
(defcomp ~navigation/category-link (&key (href :as string) (hx-select :as string) (active :as boolean) (select-colours :as string) (label :as string))
(div :class "relative nav-group"
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
@@ -8,27 +8,27 @@
:class (str "block px-2 py-1 rounded text-center whitespace-normal break-words leading-snug bg-stone-200 text-black " select-colours)
label)))
(defcomp ~market-desktop-category-nav (&key (links :as list) (admin :as list?))
(defcomp ~navigation/desktop-category-nav (&key (links :as list) (admin :as list?))
(nav :class "hidden md:flex gap-4 text-sm ml-2 w-full justify-end items-center"
links admin))
(defcomp ~market-mobile-nav-wrapper (&key (items :as list))
(defcomp ~navigation/mobile-nav-wrapper (&key (items :as list))
(div :class "px-4 py-2" (div :class "divide-y" items)))
(defcomp ~market-mobile-all-link (&key (href :as string) (hx-select :as string) (active :as boolean) (select-colours :as string))
(defcomp ~navigation/mobile-all-link (&key (href :as string) (hx-select :as string) (active :as boolean) (select-colours :as string))
(a :role "option" :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
:aria-selected (if active "true" "false")
:class (str "block rounded-lg px-3 py-3 text-base hover:bg-stone-50 " select-colours)
(div :class "prose prose-stone max-w-none" "All")))
(defcomp ~market-mobile-chevron ()
(defcomp ~navigation/mobile-chevron ()
(svg :class "w-4 h-4 shrink-0 transition-transform group-open/cat:rotate-180"
:viewBox "0 0 20 20" :fill "currentColor"
(path :fill-rule "evenodd" :clip-rule "evenodd"
:d "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z")))
(defcomp ~market-mobile-cat-summary (&key (bg-cls :as string) (href :as string) (hx-select :as string) (select-colours :as string) (cat-name :as string) (count-label :as string) (count-str :as string) (chevron :as list))
(defcomp ~navigation/mobile-cat-summary (&key (bg-cls :as string) (href :as string) (hx-select :as string) (select-colours :as string) (cat-name :as string) (count-label :as string) (count-str :as string) (chevron :as list))
(summary :class (str "flex items-center justify-between cursor-pointer select-none block rounded-lg px-3 py-3 text-base hover:bg-stone-50" bg-cls)
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
@@ -37,7 +37,7 @@
(div :aria-label count-label count-str))
chevron))
(defcomp ~market-mobile-sub-link (&key (select-colours :as string) (active :as boolean) (href :as string) (hx-select :as string) (label :as string) (count-label :as string) (count-str :as string))
(defcomp ~navigation/mobile-sub-link (&key (select-colours :as string) (active :as boolean) (href :as string) (hx-select :as string) (label :as string) (count-label :as string) (count-str :as string))
(a :class (str "snap-start px-2 py-3 rounded " select-colours " flex flex-row gap-2")
:aria-selected (if active "true" "false")
:href href :sx-get href :sx-target "#main-panel"
@@ -45,20 +45,20 @@
(div label)
(div :aria-label count-label count-str)))
(defcomp ~market-mobile-subs-panel (&key (links :as list))
(defcomp ~navigation/mobile-subs-panel (&key (links :as list))
(div :class "pb-3 pl-2"
(div :data-peek-viewport "" :data-peek-size-px "18" :data-peek-edge "bottom" :data-peek-mask "true" :class "m-2 bg-stone-100"
(div :data-peek-inner "" :class "grid grid-cols-1 gap-1 snap-y snap-mandatory pr-1" :aria-label "Subcategories"
links))))
(defcomp ~market-mobile-view-all (&key (href :as string) (hx-select :as string))
(defcomp ~navigation/mobile-view-all (&key (href :as string) (hx-select :as string))
(div :class "pb-3 pl-2"
(a :class "px-2 py-1 rounded hover:bg-stone-100 block"
:href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
"View all")))
(defcomp ~market-mobile-cat-details (&key (open :as boolean) (summary :as list) (subs :as list))
(defcomp ~navigation/mobile-cat-details (&key (open :as boolean) (summary :as list) (subs :as list))
(details :class "group/cat py-1" :open open
summary subs))
@@ -67,25 +67,25 @@
;; Composition: mobile nav panel from pre-computed category data
;; ---------------------------------------------------------------------------
(defcomp ~market-mobile-nav-from-data (&key (categories :as list) (all-href :as string) (all-active :as boolean) (hx-select :as string) (select-colours :as string))
(~market-mobile-nav-wrapper :items
(defcomp ~navigation/mobile-nav-from-data (&key (categories :as list) (all-href :as string) (all-active :as boolean) (hx-select :as string) (select-colours :as string))
(~navigation/mobile-nav-wrapper :items
(<>
(~market-mobile-all-link :href all-href :hx-select hx-select
(~navigation/mobile-all-link :href all-href :hx-select hx-select
:active all-active :select-colours select-colours)
(map (lambda (cat)
(~market-mobile-cat-details
(~navigation/mobile-cat-details
:open (get cat "active")
:summary (~market-mobile-cat-summary
:summary (~navigation/mobile-cat-summary
:bg-cls (if (get cat "active") " bg-stone-900 text-white hover:bg-stone-900" "")
:href (get cat "href") :hx-select hx-select
:select-colours select-colours :cat-name (get cat "name")
:count-label (str (get cat "count") " products")
:count-str (str (get cat "count"))
:chevron (~market-mobile-chevron))
:chevron (~navigation/mobile-chevron))
:subs (if (get cat "subs")
(~market-mobile-subs-panel :links
(~navigation/mobile-subs-panel :links
(<> (map (lambda (sub)
(~market-mobile-sub-link
(~navigation/mobile-sub-link
:select-colours select-colours
:active (get sub "active")
:href (get sub "href") :hx-select hx-select
@@ -93,5 +93,5 @@
:count-label (str (get sub "count") " products")
:count-str (str (get sub "count"))))
(get cat "subs"))))
(~market-mobile-view-all :href (get cat "href") :hx-select hx-select))))
(~navigation/mobile-view-all :href (get cat "href") :hx-select hx-select))))
categories))))

View File

@@ -1,36 +1,36 @@
;; Market price display components
(defcomp ~market-price-special (&key (price :as string))
(defcomp ~prices/special (&key (price :as string))
(div :class "text-lg font-semibold text-emerald-700" price))
(defcomp ~market-price-regular-strike (&key (price :as string))
(defcomp ~prices/regular-strike (&key (price :as string))
(div :class "text-sm line-through text-stone-500" price))
(defcomp ~market-price-regular (&key (price :as string))
(defcomp ~prices/regular (&key (price :as string))
(div :class "mt-1 text-lg font-semibold" price))
(defcomp ~market-price-line (&key (inner :as list))
(defcomp ~prices/line (&key (inner :as list))
(div :class "mt-1 flex items-baseline gap-2 justify-center" inner))
(defcomp ~market-header-price-special-label ()
(defcomp ~prices/header-price-special-label ()
(div :class "text-md font-bold text-emerald-700" "Special price"))
(defcomp ~market-header-price-special (&key (price :as string))
(defcomp ~prices/header-price-special (&key (price :as string))
(div :class "text-xl font-semibold text-emerald-700" price))
(defcomp ~market-header-price-strike (&key (price :as string))
(defcomp ~prices/header-price-strike (&key (price :as string))
(div :class "text-base text-md line-through text-stone-500" price))
(defcomp ~market-header-price-regular-label ()
(defcomp ~prices/header-price-regular-label ()
(div :class "hidden md:block text-xl font-bold" "Our price"))
(defcomp ~market-header-price-regular (&key (price :as string))
(defcomp ~prices/header-price-regular (&key (price :as string))
(div :class "text-xl font-semibold" price))
(defcomp ~market-header-rrp (&key (rrp :as string))
(defcomp ~prices/header-rrp (&key (rrp :as string))
(div :class "text-base text-stone-400" (span "rrp:") " " (span rrp)))
(defcomp ~market-prices-row (&key (inner :as list))
(defcomp ~prices/row (&key (inner :as list))
(div :class "flex flex-row items-center justify-between md:gap-2 md:px-2" inner))
@@ -38,31 +38,31 @@
;; Composition: prices header + cart button from data
;; ---------------------------------------------------------------------------
(defcomp ~market-prices-header-from-data (&key (cart-id :as string) (cart-action :as string) (csrf :as string) (quantity :as number?)
(defcomp ~prices/header-from-data (&key (cart-id :as string) (cart-action :as string) (csrf :as string) (quantity :as number?)
(cart-href :as string)
(sp-val :as number?) (sp-str :as string?) (rp-val :as number?) (rp-str :as string?) (rrp-str :as string?))
(~market-prices-row :inner
(~prices/row :inner
(<>
(if quantity
(~market-cart-add-quantity :cart-id cart-id :action cart-action :csrf csrf
(~cart/add-quantity :cart-id cart-id :action cart-action :csrf csrf
:minus-val (str (- quantity 1)) :plus-val (str (+ quantity 1))
:quantity (str quantity) :cart-href cart-href)
(~market-cart-add-empty :cart-id cart-id :action cart-action :csrf csrf))
(~cart/add-empty :cart-id cart-id :action cart-action :csrf csrf))
(when sp-val
(<> (~market-header-price-special-label)
(~market-header-price-special :price sp-str)
(when rp-val (~market-header-price-strike :price rp-str))))
(<> (~prices/header-price-special-label)
(~prices/header-price-special :price sp-str)
(when rp-val (~prices/header-price-strike :price rp-str))))
(when (and (not sp-val) rp-val)
(<> (~market-header-price-regular-label)
(~market-header-price-regular :price rp-str)))
(when rrp-str (~market-header-rrp :rrp rrp-str)))))
(<> (~prices/header-price-regular-label)
(~prices/header-price-regular :price rp-str)))
(when rrp-str (~prices/header-rrp :rrp rrp-str)))))
;; Card price line from data (used in product cards)
(defcomp ~market-card-price-from-data (&key (sp-val :as number?) (sp-str :as string?) (rp-val :as number?) (rp-str :as string?))
(~market-price-line :inner
(defcomp ~prices/card-price-from-data (&key (sp-val :as number?) (sp-str :as string?) (rp-val :as number?) (rp-str :as string?))
(~prices/line :inner
(<>
(when sp-val
(<> (~market-price-special :price sp-str)
(when rp-val (~market-price-regular-strike :price rp-str))))
(<> (~prices/special :price sp-str)
(when rp-val (~prices/regular-strike :price rp-str))))
(when (and (not sp-val) rp-val)
(~market-price-regular :price rp-str)))))
(~prices/regular :price rp-str)))))

View File

@@ -13,10 +13,10 @@
:layout :root
:data (all-markets-data)
:content (if no-markets
(~empty-state :icon "fa fa-store" :message "No markets available"
(~shared:misc/empty-state :icon "fa fa-store" :message "No markets available"
:cls "px-3 py-12 text-center text-stone-400")
(~market-markets-grid
:cards (~market-cards-content
(~grids/markets-grid
:cards (~cards/content
:markets market-data :page market-page
:has-more has-more :next-url next-url))))
@@ -26,10 +26,10 @@
:layout :post
:data (page-markets-data)
:content (if no-markets
(~empty-state :message "No markets for this page"
(~shared:misc/empty-state :message "No markets for this page"
:cls "px-3 py-12 text-center text-stone-400")
(~market-markets-grid
:cards (~market-cards-content
(~grids/markets-grid
:cards (~cards/content
:markets market-data :page market-page
:has-more has-more :next-url next-url))))
@@ -38,24 +38,24 @@
:auth :admin
:layout (:post-admin :selected "markets")
:data (page-admin-data)
:content (~market-admin-content-wrap
:inner (~crud-panel
:content (~grids/admin-content-wrap
:inner (~shared:misc/crud-panel
:list-id "markets-list"
:form (when can-create
(~crud-create-form
(~shared:misc/crud-create-form
:create-url create-url :csrf csrf
:errors-id "market-create-errors" :list-id "markets-list"
:placeholder "e.g. Suma, Craft Fair" :btn-label "Add market"))
:list (if admin-markets
(<> (map (fn (m)
(~crud-item
(~shared:misc/crud-item
:href (get m "href") :name (get m "name") :slug (get m "slug")
:del-url (get m "del-url") :csrf-hdr (get m "csrf-hdr")
:list-id "markets-list"
:confirm-title "Delete market?"
:confirm-text "Products will be hidden (soft delete)"))
admin-markets))
(~empty-state
(~shared:misc/empty-state
:message "No markets yet. Create one above."
:cls "text-gray-500 mt-4")))))
@@ -64,7 +64,7 @@
:auth :public
:layout :market
:data (market-home-data)
:content (~market-landing-from-data
:content (~cards/landing-from-data
:excerpt excerpt :feature-image feature-image :html html))
(defpage market-admin