;; SX docs — example and demo components (defcomp ~example-card (&key title description &rest children) (div :class "border border-stone-200 rounded-lg overflow-hidden" (div :class "bg-stone-50 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 ~example-demo (&key &rest children) (div :class "border border-dashed border-stone-300 rounded p-4 bg-white" children)) (defcomp ~example-source (&key code) (div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto" (pre :class "text-sm" (code code)))) ;; --- Click to load demo --- (defcomp ~click-to-load-demo () (div :class "space-y-4" (div :id "click-result" :class "p-4 rounded bg-stone-50 text-stone-500 text-center" "Click the button to load content.") (button :sx-get "/examples/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 ~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)))) ;; --- Form submission demo --- (defcomp ~form-demo () (div :class "space-y-4" (form :sx-post "/examples/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-50 text-stone-500 text-sm text-center" "Submit the form to see the result."))) (defcomp ~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."))) ;; --- Polling demo --- (defcomp ~polling-demo () (div :class "space-y-4" (div :id "poll-target" :sx-get "/examples/api/poll" :sx-trigger "load, every 2s" :sx-swap "innerHTML" :class "p-4 rounded border border-stone-200 bg-white text-center font-mono" "Loading..."))) (defcomp ~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)))))) ;; --- Delete row demo --- (defcomp ~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) (~delete-row :id (nth 0 item) :name (nth 1 item))) items))))) (defcomp ~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 "/examples/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")))) ;; --- Inline edit demo --- (defcomp ~inline-edit-demo () (div :id "edit-target" :class "space-y-3" (~inline-view :value "Click edit to change this text"))) (defcomp ~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 "/examples/api/edit?value=" value) :sx-target "#edit-target" :sx-swap "innerHTML" :class "text-sm text-violet-600 hover:text-violet-800" "edit"))) (defcomp ~inline-edit-form (&key value) (form :sx-post "/examples/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 "/examples/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"))) ;; --- OOB swap demo --- (defcomp ~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-white 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-white text-center" (p :class "text-stone-500" "Box B") (p :class "text-sm text-stone-400" "Waiting..."))) (button :sx-get "/examples/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"))) ;; --- Lazy loading demo --- (defcomp ~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 "/examples/api/lazy" :sx-trigger "load" :sx-swap "innerHTML" :class "p-6 rounded border border-stone-200 bg-stone-50 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 ~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)))) ;; --- Infinite scroll demo --- (defcomp ~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 "/examples/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 ~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 "/examples/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...")))) ;; --- Progress bar demo --- (defcomp ~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 "/examples/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 ~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 "/examples/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!")))) ;; --- Active search demo --- (defcomp ~active-search-demo () (div :class "space-y-3" (input :type "text" :name "q" :sx-get "/examples/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 ~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)))) ;; --- Inline validation demo --- (defcomp ~inline-validation-demo () (form :class "space-y-4" :sx-post "/examples/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 "/examples/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 ~validation-ok (&key email) (p :class "text-sm text-emerald-600" (str email " is available"))) (defcomp ~validation-error (&key message) (p :class "text-sm text-rose-600" message)) ;; --- Value select demo --- (defcomp ~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 "/examples/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 ~value-options (&key items) (<> (map (fn (item) (option :value item item)) items))) ;; --- Reset on submit demo --- (defcomp ~reset-on-submit-demo () (div :class "space-y-3" (form :id "reset-form" :sx-post "/examples/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 ~reset-message (&key message time) (div :class "px-3 py-2 bg-stone-50 rounded text-sm text-stone-700" (str "[" time "] " message))) ;; --- Edit row demo --- (defcomp ~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) (~edit-row-view :id (nth 0 row) :name (nth 1 row) :price (nth 2 row) :stock (nth 3 row))) rows))))) (defcomp ~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 "/examples/api/editrow/" id) :sx-target (str "#erow-" id) :sx-swap "outerHTML" :class "text-sm text-violet-600 hover:text-violet-800" "edit")))) (defcomp ~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 "/examples/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 "/examples/api/editrow/" id "/cancel") :sx-target (str "#erow-" id) :sx-swap "outerHTML" :class "text-sm text-stone-500 hover:text-stone-700" "cancel")))) ;; --- Bulk update demo --- (defcomp ~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 "/examples/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 "/examples/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) (~bulk-row :id (nth 0 u) :name (nth 1 u) :email (nth 2 u) :status (nth 3 u))) users)))))) (defcomp ~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)))) ;; --- Swap positions demo --- (defcomp ~swap-positions-demo () (div :class "space-y-3" (div :class "flex gap-2" (button :sx-post "/examples/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 "/examples/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 "/examples/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 ~swap-entry (&key time mode) (div :class "px-3 py-2 text-sm text-stone-700" (str "[" time "] " mode))) ;; --- Select filter demo --- (defcomp ~select-filter-demo () (div :class "space-y-3" (div :class "flex gap-2" (button :sx-get "/examples/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 "/examples/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 "/examples/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-white" (p :class "text-sm text-stone-400" "Click a button to load content.")))) ;; --- Tabs demo --- (defcomp ~tabs-demo () (div :class "space-y-0" (div :class "flex border-b border-stone-200" :id "tab-buttons" (~tab-btn :tab "tab1" :label "Overview" :active "true") (~tab-btn :tab "tab2" :label "Details" :active "false") (~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 ~tab-btn (&key tab label active) (button :sx-get (str "/examples/api/tabs/" tab) :sx-target "#tab-content" :sx-swap "innerHTML" :sx-push-url (str "/examples/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)) ;; --- Animations demo --- (defcomp ~animations-demo () (div :class "space-y-4" (button :sx-get "/examples/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-white text-center" (p :class "text-stone-400" "Content will fade in here.")))) (defcomp ~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))))) ;; --- Dialogs demo --- (defcomp ~dialogs-demo () (div (button :sx-get "/examples/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 ~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 "/examples/api/dialog/close" :sx-target "#dialog-container" :sx-swap "innerHTML") (div :class "relative bg-white 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 "/examples/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 "/examples/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"))))) ;; --- Keyboard shortcuts demo --- (defcomp ~keyboard-shortcuts-demo () (div :class "space-y-4" (div :class "p-4 rounded border border-stone-200 bg-stone-50" (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-white 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-white 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-white border border-stone-300 rounded text-xs font-mono" "h") (span :class "text-sm text-stone-500" "Help")))) (div :id "kbd-target" :sx-get "/examples/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-white text-center" (p :class "text-stone-400 text-sm" "Press a shortcut key...")) (div :sx-get "/examples/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 "/examples/api/keyboard?key=h" :sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body" :sx-target "#kbd-target" :sx-swap "innerHTML"))) (defcomp ~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 "'")))) ;; --- PUT / PATCH demo --- (defcomp ~put-patch-demo (&key name email role) (div :id "pp-target" :class "space-y-4" (~pp-view :name name :email email :role role))) (defcomp ~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 "/examples/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 ~pp-form-full (&key name email role) (form :sx-put "/examples/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 "/examples/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")))) ;; --- JSON encoding demo --- (defcomp ~json-encoding-demo () (div :class "space-y-4" (form :sx-post "/examples/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-50 text-stone-500 text-sm" "Submit the form to see the server echo the parsed JSON."))) (defcomp ~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)))) ;; --- Vals & Headers demo --- (defcomp ~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 "/examples/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-50 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 "/examples/api/echo-headers" :sx-target "#headers-result" :sx-swap "innerHTML" :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :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-50 text-sm text-stone-400" "Click to see server-received headers.")))) (defcomp ~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))) ;; --- Loading states demo --- (defcomp ~loading-states-demo () (div :class "space-y-4" (button :sx-get "/examples/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-white text-center" (p :class "text-stone-400 text-sm" "Click the button — it takes 2 seconds.")))) (defcomp ~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)))) ;; --- Sync replace demo (request abort) --- (defcomp ~sync-replace-demo () (div :class "space-y-3" (input :type "text" :name "q" :sx-get "/examples/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-white" (p :class "text-sm text-stone-400" "Type to trigger requests — stale ones get aborted.")))) (defcomp ~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")))) ;; --- Retry demo --- (defcomp ~retry-demo () (div :class "space-y-4" (button :sx-get "/examples/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-white text-center" (p :class "text-stone-400 text-sm" "Endpoint fails twice, succeeds on 3rd attempt.")))) (defcomp ~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))))