NewsletterForm
An email box and a Subscribe button in one row, a plain form posting to your route, with a done state once it worked.
Demo
Not sent yet.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/newsletter_form.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/newsletter_form.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/newsletter_form.qwe
component NewsletterFormDemoLive() { state sent = null <div.demo-stack> {sent == null ? <NewsletterForm action="#" id="newsletter-form-demo-live" title="Stay in the loop" text="One email a month, no more." button="Join" on_submit={(email) => { sent = email }}/> : <NewsletterForm done={true} id="newsletter-form-demo-done" title="Stay in the loop"/>} <p.demo-note>{sent == null ? "Not sent yet." : `on_submit got ${sent}.`}</p> </div>}view NewsletterFormDemo() = <div.demo-stack> <NewsletterForm action="/subscribe" list="product" id="newsletter-form-demo-plain"/> <NewsletterFormDemoLive/> <NewsletterForm action="/subscribe" small={true} value="ada@example" id="newsletter-form-demo-error" error="That does not look like an email address."/> </div>Source
ui/newsletter_form.qwe
// NewsletterForm: an email box and a Subscribe button in one row, as a plain// <form method="post"> to your route, and a done state to show once it worked.//// <NewsletterForm action="/subscribe"/>// <NewsletterForm action="/subscribe" title="Stay in the loop" text="One email a month, no more."// button="Join" placeholder="you@work.com"/>// <NewsletterForm done={true}/>// <NewsletterForm action="/subscribe" value={email} error="That does not look like an email address."/>//// action: where it posts (`name` "email"; also `list` when given, to tell lists// apart). title and text: a heading and a line above the row (none by default).// button ("Subscribe"), placeholder ("you@example.com"), label (the box's name for// screen readers, "Email address"). value: an email to fill in again. error: a// message under the row; it also marks the box aria-invalid. done: shows done_text// ("Thanks, you're subscribed. Check your inbox to confirm.") in place of the row.// small (false): the box and button in the small size. id: the box's id, unique on// the page ("newsletter-form-email"). The route reads { form: { email: str } }.// on_submit(email): inside one of your components, called with the email when the// form is sent, in place of posting it (send it yourself, with a server fn, then// show done). Without on_submit the form posts to `action`.import ui/theme.qwe use ui_clslet newsletter_form_css = css`.ui-newsletter-form { display: grid; gap: 10px; min-width: 0; font: 14px/1.45 var(--ui-font); color: var(--ui-fg); }.ui-newsletter-form-title { margin: 0; font-size: 18px; font-weight: 650; line-height: 1.25; }.ui-newsletter-form-text { margin: 0; color: var(--ui-muted); }.ui-newsletter-form-fields { display: contents; }.ui-newsletter-form-form { display: flex; gap: 8px; margin: 0; min-width: 0; }.ui-newsletter-form-input { flex: 1; min-width: 0; height: 40px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); transition: border-color .15s, box-shadow .15s;}.ui-newsletter-form-input::placeholder { color: var(--ui-muted); opacity: 1; }.ui-newsletter-form-input:hover { border-color: var(--ui-strong); }.ui-newsletter-form-input:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-newsletter-form-input[aria-invalid="true"] { border-color: var(--ui-danger); }.ui-newsletter-form-button { flex: none; display: inline-flex; align-items: center; justify-content: center; height: 40px; padding: 0 18px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 500 14px var(--ui-font); white-space: nowrap; cursor: pointer; transition: background .15s, box-shadow .15s;}.ui-newsletter-form-button:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-newsletter-form-button:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-newsletter-form-sm .ui-newsletter-form-input, .ui-newsletter-form-sm .ui-newsletter-form-button { height: 32px; font-size: 13px; }.ui-newsletter-form-sm .ui-newsletter-form-button { padding: 0 12px; }.ui-newsletter-form-error { margin: 0; font-size: 13px; color: var(--ui-danger); }.ui-newsletter-form-done { display: flex; align-items: center; gap: 10px; margin: 0; padding: 10px 14px; border-radius: var(--ui-radius-sm); background: var(--ui-success-soft); color: var(--ui-success); font-weight: 500;}.ui-newsletter-form-done svg { flex: none; width: 18px; height: 18px; }@media (max-width: 420px) { .ui-newsletter-form-form { flex-direction: column; } .ui-newsletter-form-button { width: 100%; } }@media (prefers-reduced-motion: reduce) { .ui-newsletter-form-input, .ui-newsletter-form-button { transition: none; } }`view NewsletterForm(action: str = "/subscribe", title: str? = null, text: str? = null, button: str = "Subscribe", placeholder: str = "you@example.com", label: str = "Email address", value: str? = null, error: str? = null, done: bool = false, done_text: str = "Thanks, you're subscribed. Check your inbox to confirm.", list: str? = null, small: bool = false, id: str = "newsletter-form-email", on_submit = null, class: str? = null) { style global newsletter_form_css let fields = <div.ui-newsletter-form-fields> {list != null && <input type="hidden" name="list" value={list}/>} <input.ui-newsletter-form-input type="email" id={id} name="email" value={value} placeholder={placeholder} required={true} autocomplete="email" autocapitalize="off" spellcheck="false" aria-label={label} aria-invalid={error != null ? "true" : null} aria-describedby={error != null ? `${id}-error` : null}/> <button.ui-newsletter-form-button type="submit">{button}</button> </div> <div class={ui_cls(["ui-newsletter-form", small && "ui-newsletter-form-sm", class])}> {title != null && <h3.ui-newsletter-form-title>{title}</h3>} {text != null && <p.ui-newsletter-form-text>{text}</p>} {done ? <p.ui-newsletter-form-done role="status"> <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10" cy="10" r="8"/><path d="M6.5 10.5l2.5 2.5 4.5-5"/></svg> {done_text} </p> : (on_submit != null ? <form.ui-newsletter-form-form method="post" action={action} @submit={(e) => { on_submit && on_submit(e.form.email ?? "") }}>{fields}</form> : <form.ui-newsletter-form-form method="post" action={action}>{fields}</form>)} {!done && error != null && <p.ui-newsletter-form-error id={`${id}-error`} role="alert">{error}</p>} </div>}Related
AddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloadsAutocompleteA free-text box that offers matches as one types, with the arrow keys and Enter; whatever is typed may stay.5 views · 7 downloadsCardInputA payment card as it is typed: the number grouped, the brand, MM/YY and the code, with typos said.6 views · 6 downloadsCharacterCounterA text box or textarea with a live 120 / 280 count that warns near the limit, as a hard stop or a soft one that blocks sending.5 views · 6 downloadsCheckboxA checkbox drawn by CSS on a real input, with an indeterminate state, and a group of them in a fieldset.5 views · 6 downloadsCheckboxCardsAny number of choices as cards with a title, a line of detail and a check, each a real checkbox a form sends.5 views · 6 downloads
Thread
Thread 0 posts
On NewsletterForm: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.