Component gallery¶
An interactive gallery of the Companion UI components, each rendered in
isolation on one live page — no database, no auth, just in-memory
per-connection state in the @ws loop.
Where the Admin ABM shows the components working together in a real app over Postgres, the gallery shows each one on its own so you can lift the pattern straight into your project. It's the runnable companion to the UI components catalog and the seed for a future playground.
Source: examples/gallery/
Run¶
Open http://127.0.0.1:3000/. Or build a native binary — both serve the same page bit-for-bit:
What's in it¶
Three tabs, each a group of cards. Every interactive control is wired over the
WebSocket with data-flv-* attributes — no custom JS.
| Tab | Components |
|---|---|
| Controles | Button + reactive counter, Badge, Chip, Tooltip (CSS), Rating (★), ProgressBar, Spinner, Divider |
| Feedback | Modal / ConfirmDialog, Toast, ExpansionPanel (native <details>), Stepper |
| Formulario | Text input, Select, Radio group, Checkbox group, Textarea, submit → echo |
Each control demonstrates one of the three wiring patterns:
data-flv-click="event"— a button click sendseventover the socket (counter, tabs, modal, toast, stepper).data-flv-submit="event"on a<form>— the submit serializes every named input (checkbox groups included) into the event payload.- CSS-only — Tooltip, ExpansionPanel and Spinner need no server round-trip; they're pure markup + CSS.
How it fits together¶
The whole gallery is one file, src/main.fitz:
- helpers (
badge,chip,progress_bar,rating_input) return HTML fragments asStr; - panels (
panel_controls,panel_feedback,panel_form) compose the helpers into cards; render_gallery(...)builds the full page from the current state;@ws("/live/gallery")keeps state in local variables, applies each event, re-renders, and ships the diff withdiff_html.
No component here uses a database or a LiveComponent (.fitzv) — it's the
smallest possible surface to see the visual components and their events.
The @ws loop¶
The state lives entirely in the socket handler — one set of locals per connection, mutated per event, re-rendered, diffed:
@ws("/live/gallery")
async fn gallery_socket(ws: WsConn<LiveFrame>) {
let tab = "controles"
let count = 0
let rating = 3
let modal = false
let flash = ""
let step = 1
let form_result = ""
let last = render_gallery(tab, count, rating, modal, flash, step, form_result).raw
loop {
let frame = ws.recv()?
let ev = frame.event
// A toast lives for one render: any next event clears it.
flash = ""
if (ev == "tab") {
tab = pget(frame.payload, "tab", tab)
}
if (ev == "inc") {
count = count + 1
}
if (ev == "dec") {
count = count - 1
}
if (ev == "reset_count") {
count = 0
}
if (ev == "set_rating") {
rating = pget_int(frame.payload, "n", rating)
}
if (ev == "open_modal") {
modal = true
}
if (ev == "cancel_modal") {
modal = false
}
if (ev == "confirm_modal") {
modal = false
flash = "Eliminado."
tab = "feedback"
}
if (ev == "show_toast") {
flash = "¡Hola! Soy un toast."
tab = "feedback"
}
if (ev == "dismiss_toast") {
flash = ""
}
if (ev == "step_next") {
step = match step < 3 {
true => step + 1,
false => 1,
}
}
if (ev == "step_prev") {
if (step > 1) {
step = step - 1
}
}
if (ev == "submit_form") {
let nombre = pget(frame.payload, "nombre", "")
let rol = pget(frame.payload, "rol", "")
let activo = pget(frame.payload, "activo", "true")
let skills = pget(frame.payload, "skills", "")
let notas = pget(frame.payload, "notas", "")
form_result = "nombre={nombre} · rol={rol} · activo={activo} · skills=[{skills}] · notas={notas}"
}
let new_html = render_gallery(tab, count, rating, modal, flash, step, form_result).raw
let patches = diff_html(last, new_html)
ws.send(LiveFrame { html: new_html, patches: patches })?
last = new_html
}
}
Next¶
This gallery is the seed for a playground (edit-and-preview) and the per-component runnable examples of the Companion UI adoption package. See the UI components catalog for every component and the Admin ABM for the same components in a production-shaped app.