- Server sends sexp source text, client (sexp.js) renders everything - SexpExpr marker class for nested sexp composition in serialize() - sexp_page() HTML shell with data-mount="body" for full page loads - sexp_response() returns text/sexp for OOB/partial responses - ~app-body layout component replaces ~app-layout (no raw!) - ~rich-text is the only component using raw! (for CMS HTML content) - Fragment endpoints return text/sexp, auto-wrapped in SexpExpr - All _*_html() helpers converted to _*_sexp() returning sexp source - Head auto-hoist: sexp.js moves meta/title/link/script[ld+json] from rendered body to document.head automatically - Unknown components render warning box instead of crashing page - Component kwargs preserve AST for lazy rendering (fixes <> in kwargs) - Fix unterminated paren in events/sexp/tickets.sexpr Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
3.4 KiB
Plaintext
68 lines
3.4 KiB
Plaintext
;; Blog navigation components
|
|
|
|
(defcomp ~blog-nav-empty (&key wrapper-id)
|
|
(div :id wrapper-id :sx-swap-oob "outerHTML"))
|
|
|
|
(defcomp ~blog-nav-item-image (&key src label)
|
|
(if src (img :src src :alt label :class "w-8 h-8 rounded-full object-cover flex-shrink-0")
|
|
(div :class "w-8 h-8 rounded-full bg-stone-200 flex-shrink-0")))
|
|
|
|
(defcomp ~blog-nav-item-link (&key href hx-get selected nav-cls img label)
|
|
(div (a :href href :sx-get hx-get :sx-target "#main-panel"
|
|
:sx-swap "outerHTML" :sx-push-url "true"
|
|
:aria-selected selected :class nav-cls
|
|
img (span label))))
|
|
|
|
(defcomp ~blog-nav-item-plain (&key href selected nav-cls img label)
|
|
(div (a :href href :aria-selected selected :class nav-cls
|
|
img (span label))))
|
|
|
|
(defcomp ~blog-nav-wrapper (&key arrow-cls container-id left-hs scroll-hs right-hs items)
|
|
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
|
:id "menu-items-nav-wrapper" :sx-swap-oob "outerHTML"
|
|
(button :class (str arrow-cls " hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded")
|
|
:aria-label "Scroll left"
|
|
:_ left-hs (i :class "fa fa-chevron-left"))
|
|
(div :id container-id
|
|
:class "overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
|
|
:style "scroll-behavior: smooth;" :_ scroll-hs
|
|
(div :class "flex flex-col sm:flex-row gap-1" items))
|
|
(style ".scrollbar-hide::-webkit-scrollbar { display: none; } .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }")
|
|
(button :class (str arrow-cls " hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded")
|
|
:aria-label "Scroll right"
|
|
:_ right-hs (i :class "fa fa-chevron-right"))))
|
|
|
|
;; Nav entries
|
|
|
|
(defcomp ~blog-nav-entries-empty ()
|
|
(div :id "entries-calendars-nav-wrapper" :sx-swap-oob "true"))
|
|
|
|
(defcomp ~blog-nav-entry-item (&key href nav-cls name date-str)
|
|
(a :href href :class nav-cls
|
|
(div :class "w-8 h-8 rounded bg-stone-200 flex-shrink-0")
|
|
(div :class "flex-1 min-w-0"
|
|
(div :class "font-medium truncate" name)
|
|
(div :class "text-xs text-stone-600 truncate" date-str))))
|
|
|
|
(defcomp ~blog-nav-calendar-item (&key href nav-cls name)
|
|
(a :href href :class nav-cls
|
|
(i :class "fa fa-calendar" :aria-hidden "true")
|
|
(div name)))
|
|
|
|
(defcomp ~blog-nav-entries-wrapper (&key scroll-hs items)
|
|
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
|
:id "entries-calendars-nav-wrapper" :sx-swap-oob "true"
|
|
(button :class "entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
|
:aria-label "Scroll left"
|
|
:_ "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft - 200"
|
|
(i :class "fa fa-chevron-left"))
|
|
(div :id "associated-items-container"
|
|
:class "overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
|
|
:style "scroll-behavior: smooth;" :_ scroll-hs
|
|
(div :class "flex flex-col sm:flex-row gap-1" items))
|
|
(style ".scrollbar-hide::-webkit-scrollbar { display: none; } .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }")
|
|
(button :class "entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
|
:aria-label "Scroll right"
|
|
:_ "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200"
|
|
(i :class "fa fa-chevron-right"))))
|