SXC content: docs/examples/home/reference pages + SX testing runner

New sxc/ content tree with 120 page files across docs, examples, home,
and reference demos. sx/sx/testing/ adds page-runner.sx (317L) and
index-runner.sx (394L) — SX-native test runner pages for
browser-based evaluation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 09:08:47 +00:00
parent 1a9c8d61b5
commit a7da235459
122 changed files with 2866 additions and 0 deletions

25
sx/sxc/docs/attr-row.sx Normal file
View File

@@ -0,0 +1,25 @@
(defcomp
(&key attr description exists href)
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm whitespace-nowrap")
(if
href
(a
:href href
:sx-get href
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
(~tw :tokens "text-violet-700 hover:text-violet-900 underline")
attr)
(span (~tw :tokens "text-violet-700") attr)))
(td (~tw :tokens "px-3 py-2 text-stone-700 text-sm") description)
(td
(~tw :tokens "px-3 py-2 text-center")
(if
exists
(span (~tw :tokens "text-emerald-600 text-sm") "yes")
(span (~tw :tokens "text-stone-400 text-sm italic") "not yet")))))

7
sx/sxc/docs/code.sx Normal file
View File

@@ -0,0 +1,7 @@
(defcomp
(&key src)
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 overflow-x-auto my-6")
(pre
(~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words font-mono")
src)))

22
sx/sxc/docs/nav.sx Normal file
View File

@@ -0,0 +1,22 @@
(defcomp
(&key items current)
(nav
(~tw :tokens "flex flex-wrap gap-2 mb-8")
(map
(fn
(item)
(a
:href (nth item 1)
:sx-get (nth item 1)
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
:class (str
"px-3 py-1.5 rounded text-sm font-medium no-underline "
(if
(= (nth item 0) current)
"bg-violet-100 text-violet-800"
"bg-stone-100 text-stone-600 hover:bg-stone-200"))
(nth item 0)))
items)))

5
sx/sxc/docs/note.sx Normal file
View File

@@ -0,0 +1,5 @@
(defcomp
(&key &rest children)
(div
(~tw :tokens "border-l-4 border-violet-400 bg-violet-50 p-4 text-stone-700 text-sm")
children))

5
sx/sxc/docs/page.sx Normal file
View File

@@ -0,0 +1,5 @@
(defcomp
(&key title &rest children)
(div
(~tw :tokens "max-w-4xl mx-auto px-6 pb-8 pt-4")
(div (~tw :tokens "prose prose-stone max-w-none space-y-6") children)))

View File

@@ -0,0 +1,14 @@
(defcomp
(&key category primitives)
(div
(~tw :tokens "space-y-2")
(h4 (~tw :tokens "text-lg font-semibold text-stone-700") category)
(div
(~tw :tokens "flex flex-wrap gap-2")
(map
(fn
(p)
(span
(~tw :tokens "inline-block px-2 py-1 rounded bg-stone-100 font-mono text-sm text-stone-700")
p))
primitives))))

7
sx/sxc/docs/section.sx Normal file
View File

@@ -0,0 +1,7 @@
(defcomp
(&key title id &rest children)
(section
:id id
(~tw :tokens "space-y-4")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") title)
children))

View File

@@ -0,0 +1,6 @@
(defcomp
(&key title &rest children)
(div
(~tw :tokens "space-y-3")
(h3 (~tw :tokens "text-xl font-semibold text-stone-700") title)
children))

22
sx/sxc/docs/table.sx Normal file
View File

@@ -0,0 +1,22 @@
(defcomp
(&key headers rows)
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(map
(fn (h) (th (~tw :tokens "px-3 py-2 font-medium text-stone-600") h))
headers)))
(tbody
(map
(fn
(row)
(tr
(~tw :tokens "border-b border-stone-100")
(map
(fn (cell) (td (~tw :tokens "px-3 py-2 text-stone-700") cell))
row)))
rows)))))

View File

@@ -0,0 +1,17 @@
(defcomp
()
(div
(~tw :tokens "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..."
(~tw :tokens "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"
(~tw :tokens "border border-stone-200 rounded divide-y divide-stone-100")
(p (~tw :tokens "p-3 text-sm text-stone-400") "Type to search..."))))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key color time)
(div
(~tw :tokens "sx-fade-in space-y-2")
(div
:class (str "p-4 rounded transition-colors duration-700 " color)
(p (~tw :tokens "font-medium") "Faded in!")
(p (~tw :tokens "text-sm mt-1") (str "Loaded at " time)))))

View File

@@ -0,0 +1,14 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.animate))))"
:sx-target "#anim-target"
:sx-swap "innerHTML"
(~tw :tokens "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"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p (~tw :tokens "text-stone-400") "Content will fade in here."))))

View File

@@ -0,0 +1,17 @@
(defcomp
(&key id name email status)
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2") (input :type "checkbox" :name "ids" :value id))
(td (~tw :tokens "px-3 py-2 text-stone-700") name)
(td (~tw :tokens "px-3 py-2 text-stone-700") email)
(td
(~tw :tokens "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))))

View File

@@ -0,0 +1,44 @@
(defcomp
(&key users)
(div
(~tw :tokens "space-y-3")
(form
:id "bulk-form"
(div
(~tw :tokens "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"
(~tw :tokens "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"
(~tw :tokens "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700")
"Deactivate"))
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200")
(th (~tw :tokens "px-3 py-2 w-8") "")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Email")
(th (~tw :tokens "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))))))

9
sx/sxc/examples/card.sx Normal file
View File

@@ -0,0 +1,9 @@
(defcomp
(&key title description &rest children)
(div
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(div
(~tw :tokens "bg-stone-100 px-4 py-3 border-b border-stone-200")
(h3 (~tw :tokens "font-semibold text-stone-800") title)
(when description (p (~tw :tokens "text-sm text-stone-500 mt-1") description)))
(div (~tw :tokens "p-4") children)))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key time)
(div
(~tw :tokens "space-y-2")
(p (~tw :tokens "text-stone-800 font-medium") "Content loaded!")
(p
(~tw :tokens "text-stone-500 text-sm")
(str "Fetched from the server via sx-get at " time))))

View File

@@ -0,0 +1,14 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(div
:id "click-result"
(~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors")
"Load content")))

View File

@@ -0,0 +1,17 @@
(defcomp
(&key items)
(div
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Item")
(th (~tw :tokens "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)))))

View File

@@ -0,0 +1,15 @@
(defcomp
(&key id name)
(tr
:id (str "row-" id)
(~tw :tokens "border-b border-stone-100 transition-all")
(td (~tw :tokens "px-3 py-2 text-stone-700") name)
(td
(~tw :tokens "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?"
(~tw :tokens "text-rose-500 hover:text-rose-700 text-sm")
"delete"))))

5
sx/sxc/examples/demo.sx Normal file
View File

@@ -0,0 +1,5 @@
(defcomp
(&key &rest children)
(div
(~tw :tokens "border border-dashed border-stone-300 rounded p-4 bg-stone-100")
children))

View File

@@ -0,0 +1,27 @@
(defcomp
(&key title message)
(div
(~tw :tokens "fixed inset-0 z-50 flex items-center justify-center")
(div
(~tw :tokens "absolute inset-0 bg-black/50")
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML")
(div
(~tw :tokens "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4")
(h3 (~tw :tokens "text-lg font-semibold text-stone-800") title)
(p (~tw :tokens "text-stone-600") message)
(div
(~tw :tokens "flex justify-end gap-2")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
(~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Confirm")))))

View File

@@ -0,0 +1,10 @@
(defcomp
()
(div
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Open Dialog")
(div :id "dialog-container")))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key label items)
(div
(~tw :tokens "space-y-1")
(p (~tw :tokens "text-stone-800 font-medium") (str "Server received " label ":"))
(map
(fn (item) (div (~tw :tokens "text-sm text-stone-600 font-mono") item))
items)))

View File

@@ -0,0 +1,23 @@
(defcomp
(&key rows)
(div
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Price")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Stock")
(th (~tw :tokens "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)))))

View File

@@ -0,0 +1,44 @@
(defcomp
(&key id name price stock)
(tr
:id (str "erow-" id)
(~tw :tokens "border-b border-stone-100 bg-violet-50")
(td
(~tw :tokens "px-3 py-2")
(input
:type "text"
:name "name"
:value name
(~tw :tokens "w-full px-2 py-1 border border-stone-300 rounded text-sm")))
(td
(~tw :tokens "px-3 py-2")
(input
:type "text"
:name "price"
:value price
(~tw :tokens "w-20 px-2 py-1 border border-stone-300 rounded text-sm")))
(td
(~tw :tokens "px-3 py-2")
(input
:type "text"
:name "stock"
:value stock
(~tw :tokens "w-20 px-2 py-1 border border-stone-300 rounded text-sm")))
(td
(~tw :tokens "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)
(~tw :tokens "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"
(~tw :tokens "text-sm text-stone-500 hover:text-stone-700")
"cancel"))))

View File

@@ -0,0 +1,16 @@
(defcomp
(&key id name price stock)
(tr
:id (str "erow-" id)
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") name)
(td (~tw :tokens "px-3 py-2 text-stone-700") (str "$" price))
(td (~tw :tokens "px-3 py-2 text-stone-700") stock)
(td
(~tw :tokens "px-3 py-2")
(button
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
(~tw :tokens "text-sm text-violet-600 hover:text-violet-800")
"edit"))))

View File

@@ -0,0 +1,24 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.form))))"
:sx-target "#form-result"
:sx-swap "innerHTML"
(~tw :tokens "space-y-3")
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Name")
(input
:type "text"
:name "name"
:placeholder "Enter a name"
(~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Submit"))
(div
:id "form-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-500 text-sm text-center")
"Submit the form to see the result.")))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key name)
(div
(~tw :tokens "text-stone-800")
(p (str "Hello, " (if (empty? name) "stranger" name) "!"))
(p
(~tw :tokens "text-sm text-stone-500 mt-1")
"Submitted via sx-post. The form data was sent as a POST request.")))

View File

@@ -0,0 +1,22 @@
(defcomp
()
(div
(~tw :tokens "h-64 overflow-y-auto border border-stone-200 rounded")
:id "scroll-container"
(div
:id "scroll-items"
(map-indexed
(fn
(i item)
(div
(~tw :tokens "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"
(~tw :tokens "p-3 text-center text-stone-400 text-sm")
"Loading more..."))))

View File

@@ -0,0 +1,6 @@
(defcomp
()
(div
:id "edit-target"
(~tw :tokens "space-y-3")
(~examples/inline-view :value "Click edit to change this text")))

View File

@@ -0,0 +1,25 @@
(defcomp
(&key value)
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.edit))))"
:sx-target "#edit-target"
:sx-swap "innerHTML"
(~tw :tokens "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50")
(input
:type "text"
:name "value"
:value value
(~tw :tokens "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"
(~tw :tokens "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"
(~tw :tokens "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300")
"cancel")))

View File

@@ -0,0 +1,24 @@
(defcomp
()
(form
(~tw :tokens "space-y-4")
:sx-post "/sx/(geography.(hypermedia.(example.(api.validate-submit))))"
:sx-target "#validation-result"
:sx-swap "innerHTML"
(div
(label (~tw :tokens "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"
(~tw :tokens "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" (~tw :tokens "mt-1")))
(button
:type "submit"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Submit")
(div :id "validation-result")))

View File

@@ -0,0 +1,11 @@
(defcomp
(&key value)
(div
(~tw :tokens "flex items-center justify-between p-3 rounded border border-stone-200")
(span (~tw :tokens "text-stone-800") value)
(button
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.edit))))?value=" value)
:sx-target "#edit-target"
:sx-swap "innerHTML"
(~tw :tokens "text-sm text-violet-600 hover:text-violet-800")
"edit")))

View File

@@ -0,0 +1,32 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(form
:sx-post "/sx/(geography.(hypermedia.(example.(api.json-echo))))"
:sx-target "#json-result"
:sx-swap "innerHTML"
:sx-encoding "json"
(~tw :tokens "space-y-3")
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Name")
(input
:type "text"
:name "name"
:value "Ada Lovelace"
(~tw :tokens "w-full px-3 py-2 border border-stone-300 rounded text-sm")))
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Age")
(input
:type "number"
:name "age"
:value "36"
(~tw :tokens "w-full px-3 py-2 border border-stone-300 rounded text-sm")))
(button
:type "submit"
(~tw :tokens "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"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-500 text-sm")
"Submit the form to see the server echo the parsed JSON.")))

View File

@@ -0,0 +1,9 @@
(defcomp
(&key body content-type)
(div
(~tw :tokens "space-y-2")
(p (~tw :tokens "text-stone-800 font-medium") "Server received:")
(pre
(~tw :tokens "text-sm bg-stone-100 p-3 rounded overflow-x-auto")
(code body))
(p (~tw :tokens "text-sm text-stone-500") (str "Content-Type: " content-type))))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key key action)
(div
(~tw :tokens "space-y-1")
(p (~tw :tokens "text-stone-800 font-medium") action)
(p
(~tw :tokens "text-sm text-stone-500")
(str "Triggered by pressing '" key "'"))))

View File

@@ -0,0 +1,46 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(div
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100")
(p
(~tw :tokens "text-sm text-stone-600 font-medium mb-2")
"Keyboard shortcuts:")
(div
(~tw :tokens "flex gap-4")
(div
(~tw :tokens "flex items-center gap-1")
(kbd
(~tw :tokens "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono")
"s")
(span (~tw :tokens "text-sm text-stone-500") "Search"))
(div
(~tw :tokens "flex items-center gap-1")
(kbd
(~tw :tokens "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono")
"n")
(span (~tw :tokens "text-sm text-stone-500") "New item"))
(div
(~tw :tokens "flex items-center gap-1")
(kbd
(~tw :tokens "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono")
"h")
(span (~tw :tokens "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"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p (~tw :tokens "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")))

View File

@@ -0,0 +1,17 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(p
(~tw :tokens "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"
(~tw :tokens "p-6 rounded border border-stone-200 bg-stone-100 text-center")
(div
(~tw :tokens "animate-pulse space-y-2")
(div (~tw :tokens "h-4 bg-stone-200 rounded w-3/4 mx-auto"))
(div (~tw :tokens "h-4 bg-stone-200 rounded w-1/2 mx-auto"))))))

View File

@@ -0,0 +1,8 @@
(defcomp
(&key time)
(div
(~tw :tokens "space-y-2")
(p (~tw :tokens "text-stone-800 font-medium") "Content loaded on page render!")
(p
(~tw :tokens "text-stone-500 text-sm")
(str "Loaded via sx-trigger=\"load\" at " time))))

View File

@@ -0,0 +1,5 @@
(defcomp
(&key time)
(div
(p (~tw :tokens "text-stone-800 font-medium") "Loaded!")
(p (~tw :tokens "text-sm text-stone-500") (str "Response arrived at " time))))

View File

@@ -0,0 +1,18 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.slow))))"
:sx-target "#loading-result"
:sx-swap "innerHTML"
(~tw :tokens "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
(~tw :tokens "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"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p
(~tw :tokens "text-stone-400 text-sm")
"Click the button — it takes 2 seconds."))))

View File

@@ -0,0 +1,22 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(div
(~tw :tokens "grid grid-cols-2 gap-4")
(div
:id "oob-box-a"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p (~tw :tokens "text-stone-500") "Box A")
(p (~tw :tokens "text-sm text-stone-400") "Waiting..."))
(div
:id "oob-box-b"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p (~tw :tokens "text-stone-500") "Box B")
(p (~tw :tokens "text-sm text-stone-400") "Waiting...")))
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.oob))))"
:sx-target "#oob-box-a"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Update both boxes")))

View File

@@ -0,0 +1,17 @@
(defcomp
(&key time count)
(div
(p (~tw :tokens "text-stone-800 font-medium") (str "Server time: " time))
(p (~tw :tokens "text-stone-500 text-sm mt-1") (str "Poll count: " count))
(div
(~tw :tokens "mt-2 flex justify-center")
(div
(~tw :tokens "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))))))

View File

@@ -0,0 +1,11 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(div
:id "poll-target"
:sx-get "/sx/(geography.(hypermedia.(example.(api.poll))))"
:sx-trigger "load, every 2s"
:sx-swap "innerHTML"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono")
"Loading...")))

View File

@@ -0,0 +1,41 @@
(defcomp
(&key name email role)
(form
:sx-put "/sx/(geography.(hypermedia.(example.(api.putpatch))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
(~tw :tokens "space-y-3")
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Name")
(input
:type "text"
:name "name"
:value name
(~tw :tokens "w-full px-3 py-2 border border-stone-300 rounded text-sm")))
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Email")
(input
:type "text"
:name "email"
:value email
(~tw :tokens "w-full px-3 py-2 border border-stone-300 rounded text-sm")))
(div
(label (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Role")
(input
:type "text"
:name "role"
:value role
(~tw :tokens "w-full px-3 py-2 border border-stone-300 rounded text-sm")))
(div
(~tw :tokens "flex gap-2")
(button
:type "submit"
(~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300")
"Cancel"))))

View File

@@ -0,0 +1,16 @@
(defcomp
(&key name email role)
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex justify-between items-start")
(div
(p (~tw :tokens "text-stone-800 font-medium") name)
(p (~tw :tokens "text-sm text-stone-500") email)
(p (~tw :tokens "text-sm text-stone-500") role))
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
(~tw :tokens "text-sm text-violet-600 hover:text-violet-800")
"Edit All (PUT)"))))

View File

@@ -0,0 +1,19 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(div
:id "progress-target"
(~tw :tokens "space-y-3")
(div
(~tw :tokens "w-full bg-stone-200 rounded-full h-4")
(div
(~tw :tokens "bg-violet-600 h-4 rounded-full transition-all")
:style "width: 0%"))
(p (~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Start job")))

View File

@@ -0,0 +1,24 @@
(defcomp
(&key percent job-id)
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "w-full bg-stone-200 rounded-full h-4")
(div
(~tw :tokens "bg-violet-600 h-4 rounded-full transition-all")
:style (str "width: " percent "%")))
(p (~tw :tokens "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
(~tw :tokens "text-sm text-emerald-600 font-medium text-center")
"Job complete!"))))

View File

@@ -0,0 +1,6 @@
(defcomp
(&key name email role)
(div
:id "pp-target"
(~tw :tokens "space-y-4")
(~examples/pp-view :name name :email email :role role)))

View File

@@ -0,0 +1,5 @@
(defcomp
(&key message time)
(div
(~tw :tokens "px-3 py-2 bg-stone-100 rounded text-sm text-stone-700")
(str "[" time "] " message)))

View File

@@ -0,0 +1,24 @@
(defcomp
()
(div
(~tw :tokens "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()"
(~tw :tokens "flex gap-2")
(input
:type "text"
:name "message"
:placeholder "Type a message..."
(~tw :tokens "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"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Send"))
(div
:id "reset-result"
(~tw :tokens "space-y-2")
(p (~tw :tokens "text-sm text-stone-400") "Messages will appear here."))))

View File

@@ -0,0 +1,17 @@
(defcomp
()
(div
(~tw :tokens "space-y-4")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.flaky))))"
:sx-target "#retry-result"
:sx-swap "innerHTML"
:sx-retry "exponential:1000:8000"
(~tw :tokens "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"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100 text-center")
(p
(~tw :tokens "text-stone-400 text-sm")
"Endpoint fails twice, succeeds on 3rd attempt."))))

View File

@@ -0,0 +1,6 @@
(defcomp
(&key attempt message)
(div
(~tw :tokens "space-y-1")
(p (~tw :tokens "text-stone-800 font-medium") message)
(p (~tw :tokens "text-sm text-stone-500") (str "Succeeded on attempt #" attempt))))

View File

@@ -0,0 +1,20 @@
(defcomp
(&key items page)
(<>
(map
(fn
(item)
(div
(~tw :tokens "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"
(~tw :tokens "p-3 text-center text-stone-400 text-sm")
"Loading more..."))))

View File

@@ -0,0 +1,11 @@
(defcomp
(&key items query)
(<>
(if
(empty? items)
(p
(~tw :tokens "p-3 text-sm text-stone-400")
(str "No results for \"" query "\""))
(map
(fn (item) (div (~tw :tokens "px-3 py-2 text-sm text-stone-700") item))
items))))

View File

@@ -0,0 +1,30 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-2")
(button
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:sx-select "#dash-stats"
(~tw :tokens "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"
(~tw :tokens "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"
(~tw :tokens "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700")
"Full Dashboard"))
(div
:id "filter-target"
(~tw :tokens "border border-stone-200 rounded p-4 bg-stone-100")
(p (~tw :tokens "text-sm text-stone-400") "Click a button to load content."))))

View File

@@ -0,0 +1,7 @@
(defcomp
(&key src-code)
(div
(~tw :tokens "not-prose bg-stone-100 rounded p-5 mt-3 mx-auto max-w-3xl")
(pre
(~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words")
(code src-code))))

View File

@@ -0,0 +1,3 @@
(defcomp
(&key time mode)
(div (~tw :tokens "px-3 py-2 text-sm text-stone-700") (str "[" time "] " mode)))

View File

@@ -0,0 +1,32 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-2")
(button
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend"
:sx-target "#swap-log"
:sx-swap "beforeend"
(~tw :tokens "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"
(~tw :tokens "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"
(~tw :tokens "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700")
"Silent Ping")
(span
:id "swap-counter"
(~tw :tokens "self-center text-sm text-stone-500")
"Count: 0"))
(div
:id "swap-log"
(~tw :tokens "border border-stone-200 rounded h-48 overflow-y-auto divide-y divide-stone-100")
(p (~tw :tokens "p-3 text-sm text-stone-400") "Log entries will appear here."))))

View File

@@ -0,0 +1,20 @@
(defcomp
()
(div
(~tw :tokens "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)..."
(~tw :tokens "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"
(~tw :tokens "p-4 rounded border border-stone-200 bg-stone-100")
(p
(~tw :tokens "text-sm text-stone-400")
"Type to trigger requests — stale ones get aborted."))))

View File

@@ -0,0 +1,7 @@
(defcomp
(&key query delay)
(div
(p (~tw :tokens "text-stone-800 font-medium") (str "Result for: \"" query "\""))
(p
(~tw :tokens "text-sm text-stone-500")
(str "Server took " delay "ms to respond"))))

View File

@@ -0,0 +1,14 @@
(defcomp
(&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))

View File

@@ -0,0 +1,19 @@
(defcomp
()
(div
(~tw :tokens "space-y-0")
(div
(~tw :tokens "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"
(~tw :tokens "p-4 border border-t-0 border-stone-200 rounded-b")
(p
(~tw :tokens "text-stone-700")
"Welcome to the Overview tab. This content is loaded by default.")
(p
(~tw :tokens "text-stone-500 text-sm mt-2")
"Click the tabs above to navigate. Watch the browser URL update."))))

View File

@@ -0,0 +1,3 @@
(defcomp
(&key message)
(p (~tw :tokens "text-sm text-rose-600") message))

View File

@@ -0,0 +1,3 @@
(defcomp
(&key email)
(p (~tw :tokens "text-sm text-emerald-600") (str email " is available")))

View File

@@ -0,0 +1,36 @@
(defcomp
()
(div
(~tw :tokens "space-y-6")
(div
(~tw :tokens "space-y-2")
(h4
(~tw :tokens "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\"}"
(~tw :tokens "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"
(~tw :tokens "p-3 rounded bg-stone-100 text-sm text-stone-400")
"Click to see server-received values."))
(div
(~tw :tokens "space-y-2")
(h4
(~tw :tokens "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"}
(~tw :tokens "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"
(~tw :tokens "p-3 rounded bg-stone-100 text-sm text-stone-400")
"Click to see server-received headers."))))

View File

@@ -0,0 +1,3 @@
(defcomp
(&key items)
(<> (map (fn (item) (option :value item item)) items)))

View File

@@ -0,0 +1,23 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(label (~tw :tokens "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"
(~tw :tokens "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 (~tw :tokens "block text-sm font-medium text-stone-700 mb-1") "Item")
(select
:id "value-items"
(~tw :tokens "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...")))))

9
sx/sxc/home/credits.sx Normal file
View File

@@ -0,0 +1,9 @@
(defcomp ()
(div (~tw :tokens "max-w-4xl mx-auto px-6 py-12 border-t border-stone-200")
(p (~tw :tokens "text-stone-500 text-sm")
"sx is heavily inspired by "
(a :href "https://htmx.org" (~tw :tokens "text-violet-600 hover:underline") "htmx")
" by Carson Gross. This documentation site is modelled on "
(a :href "https://four.htmx.org" (~tw :tokens "text-violet-600 hover:underline") "four.htmx.org")
". htmx showed that hypermedia belongs on the server. "
"sx takes that idea and wraps it in parentheses.")))

13
sx/sxc/home/hero.sx Normal file
View File

@@ -0,0 +1,13 @@
;; SX docs — home page components
(defcomp (&key &rest children)
(div (~tw :tokens "max-w-4xl mx-auto px-6 py-16 text-center")
(h1 (~tw :tokens "text-5xl font-bold text-stone-900 mb-4")
(span (~tw :tokens "text-violet-600 font-mono") "(<sx>)"))
(p (~tw :tokens "text-2xl text-stone-600 mb-4")
"The framework-free reactive hypermedium")
(p (~tw :tokens "text-sm text-stone-400")
"© Giles Bradshaw 2026")
(p (~tw :tokens "text-lg text-stone-500 max-w-2xl mx-auto mb-12")
"(sx === code === data === protocol === content === behaviour === layout === style === spec === sx)")
(div (~tw :tokens "bg-stone-100 rounded-lg p-6 text-left font-mono text-sm mx-auto max-w-2xl")
(pre (~tw :tokens "leading-relaxed whitespace-pre-wrap") children))))

View File

@@ -0,0 +1,19 @@
(defcomp ()
(div (~tw :tokens "max-w-4xl mx-auto px-6 py-12")
(h2 (~tw :tokens "text-3xl font-bold text-stone-900 mb-8") "How it works")
(div (~tw :tokens "space-y-6")
(div (~tw :tokens "flex items-start gap-4")
(div (~tw :tokens "flex-shrink-0 w-8 h-8 rounded-full bg-violet-100 text-violet-700 flex items-center justify-center font-bold") "1")
(div
(h3 (~tw :tokens "font-semibold text-stone-900") "Server renders sx")
(p (~tw :tokens "text-stone-600") "Python builds s-expression trees. Components, elements, data — all in one format.")))
(div (~tw :tokens "flex items-start gap-4")
(div (~tw :tokens "flex-shrink-0 w-8 h-8 rounded-full bg-violet-100 text-violet-700 flex items-center justify-center font-bold") "2")
(div
(h3 (~tw :tokens "font-semibold text-stone-900") "Wire sends text/sx")
(p (~tw :tokens "text-stone-600") "Responses are s-expression source code with content type text/sx. Component definitions cached client-side.")))
(div (~tw :tokens "flex items-start gap-4")
(div (~tw :tokens "flex-shrink-0 w-8 h-8 rounded-full bg-violet-100 text-violet-700 flex items-center justify-center font-bold") "3")
(div
(h3 (~tw :tokens "font-semibold text-stone-900") "Client evaluates + renders")
(p (~tw :tokens "text-stone-600") "sx.js parses, evaluates, and renders to DOM. Same evaluator runs server-side (Python) and client-side (JS)."))))))

20
sx/sxc/home/philosophy.sx Normal file
View File

@@ -0,0 +1,20 @@
(defcomp ()
(div (~tw :tokens "max-w-4xl mx-auto px-6 py-12")
(h2 (~tw :tokens "text-3xl font-bold text-stone-900 mb-8") "Design philosophy")
(div (~tw :tokens "grid md:grid-cols-2 gap-8")
(div (~tw :tokens "space-y-4")
(h3 (~tw :tokens "text-xl font-semibold text-violet-700") "From htmx")
(ul (~tw :tokens "space-y-2 text-stone-600")
(li "Server-rendered DOM over the wire (no HTML)")
(li "Hypermedia attributes on any element (sx-get, sx-post, ...)")
(li "Target/swap model for partial page updates")
(li "No client-side routing, no virtual DOM")
(li "Progressive enhancement — works without JS (mostly)")))
(div (~tw :tokens "space-y-4")
(h3 (~tw :tokens "text-xl font-semibold text-violet-700") "From React")
(ul (~tw :tokens "space-y-2 text-stone-600")
(li "Composable components with defcomp")
(li "Client-side rendering from s-expression source")
(li "Component caching via localStorage + hash invalidation")
(li "On-demand CSS — only ship what's used")
(li "DOM morphing for smooth history navigation"))))))

View File

@@ -0,0 +1,23 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(nav
:sx-boost "true"
(~tw :tokens "flex gap-3")
(a
:href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
(~tw :tokens "text-violet-600 hover:text-violet-800 underline text-sm")
"sx-get")
(a
:href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
(~tw :tokens "text-violet-600 hover:text-violet-800 underline text-sm")
"sx-post")
(a
:href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-target)))"
(~tw :tokens "text-violet-600 hover:text-violet-800 underline text-sm")
"sx-target"))
(p
(~tw :tokens "text-xs text-stone-400")
"These links use AJAX navigation via sx-boost — no sx-get needed on each link. "
"#sx-content")))

View File

@@ -0,0 +1,15 @@
(defcomp
()
(div
(~tw :tokens "space-y-2")
(div
:id "ref-confirm-item"
(~tw :tokens "flex items-center justify-between p-3 border border-stone-200 rounded")
(span (~tw :tokens "text-sm text-stone-700") "Important file.txt")
(button
:sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.confirm)))))"
:sx-target "#ref-confirm-item"
:sx-swap "delete"
:sx-confirm "Are you sure you want to delete this file?"
(~tw :tokens "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50")
"Delete"))))

View File

@@ -0,0 +1,9 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
:data-sx "(div :class \"p-3 bg-violet-50 rounded\" (h3 :class \"font-semibold text-violet-800\" \"Client-rendered\") (p :class \"text-sm text-stone-600\" \"This was evaluated in the browser — no server request.\"))")
(p
(~tw :tokens "text-xs text-stone-400")
"The content above is rendered client-side from the data-sx attribute.")))

View File

@@ -0,0 +1,10 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
:data-sx "(div :class \"p-3 bg-emerald-50 rounded\" (h3 :class \"font-semibold text-emerald-800\" title) (p :class \"text-sm text-stone-600\" message))"
:data-sx-env "{\"title\": \"Dynamic content\", \"message\": \"Variables passed via data-sx-env are available in the expression.\"}")
(p
(~tw :tokens "text-xs text-stone-400")
"The title and message above come from the data-sx-env JSON.")))

View File

@@ -0,0 +1,34 @@
(defcomp
()
(div
(~tw :tokens "space-y-2")
(div
:id "ref-del-1"
(~tw :tokens "flex items-center justify-between p-2 border border-stone-200 rounded")
(span (~tw :tokens "text-sm text-stone-700") "Item A")
(button
:sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.1)))))"
:sx-target "#ref-del-1"
:sx-swap "delete"
(~tw :tokens "text-red-500 text-sm hover:text-red-700")
"Remove"))
(div
:id "ref-del-2"
(~tw :tokens "flex items-center justify-between p-2 border border-stone-200 rounded")
(span (~tw :tokens "text-sm text-stone-700") "Item B")
(button
:sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.2)))))"
:sx-target "#ref-del-2"
:sx-swap "delete"
(~tw :tokens "text-red-500 text-sm hover:text-red-700")
"Remove"))
(div
:id "ref-del-3"
(~tw :tokens "flex items-center justify-between p-2 border border-stone-200 rounded")
(span (~tw :tokens "text-sm text-stone-700") "Item C")
(button
:sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.3)))))"
:sx-target "#ref-del-3"
:sx-swap "delete"
(~tw :tokens "text-red-500 text-sm hover:text-red-700")
"Remove"))))

View File

@@ -0,0 +1,30 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "grid grid-cols-2 gap-3")
(div
(~tw :tokens "p-3 border border-stone-200 rounded")
(p (~tw :tokens "text-xs text-stone-400 mb-2") "sx enabled")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-a"
:sx-swap "innerHTML"
(~tw :tokens "px-3 py-1 bg-violet-600 text-white rounded text-sm")
"Load")
(div :id "ref-dis-a" (~tw :tokens "mt-2 text-sm text-stone-500") "—"))
(div
:sx-disable "true"
(~tw :tokens "p-3 border border-stone-200 rounded")
(p (~tw :tokens "text-xs text-stone-400 mb-2") "sx disabled")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-b"
:sx-swap "innerHTML"
(~tw :tokens "px-3 py-1 bg-stone-400 text-white rounded text-sm")
"Load")
(div
:id "ref-dis-b"
(~tw :tokens "mt-2 text-sm text-stone-500")
"Button won't fire sx request")))))

View File

@@ -0,0 +1,22 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-3 items-center")
(button
:id "ref-diselt-btn"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-diselt-result"
:sx-swap "innerHTML"
:sx-disabled-elt "#ref-diselt-btn"
:sx-vals "{\"q\": \"hello\"}"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm disabled:opacity-50")
"Click (disables during request)")
(span
(~tw :tokens "text-xs text-stone-400")
"Button is disabled while request is in-flight."))
(div
:id "ref-diselt-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click the button to see it disable during the request.")))

View File

@@ -0,0 +1,22 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(form
:sx-post "/sx/(geography.(hypermedia.(reference.(api.upload-name))))"
:sx-encoding "multipart/form-data"
:sx-target "#ref-encoding-result"
:sx-swap "innerHTML"
(~tw :tokens "flex gap-2")
(input
:type "file"
:name "file"
(~tw :tokens "flex-1 text-sm text-stone-500 file:mr-2 file:px-3 file:py-1 file:rounded file:border-0 file:text-sm file:bg-violet-50 file:text-violet-700"))
(button
:type "submit"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Upload"))
(div
:id "ref-encoding-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Select a file and submit.")))

View File

@@ -0,0 +1,19 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-ar-result"
:sx-swap "innerHTML"
:sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load (logs after response)")
(div
:id "ref-evt-ar-log"
(~tw :tokens "p-2 rounded bg-emerald-50 text-emerald-700 text-sm")
"Event log will appear here.")
(div
:id "ref-evt-ar-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to load — afterRequest fires before the swap.")))

View File

@@ -0,0 +1,18 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-evt-as-list"
:sx-swap "beforeend"
:sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Add item (scrolls after swap)")
(div :id "ref-evt-as-count" (~tw :tokens "text-sm text-emerald-700") "1 items")
(div
:id "ref-evt-as-list"
(~tw :tokens "p-3 rounded border border-stone-200 space-y-1 max-h-32 overflow-y-auto")
(div
(~tw :tokens "text-sm text-stone-500")
"Items will be appended and scrolled into view."))))

View File

@@ -0,0 +1,22 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-2 items-center")
(input
:id "ref-evt-br-input"
:type "text"
:placeholder "Type something first..."
(~tw :tokens "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"))
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-br-result"
:sx-swap "innerHTML"
:sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load"))
(div
:id "ref-evt-br-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Request is cancelled via preventDefault() if the input is empty.")))

View File

@@ -0,0 +1,26 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(p
(~tw :tokens "text-sm text-stone-600")
"Open DevTools console, then navigate to a pure page (no :data expression). "
"You'll see \"sx:route client /path\" in the console — no network request is made.")
(div
(~tw :tokens "flex gap-2 flex-wrap")
(a
:href "/sx/(etc.(essay))"
(~tw :tokens "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200")
"Essays")
(a
:href "/sx/(etc.(plan))"
(~tw :tokens "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200")
"Plans")
(a
:href "/sx/(applications.(protocol))"
(~tw :tokens "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200")
"Protocols"))
(p
(~tw :tokens "text-xs text-stone-400")
"The sx:clientRoute event fires on the swap target and bubbles to document.body. "
"Apps use it to update nav selection, analytics, or other post-navigation state.")))

View File

@@ -0,0 +1,20 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "https://this-domain-does-not-exist.invalid/api"
:sx-target "#ref-evt-re-result"
:sx-swap "innerHTML"
:sx-on:sx:requestError "document.getElementById('ref-evt-re-status').style.display = 'block'; document.getElementById('ref-evt-re-status').textContent = 'Network error — request never reached a server'"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Request invalid domain")
(div
:id "ref-evt-re-status"
(~tw :tokens "p-2 rounded bg-red-50 text-red-600 text-sm")
:style "display: none"
"")
(div
:id "ref-evt-re-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to trigger a network error — sx:requestError fires.")))

View File

@@ -0,0 +1,20 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.error-500))))"
:sx-target "#ref-evt-err-result"
:sx-swap "innerHTML"
:sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Call failing endpoint")
(div
:id "ref-evt-err-status"
(~tw :tokens "p-2 rounded bg-red-50 text-red-600 text-sm")
:style "display: none"
"")
(div
:id "ref-evt-err-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to trigger an error — the sx:responseError event fires.")))

View File

@@ -0,0 +1,20 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
:sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'"
:sx-on:sx:sseOpen "document.getElementById('ref-evt-sseerr-status').textContent = 'Connected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'"
(div
(~tw :tokens "flex items-center gap-3")
(span
:id "ref-evt-sseerr-status"
(~tw :tokens "inline-block px-2 py-0.5 rounded text-xs bg-amber-100 text-amber-700")
"Connecting...")
(span (~tw :tokens "text-sm text-stone-500") "SSE stream")))
(p
(~tw :tokens "text-xs text-stone-400")
"If the SSE connection drops, the badge turns red via sx:sseError.")))

View File

@@ -0,0 +1,18 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
:sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'"
(div
:id "ref-evt-ssemsg-output"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-600 text-sm font-mono")
"Waiting for SSE messages..."))
(div
:id "ref-evt-ssemsg-count"
(~tw :tokens "text-sm text-emerald-700")
:data-count "0"
"0 messages received")))

View File

@@ -0,0 +1,19 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
:sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'"
(div
(~tw :tokens "flex items-center gap-3")
(span
:id "ref-evt-sseopen-status"
(~tw :tokens "inline-block px-2 py-0.5 rounded text-xs bg-amber-100 text-amber-700")
"Connecting...")
(span (~tw :tokens "text-sm text-stone-500") "SSE stream")))
(p
(~tw :tokens "text-xs text-stone-400")
"The status badge turns green when the SSE connection opens.")))

View File

@@ -0,0 +1,30 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(form
:sx-post "/sx/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-evt-vf-result"
:sx-swap "innerHTML"
:sx-validate "true"
:sx-on:sx:validationFailed "document.getElementById('ref-evt-vf-status').style.display = 'block'"
(~tw :tokens "flex gap-2")
(input
:type "email"
:name "email"
:required "true"
:placeholder "Email (required)"
(~tw :tokens "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500 invalid:border-red-400"))
(button
:type "submit"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Submit"))
(div
:id "ref-evt-vf-status"
(~tw :tokens "p-2 rounded bg-amber-50 text-amber-700 text-sm")
:style "display: none"
"Validation failed — form was not submitted.")
(div
:id "ref-evt-vf-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Submit with empty/invalid email to trigger the event.")))

View File

@@ -0,0 +1,14 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-get-result"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load server time")
(div
:id "ref-get-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to load.")))

View File

@@ -0,0 +1,15 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-target "#ref-hdr-prompt-result"
:sx-swap "innerHTML"
:sx-prompt "Enter your name:"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Prompt & send")
(div
:id "ref-hdr-prompt-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to enter a name via prompt — the value is sent as the SX-Prompt header.")))

View File

@@ -0,0 +1,26 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.retarget))))"
:sx-target "#ref-hdr-retarget-main"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load (server retargets)")
(div
(~tw :tokens "grid grid-cols-2 gap-3")
(div
(~tw :tokens "rounded border border-stone-200 p-3")
(div (~tw :tokens "text-xs text-stone-400 mb-1") "Original target")
(div
:id "ref-hdr-retarget-main"
(~tw :tokens "text-sm text-stone-500")
"Waiting..."))
(div
(~tw :tokens "rounded border border-stone-200 p-3")
(div (~tw :tokens "text-xs text-stone-400 mb-1") "Retarget destination")
(div
:id "ref-hdr-retarget-alt"
(~tw :tokens "text-sm text-stone-500")
"Waiting...")))))

View File

@@ -0,0 +1,15 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.trigger-event))))"
:sx-target "#ref-hdr-trigger-result"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load with trigger")
(div
:id "ref-hdr-trigger-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
:sx-on:showNotice "this.style.borderColor = '#8b5cf6'; this.style.borderWidth = '2px'"
"Click — the server response includes SX-Trigger: showNotice, which highlights this box.")))

View File

@@ -0,0 +1,15 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.echo-headers))))"
:sx-headers {:X-Request-Source "demo" :X-Custom-Token "abc123"}
:sx-target "#ref-headers-result"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Send with custom headers")
(div
:id "ref-headers-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to see echoed headers.")))

View File

@@ -0,0 +1,26 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-ignore-container"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Swap container")
(div
:id "ref-ignore-container"
(~tw :tokens "space-y-2")
(div
:sx-ignore "true"
(~tw :tokens "p-2 bg-amber-50 rounded border border-amber-200")
(p
(~tw :tokens "text-sm text-amber-800")
"This subtree has sx-ignore — it won't change.")
(input
:type "text"
:placeholder "Type here — ignored during swap"
(~tw :tokens "mt-1 w-full px-2 py-1 border border-amber-300 rounded text-sm")))
(div
(~tw :tokens "p-2 bg-stone-100 rounded text-sm text-stone-600")
"This text WILL be replaced on swap."))))

View File

@@ -0,0 +1,26 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-2 items-end")
(div
(label (~tw :tokens "block text-xs text-stone-500 mb-1") "Category")
(select
:id "ref-inc-cat"
:name "category"
(~tw :tokens "px-3 py-2 border border-stone-300 rounded text-sm")
(option :value "all" "All")
(option :value "books" "Books")
(option :value "tools" "Tools")))
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-include "#ref-inc-cat"
:sx-target "#ref-include-result"
:sx-swap "innerHTML"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Filter"))
(div
:id "ref-include-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click Filter — the select value is included in the request.")))

View File

@@ -0,0 +1,23 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex gap-3 items-center")
(button
:sx-get "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-indicator-result"
:sx-swap "innerHTML"
:sx-indicator "#ref-spinner"
:sx-vals "{\"q\": \"hello\"}"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm")
"Load (slow)")
(span
:id "ref-spinner"
(~tw :tokens "text-violet-600 text-sm")
:style "display: none"
"Loading..."))
(div
:id "ref-indicator-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click to load (indicator shows during request).")))

View File

@@ -0,0 +1,17 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(a
:href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
:sx-media "(min-width: 768px)"
(~tw :tokens "inline-block px-4 py-2 bg-violet-600 text-white rounded text-sm no-underline hover:bg-violet-700")
"sx navigation (desktop only)")
(p
(~tw :tokens "text-sm text-stone-500")
"On screens narrower than 768px this link uses normal navigation. On wider screens it uses sx.")))

View File

@@ -0,0 +1,12 @@
(defcomp
()
(div
(~tw :tokens "space-y-3")
(button
:sx-on:click "document.getElementById('ref-on-result').textContent = 'Clicked at ' + new Date().toLocaleTimeString()"
(~tw :tokens "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Click me")
(div
:id "ref-on-result"
(~tw :tokens "p-3 rounded bg-stone-100 text-stone-400 text-sm")
"Click the button — runs JavaScript, no server request.")))

Some files were not shown because too many files have changed in this diff Show More