Files
rose-ash/sx/sxc/examples.sx
giles 584445a843 SPA navigation, page component refactors, WASM rebuild
Refactor page components (docs, examples, specs, reference, layouts)
and adapters (adapter-sx, boot-helpers, orchestration) across sx/ and
web/ directories. Add Playwright SPA navigation tests. Rebuild WASM
kernel with updated bytecode. Add OCaml primitives for request handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:00:51 +00:00

1116 lines
34 KiB
Plaintext

(defcomp
~examples/card
(&key title description &rest children)
(div
:class "border border-stone-200 rounded-lg overflow-hidden"
(div
:class "bg-stone-100 px-4 py-3 border-b border-stone-200"
(h3 :class "font-semibold text-stone-800" title)
(when description (p :class "text-sm text-stone-500 mt-1" description)))
(div :class "p-4" children)))
(defcomp
~examples/demo
(&key &rest children)
(div
:class "border border-dashed border-stone-300 rounded p-4 bg-stone-100"
children))
(defcomp
~examples/source
(&key src-code)
(div
:class "not-prose bg-stone-100 rounded p-5 mt-3 mx-auto max-w-3xl"
(pre
:class "text-sm leading-relaxed whitespace-pre-wrap break-words"
(code src-code))))
(defcomp
~examples/click-to-load-demo
()
(div
:class "space-y-4"
(div
:id "click-result"
:class "p-4 rounded bg-stone-100 text-stone-500 text-center"
"Click the button to load content.")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.click))))"
:sx-target "#click-result"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
"Load content")))
(defcomp
~examples/click-result
(&key time)
(div
:class "space-y-2"
(p :class "text-stone-800 font-medium" "Content loaded!")
(p
:class "text-stone-500 text-sm"
(str "Fetched from the server via sx-get at " time))))
(defcomp
~examples/form-demo
()
(div
:class "space-y-4"
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.form))))"
:sx-target "#form-result"
:sx-swap "innerHTML"
:class "space-y-3"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Name")
(input
:type "text"
:name "name"
:placeholder "Enter a name"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"))
(button
:type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit"))
(div
:id "form-result"
:class "p-3 rounded bg-stone-100 text-stone-500 text-sm text-center"
"Submit the form to see the result.")))
(defcomp
~examples/form-result
(&key name)
(div
:class "text-stone-800"
(p (str "Hello, " (if (empty? name) "stranger" name) "!"))
(p
:class "text-sm text-stone-500 mt-1"
"Submitted via sx-post. The form data was sent as a POST request.")))
(defcomp
~examples/polling-demo
()
(div
:class "space-y-4"
(div
:id "poll-target"
:sx-get "/sx/(geography.(hypermedia.(example.(api.poll))))"
:sx-trigger "load, every 2s"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono"
"Loading...")))
(defcomp
~examples/poll-result
(&key time count)
(div
(p :class "text-stone-800 font-medium" (str "Server time: " time))
(p :class "text-stone-500 text-sm mt-1" (str "Poll count: " count))
(div
:class "mt-2 flex justify-center"
(div
:class "flex gap-1"
(map
(fn
(i)
(div
:class (str
"w-2 h-2 rounded-full "
(if (<= i count) "bg-violet-500" "bg-stone-200"))))
(list 1 2 3 4 5 6 7 8 9 10))))))
(defcomp
~examples/delete-demo
(&key items)
(div
(table
:class "w-full text-left text-sm"
(thead
(tr
:class "border-b border-stone-200"
(th :class "px-3 py-2 font-medium text-stone-600" "Item")
(th :class "px-3 py-2 font-medium text-stone-600 w-20" "")))
(tbody
:id "delete-rows"
(map
(fn
(item)
(~examples/delete-row :id (nth item 0) :name (nth item 1)))
items)))))
(defcomp
~examples/delete-row
(&key id name)
(tr
:id (str "row-" id)
:class "border-b border-stone-100 transition-all"
(td :class "px-3 py-2 text-stone-700" name)
(td
:class "px-3 py-2"
(button
:sx-delete (str "/sx/(geography.(hypermedia.(example.(api.(delete." id ")))))")
:sx-target (str "#row-" id)
:sx-swap "outerHTML"
:sx-confirm "Delete this item?"
:class "text-rose-500 hover:text-rose-700 text-sm"
"delete"))))
(defcomp
~examples/inline-edit-demo
()
(div
:id "edit-target"
:class "space-y-3"
(~examples/inline-view :value "Click edit to change this text")))
(defcomp
~examples/inline-view
(&key value)
(div
:class "flex items-center justify-between p-3 rounded border border-stone-200"
(span :class "text-stone-800" value)
(button
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.edit))))?value=" value)
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
"edit")))
(defcomp
~examples/inline-edit-form
(&key value)
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.edit))))"
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50"
(input
:type "text"
:name "value"
:value value
:class "flex-1 px-3 py-1.5 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(button
:type "submit"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"save")
(button
:type "button"
:sx-get (str
"/sx/(geography.(hypermedia.(example.(api.edit-cancel))))?value="
value)
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"cancel")))
(defcomp
~examples/oob-demo
()
(div
:class "space-y-4"
(div
:class "grid grid-cols-2 gap-4"
(div
:id "oob-box-a"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-500" "Box A")
(p :class "text-sm text-stone-400" "Waiting..."))
(div
:id "oob-box-b"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-500" "Box B")
(p :class "text-sm text-stone-400" "Waiting...")))
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.oob))))"
:sx-target "#oob-box-a"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Update both boxes")))
(defcomp
~examples/lazy-loading-demo
()
(div
:class "space-y-4"
(p
:class "text-sm text-stone-500"
"The content below loads automatically when the page renders.")
(div
:id "lazy-target"
:sx-get "/sx/(geography.(hypermedia.(example.(api.lazy))))"
:sx-trigger "load"
:sx-swap "innerHTML"
:class "p-6 rounded border border-stone-200 bg-stone-100 text-center"
(div
:class "animate-pulse space-y-2"
(div :class "h-4 bg-stone-200 rounded w-3/4 mx-auto")
(div :class "h-4 bg-stone-200 rounded w-1/2 mx-auto")))))
(defcomp
~examples/lazy-result
(&key time)
(div
:class "space-y-2"
(p :class "text-stone-800 font-medium" "Content loaded on page render!")
(p
:class "text-stone-500 text-sm"
(str "Loaded via sx-trigger=\"load\" at " time))))
(defcomp
~examples/infinite-scroll-demo
()
(div
:class "h-64 overflow-y-auto border border-stone-200 rounded"
:id "scroll-container"
(div
:id "scroll-items"
(map-indexed
(fn
(i item)
(div
:class "px-4 py-3 border-b border-stone-100 text-sm text-stone-700"
(str "Item " (+ i 1) " — loaded with the page")))
(list 1 2 3 4 5))
(div
:id "scroll-sentinel"
:sx-get "/sx/(geography.(hypermedia.(example.(api.scroll))))?page=2"
:sx-trigger "intersect once"
:sx-target "#scroll-items"
:sx-swap "beforeend"
:class "p-3 text-center text-stone-400 text-sm"
"Loading more..."))))
(defcomp
~examples/scroll-items
(&key items page)
(<>
(map
(fn
(item)
(div
:class "px-4 py-3 border-b border-stone-100 text-sm text-stone-700"
item))
items)
(when
(<= page 5)
(div
:id "scroll-sentinel"
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.scroll))))?page=" page)
:sx-trigger "intersect once"
:sx-target "#scroll-items"
:sx-swap "beforeend"
:class "p-3 text-center text-stone-400 text-sm"
"Loading more..."))))
(defcomp
~examples/progress-bar-demo
()
(div
:class "space-y-4"
(div
:id "progress-target"
:class "space-y-3"
(div
:class "w-full bg-stone-200 rounded-full h-4"
(div
:class "bg-violet-600 h-4 rounded-full transition-all"
:style "width: 0%"))
(p :class "text-sm text-stone-500 text-center" "Click start to begin."))
(button
:sx-post "/sx/(geography.(hypermedia.(example.(api.progress-start))))"
:sx-target "#progress-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Start job")))
(defcomp
~examples/progress-status
(&key percent job-id)
(div
:class "space-y-3"
(div
:class "w-full bg-stone-200 rounded-full h-4"
(div
:class "bg-violet-600 h-4 rounded-full transition-all"
:style (str "width: " percent "%")))
(p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
(when
(< percent 100)
(div
:sx-get (str
"/sx/(geography.(hypermedia.(example.(api.progress-status))))?job="
job-id)
:sx-trigger "load delay:500ms"
:sx-target "#progress-target"
:sx-swap "innerHTML"))
(when
(= percent 100)
(p
:class "text-sm text-emerald-600 font-medium text-center"
"Job complete!"))))
(defcomp
~examples/active-search-demo
()
(div
:class "space-y-3"
(input
:type "text"
:name "q"
:sx-get "/sx/(geography.(hypermedia.(example.(api.search))))"
:sx-trigger "keyup delay:300ms changed"
:sx-target "#search-results"
:sx-swap "innerHTML"
:placeholder "Search programming languages..."
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(div
:id "search-results"
:class "border border-stone-200 rounded divide-y divide-stone-100"
(p :class "p-3 text-sm text-stone-400" "Type to search..."))))
(defcomp
~examples/search-results
(&key items query)
(<>
(if
(empty? items)
(p
:class "p-3 text-sm text-stone-400"
(str "No results for \"" query "\""))
(map
(fn (item) (div :class "px-3 py-2 text-sm text-stone-700" item))
items))))
(defcomp
~examples/inline-validation-demo
()
(form
:class "space-y-4"
:sx-post "/sx/(geography.(hypermedia.(example.(api.validate-submit))))"
:sx-target "#validation-result"
:sx-swap "innerHTML"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
(input
:type "text"
:name "email"
:placeholder "user@example.com"
:sx-get "/sx/(geography.(hypermedia.(example.(api.validate))))"
:sx-trigger "blur"
:sx-target "#email-feedback"
:sx-swap "innerHTML"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(div :id "email-feedback" :class "mt-1"))
(button
:type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit")
(div :id "validation-result")))
(defcomp
~examples/validation-ok
(&key email)
(p :class "text-sm text-emerald-600" (str email " is available")))
(defcomp
~examples/validation-error
(&key message)
(p :class "text-sm text-rose-600" message))
(defcomp
~examples/value-select-demo
()
(div
:class "space-y-3"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
(select
:name "category"
:sx-get "/sx/(geography.(hypermedia.(example.(api.values))))"
:sx-trigger "change"
:sx-target "#value-items"
:sx-swap "innerHTML"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"
(option :value "" "Pick a category...")
(option :value "Languages" "Languages")
(option :value "Frameworks" "Frameworks")
(option :value "Databases" "Databases")))
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Item")
(select
:id "value-items"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"
(option :value "" "Select a category first...")))))
(defcomp
~examples/value-options
(&key items)
(<> (map (fn (item) (option :value item item)) items)))
(defcomp
~examples/reset-on-submit-demo
()
(div
:class "space-y-3"
(form
:id "reset-form"
:sx-post "/sx/(geography.(hypermedia.(example.(api.reset-submit))))"
:sx-target "#reset-result"
:sx-swap "innerHTML"
:sx-on:afterSwap "this.reset()"
:class "flex gap-2"
(input
:type "text"
:name "message"
:placeholder "Type a message..."
:class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(button
:type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Send"))
(div
:id "reset-result"
:class "space-y-2"
(p :class "text-sm text-stone-400" "Messages will appear here."))))
(defcomp
~examples/reset-message
(&key message time)
(div
:class "px-3 py-2 bg-stone-100 rounded text-sm text-stone-700"
(str "[" time "] " message)))
(defcomp
~examples/edit-row-demo
(&key rows)
(div
(table
:class "w-full text-left text-sm"
(thead
(tr
:class "border-b border-stone-200"
(th :class "px-3 py-2 font-medium text-stone-600" "Name")
(th :class "px-3 py-2 font-medium text-stone-600" "Price")
(th :class "px-3 py-2 font-medium text-stone-600" "Stock")
(th :class "px-3 py-2 font-medium text-stone-600 w-24" "")))
(tbody
:id "edit-rows"
(map
(fn
(row)
(~examples/edit-row-view
:id (nth row 0)
:name (nth row 1)
:price (nth row 2)
:stock (nth row 3)))
rows)))))
(defcomp
~examples/edit-row-view
(&key id name price stock)
(tr
:id (str "erow-" id)
:class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" name)
(td :class "px-3 py-2 text-stone-700" (str "$" price))
(td :class "px-3 py-2 text-stone-700" stock)
(td
:class "px-3 py-2"
(button
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
"edit"))))
(defcomp
~examples/edit-row-form
(&key id name price stock)
(tr
:id (str "erow-" id)
:class "border-b border-stone-100 bg-violet-50"
(td
:class "px-3 py-2"
(input
:type "text"
:name "name"
:value name
:class "w-full px-2 py-1 border border-stone-300 rounded text-sm"))
(td
:class "px-3 py-2"
(input
:type "text"
:name "price"
:value price
:class "w-20 px-2 py-1 border border-stone-300 rounded text-sm"))
(td
:class "px-3 py-2"
(input
:type "text"
:name "stock"
:value stock
:class "w-20 px-2 py-1 border border-stone-300 rounded text-sm"))
(td
:class "px-3 py-2 space-x-1"
(button
:sx-post (str "/sx/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:sx-include (str "#erow-" id)
:class "text-sm text-emerald-600 hover:text-emerald-800"
"save")
(button
:sx-get (str
"/sx/(geography.(hypermedia.(example.(api.(editrow-cancel."
id
")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:class "text-sm text-stone-500 hover:text-stone-700"
"cancel"))))
(defcomp
~examples/bulk-update-demo
(&key users)
(div
:class "space-y-3"
(form
:id "bulk-form"
(div
:class "flex gap-2 mb-3"
(button
:type "button"
:sx-post "/sx/(geography.(hypermedia.(example.(api.bulk))))?action=activate"
:sx-target "#bulk-table"
:sx-swap "innerHTML"
:sx-include "#bulk-form"
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
"Activate")
(button
:type "button"
:sx-post "/sx/(geography.(hypermedia.(example.(api.bulk))))?action=deactivate"
:sx-target "#bulk-table"
:sx-swap "innerHTML"
:sx-include "#bulk-form"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
"Deactivate"))
(table
:class "w-full text-left text-sm"
(thead
(tr
:class "border-b border-stone-200"
(th :class "px-3 py-2 w-8" "")
(th :class "px-3 py-2 font-medium text-stone-600" "Name")
(th :class "px-3 py-2 font-medium text-stone-600" "Email")
(th :class "px-3 py-2 font-medium text-stone-600" "Status")))
(tbody
:id "bulk-table"
(map
(fn
(u)
(~examples/bulk-row
:id (nth u 0)
:name (nth u 1)
:email (nth u 2)
:status (nth u 3)))
users))))))
(defcomp
~examples/bulk-row
(&key id name email status)
(tr
:class "border-b border-stone-100"
(td :class "px-3 py-2" (input :type "checkbox" :name "ids" :value id))
(td :class "px-3 py-2 text-stone-700" name)
(td :class "px-3 py-2 text-stone-700" email)
(td
:class "px-3 py-2"
(span
:class (str
"px-2 py-0.5 rounded text-xs font-medium "
(if
(= status "active")
"bg-emerald-100 text-emerald-700"
"bg-stone-100 text-stone-500"))
status))))
(defcomp
~examples/swap-positions-demo
()
(div
:class "space-y-3"
(div
:class "flex gap-2"
(button
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend"
:sx-target "#swap-log"
:sx-swap "beforeend"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to End")
(button
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin"
:sx-target "#swap-log"
:sx-swap "afterbegin"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to Start")
(button
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=none"
:sx-target "#swap-log"
:sx-swap "none"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
"Silent Ping")
(span
:id "swap-counter"
:class "self-center text-sm text-stone-500"
"Count: 0"))
(div
:id "swap-log"
:class "border border-stone-200 rounded h-48 overflow-y-auto divide-y divide-stone-100"
(p :class "p-3 text-sm text-stone-400" "Log entries will appear here."))))
(defcomp
~examples/swap-entry
(&key time mode)
(div :class "px-3 py-2 text-sm text-stone-700" (str "[" time "] " mode)))
(defcomp
~examples/select-filter-demo
()
(div
:class "space-y-3"
(div
:class "flex gap-2"
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:sx-select "#dash-stats"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Stats Only")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:sx-select "#dash-header"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Header Only")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
"Full Dashboard"))
(div
:id "filter-target"
:class "border border-stone-200 rounded p-4 bg-stone-100"
(p :class "text-sm text-stone-400" "Click a button to load content."))))
(defcomp
~examples/tabs-demo
()
(div
:class "space-y-0"
(div
:class "flex border-b border-stone-200"
:id "tab-buttons"
(~examples/tab-btn :tab "tab1" :label "Overview" :active "true")
(~examples/tab-btn :tab "tab2" :label "Details" :active "false")
(~examples/tab-btn :tab "tab3" :label "History" :active "false"))
(div
:id "tab-content"
:class "p-4 border border-t-0 border-stone-200 rounded-b"
(p
:class "text-stone-700"
"Welcome to the Overview tab. This content is loaded by default.")
(p
:class "text-stone-500 text-sm mt-2"
"Click the tabs above to navigate. Watch the browser URL update."))))
(defcomp
~examples/tab-btn
(&key tab label active)
(button
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(tabs." tab ")))))")
:sx-target "#tab-content"
:sx-swap "innerHTML"
:sx-push-url (str "/sx/(geography.(hypermedia.(example.tabs)))?tab=" tab)
:class (str
"px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors "
(if
(= active "true")
"border-violet-600 text-violet-600"
"border-transparent text-stone-500 hover:text-stone-700"))
label))
(defcomp
~examples/animations-demo
()
(div
:class "space-y-4"
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.animate))))"
:sx-target "#anim-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Load with animation")
(div
:id "anim-target"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400" "Content will fade in here."))))
(defcomp
~examples/anim-result
(&key color time)
(div
:class "sx-fade-in space-y-2"
(div
:class (str "p-4 rounded transition-colors duration-700 " color)
(p :class "font-medium" "Faded in!")
(p :class "text-sm mt-1" (str "Loaded at " time)))))
(defcomp
~examples/dialogs-demo
()
(div
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Open Dialog")
(div :id "dialog-container")))
(defcomp
~examples/dialog-modal
(&key title message)
(div
:class "fixed inset-0 z-50 flex items-center justify-center"
(div
:class "absolute inset-0 bg-black/50"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML")
(div
:class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4"
(h3 :class "text-lg font-semibold text-stone-800" title)
(p :class "text-stone-600" message)
(div
:class "flex justify-end gap-2"
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"Cancel")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Confirm")))))
(defcomp
~examples/keyboard-shortcuts-demo
()
(div
:class "space-y-4"
(div
:class "p-4 rounded border border-stone-200 bg-stone-100"
(p
:class "text-sm text-stone-600 font-medium mb-2"
"Keyboard shortcuts:")
(div
:class "flex gap-4"
(div
:class "flex items-center gap-1"
(kbd
:class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono"
"s")
(span :class "text-sm text-stone-500" "Search"))
(div
:class "flex items-center gap-1"
(kbd
:class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono"
"n")
(span :class "text-sm text-stone-500" "New item"))
(div
:class "flex items-center gap-1"
(kbd
:class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono"
"h")
(span :class "text-sm text-stone-500" "Help"))))
(div
:id "kbd-target"
:sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=s"
:sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Press a shortcut key..."))
(div
:sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=n"
:sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target"
:sx-swap "innerHTML")
(div
:sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=h"
:sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target"
:sx-swap "innerHTML")))
(defcomp
~examples/kbd-result
(&key key action)
(div
:class "space-y-1"
(p :class "text-stone-800 font-medium" action)
(p
:class "text-sm text-stone-500"
(str "Triggered by pressing '" key "'"))))
(defcomp
~examples/put-patch-demo
(&key name email role)
(div
:id "pp-target"
:class "space-y-4"
(~examples/pp-view :name name :email email :role role)))
(defcomp
~examples/pp-view
(&key name email role)
(div
:class "space-y-3"
(div
:class "flex justify-between items-start"
(div
(p :class "text-stone-800 font-medium" name)
(p :class "text-sm text-stone-500" email)
(p :class "text-sm text-stone-500" role))
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
"Edit All (PUT)"))))
(defcomp
~examples/pp-form-full
(&key name email role)
(form
:sx-put "/sx/(geography.(hypermedia.(example.(api.putpatch))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "space-y-3"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Name")
(input
:type "text"
:name "name"
:value name
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm"))
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
(input
:type "text"
:name "email"
:value email
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm"))
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Role")
(input
:type "text"
:name "role"
:value role
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm"))
(div
:class "flex gap-2"
(button
:type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Save All (PUT)")
(button
:type "button"
:sx-get "/sx/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"Cancel"))))
(defcomp
~examples/json-encoding-demo
()
(div
:class "space-y-4"
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.json-echo))))"
:sx-target "#json-result"
:sx-swap "innerHTML"
:sx-encoding "json"
:class "space-y-3"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Name")
(input
:type "text"
:name "name"
:value "Ada Lovelace"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm"))
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Age")
(input
:type "number"
:name "age"
:value "36"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm"))
(button
:type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit as JSON"))
(div
:id "json-result"
:class "p-3 rounded bg-stone-100 text-stone-500 text-sm"
"Submit the form to see the server echo the parsed JSON.")))
(defcomp
~examples/json-result
(&key body content-type)
(div
:class "space-y-2"
(p :class "text-stone-800 font-medium" "Server received:")
(pre
:class "text-sm bg-stone-100 p-3 rounded overflow-x-auto"
(code body))
(p :class "text-sm text-stone-500" (str "Content-Type: " content-type))))
(defcomp
~examples/vals-headers-demo
()
(div
:class "space-y-6"
(div
:class "space-y-2"
(h4
:class "text-sm font-semibold text-stone-700"
"sx-vals — send extra values")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.echo-vals))))"
:sx-target "#vals-result"
:sx-swap "innerHTML"
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Send with vals")
(div
:id "vals-result"
:class "p-3 rounded bg-stone-100 text-sm text-stone-400"
"Click to see server-received values."))
(div
:class "space-y-2"
(h4
:class "text-sm font-semibold text-stone-700"
"sx-headers — send custom headers")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.echo-headers))))"
:sx-target "#headers-result"
:sx-swap "innerHTML"
:sx-headers {:X-Request-Source "demo" :X-Custom-Token "abc123"}
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Send with headers")
(div
:id "headers-result"
:class "p-3 rounded bg-stone-100 text-sm text-stone-400"
"Click to see server-received headers."))))
(defcomp
~examples/echo-result
(&key label items)
(div
:class "space-y-1"
(p :class "text-stone-800 font-medium" (str "Server received " label ":"))
(map
(fn (item) (div :class "text-sm text-stone-600 font-mono" item))
items)))
(defcomp
~examples/loading-states-demo
()
(div
:class "space-y-4"
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.slow))))"
:sx-target "#loading-result"
:sx-swap "innerHTML"
:class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2"
(span
:class "sx-spinner w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin")
(span "Load slow endpoint"))
(div
:id "loading-result"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p
:class "text-stone-400 text-sm"
"Click the button — it takes 2 seconds."))))
(defcomp
~examples/loading-result
(&key time)
(div
(p :class "text-stone-800 font-medium" "Loaded!")
(p :class "text-sm text-stone-500" (str "Response arrived at " time))))
(defcomp
~examples/sync-replace-demo
()
(div
:class "space-y-3"
(input
:type "text"
:name "q"
:sx-get "/sx/(geography.(hypermedia.(example.(api.slow-search))))"
:sx-trigger "keyup delay:200ms changed"
:sx-target "#sync-result"
:sx-swap "innerHTML"
:sx-sync "replace"
:placeholder "Type to search (random delay 0.5-2s)..."
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(div
:id "sync-result"
:class "p-4 rounded border border-stone-200 bg-stone-100"
(p
:class "text-sm text-stone-400"
"Type to trigger requests — stale ones get aborted."))))
(defcomp
~examples/sync-result
(&key query delay)
(div
(p :class "text-stone-800 font-medium" (str "Result for: \"" query "\""))
(p
:class "text-sm text-stone-500"
(str "Server took " delay "ms to respond"))))
(defcomp
~examples/retry-demo
()
(div
:class "space-y-4"
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.flaky))))"
:sx-target "#retry-result"
:sx-swap "innerHTML"
:sx-retry "exponential:1000:8000"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Call flaky endpoint")
(div
:id "retry-result"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p
:class "text-stone-400 text-sm"
"Endpoint fails twice, succeeds on 3rd attempt."))))
(defcomp
~examples/retry-result
(&key attempt message)
(div
:class "space-y-1"
(p :class "text-stone-800 font-medium" message)
(p :class "text-sm text-stone-500" (str "Succeeded on attempt #" attempt))))