Examples
Composition
About
Composition: building a page out of views and components.
quillo run compose.qwe --watch then open http://localhost:8080
- Card is a view: it only displays, has no state, costs no JavaScript.
- Counter is a component: it has its own state and reports changes upward through a callback prop (on_change).
- Panel takes a named slot (header) as well as children.
- Dashboard puts them together and keeps the state they share.
Run it
$ quillo run compose.qwe --watchSource
compose.qwe
view Card(title: str, children) = <section.card> <h2>{title}</h2> {children} </section>component Counter(label: str, start: int = 0, step: int = 1, on_change = null) { state n = start <div.counter> <span.label>{label}</span> <button @click={() => { n -= step; on_change && on_change(label, n) }}>−</button> <span.n>{n}</span> <button @click={() => { n += step; on_change && on_change(label, n) }}>+</button> </div>}component Panel(header, open: bool = true, children) { state shown = open <div.panel> <button.header @click={() => { shown = !shown }}> {shown ? "▾" : "▸"} {header} </button> {shown && <div.body>{children}</div>} </div>}component Dashboard() { state totals = { Apples: 0, Pears: 3, Plums: 0 } state log = [] let fruit = values(totals) | sum // Called by the Counters when their count changes. let note = (label, n) => { log = take([`${label} → ${n}`] + log, 5) } let changed = (label, n) => { totals = set(totals, label, n) note(label, n) } <main> <Card title="Fruit"> {for [label, start] in totals => <Counter key={label} label={label} start={start} on_change={changed}/>} <p.sum>Total: <b>{fruit}</b></p> </Card> <Card title="Bulk"> <Counter label="Crates" step={10} on_change={note}/> </Card> <Panel header={<span>Recent changes <small>({len(log)})</small></span>}> {len(log) == 0 ? <p.muted>Nothing yet: press + or −.</p> : <ul>{for entry in log => <li>{entry}</li>}</ul>} </Panel> </main>}let style = css`body { font: 15px/1.5 system-ui, sans-serif; max-width: 34rem; margin: 2rem auto; padding: 0 1rem; color: #222; }.card { border: 1px solid #e3e3e3; border-radius: 10px; padding: .25rem 1rem 1rem; margin-bottom: 1rem; }.card h2 { font-size: 1rem; color: #666; font-weight: 600; }.counter { display: flex; align-items: center; gap: .6rem; padding: .25rem 0; }.counter .label { flex: 1; }.counter .n { min-width: 2.5rem; text-align: center; font-variant-numeric: tabular-nums; }.counter button { width: 2rem; height: 2rem; border: 1px solid #ccc; border-radius: 6px; background: white; font: inherit; cursor: pointer; }.sum { margin: .75rem 0 0; color: #555; }.panel .header { border: none; background: none; font: inherit; font-weight: 600; cursor: pointer; padding: 0; }.panel small, .muted { color: #888; }@media (prefers-color-scheme: dark) { body { background: #161616; color: #ddd; } .card { border-color: #333; } .counter button { background: #222; color: #ddd; border-color: #444; } .panel .header { color: #ddd; }}`route GET / = <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Composition</title> <style>{style}</style> </head> <body> <h1>Composition</h1> <Dashboard/> </body> </html>Thread
Thread 0 posts
On Composition: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.