UrlInput
A web address box that shows the protocol fixed at the start and lets the browser check the shape, with a tick when it looks right.
Demo
https://
No address yet.
The whole address, scheme first.
https://github.com/
That repository does not exist.
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/url_input.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/url_input.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/url_input.qwe
component UrlInputDemoLive() { state url = null <div.demo-stack> <UrlInput name="ui-demo-site" label="Website" protocol="https://" placeholder="example.com" on_change={(u) => { url = u }}/> <p.demo-note>{url == null ? "No address yet." : `The form sends ${url}.`}</p> </div>}view UrlInputDemo() { <div.demo-stack.demo-narrow> <UrlInputDemoLive/> <UrlInput name="ui-demo-link" label="Link" value="https://quillolang.org" hint="The whole address, scheme first."/> <UrlInput name="ui-demo-repo" label="Repository" protocol="https://github.com/" placeholder="owner/repo" size="sm" error="That repository does not exist."/> </div>}Source
ui/url_input.qwe
// UrlInput: a box for a web address, with the protocol shown and the shape// checked by the browser itself (a tick once it looks right; a red border and// a message once it is left wrong). It is a real <input>, so a plain <form>// sends it, and the browser holds the form until the address is a plausible one.//// <UrlInput name="site" label="Website" protocol="https://" placeholder="example.com"/>// <UrlInput name="link" label="Link" value="https://quillolang.org" hint="Paste the whole address."/>//// protocol: shown fixed at the start of the box ("https://"); the box takes the rest// ("example.com/about"), and that is what the form sends: the route puts the// protocol back (`"https://" + form.site`). Without it the box is <input type="url">// and takes the whole address, scheme first.// value, placeholder ("example.com"), autocomplete ("url"), name ("url"), label,// hint, error, required, disabled, field (the box's id, "url-input-" + name by// default) and size ("sm" or "md") as for every field.// on_change(url): inside one of your components, given the whole address (the// protocol put back) when the box is left changed.import ui/theme.qwe use ui_clslet url_input_css = css`.ui-url-input { display: grid; gap: 6px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-url-input-label { font-size: 13px; font-weight: 500; }.ui-url-input-req { margin-left: 2px; color: var(--ui-danger); }.ui-url-input-box { display: flex; align-items: center; gap: 8px; height: 38px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); transition: border-color .15s, box-shadow .15s;}.ui-url-input-box:hover { border-color: var(--ui-strong); }.ui-url-input-box:focus-within { border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-url-input-box:has(input:user-invalid) { border-color: var(--ui-danger); }.ui-url-input-box input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; outline: none; background: transparent; color: var(--ui-fg); font: 14px var(--ui-font);}.ui-url-input-box input::placeholder { color: var(--ui-muted); opacity: 1; }.ui-url-input-protocol { flex: none; color: var(--ui-muted); user-select: none; font-variant-numeric: tabular-nums; }.ui-url-input-globe { flex: none; width: 16px; height: 16px; color: var(--ui-muted); }.ui-url-input-ok { flex: none; width: 16px; height: 16px; color: var(--ui-success); opacity: 0; transition: opacity .15s; }.ui-url-input-box:has(input:valid:not(:placeholder-shown)) .ui-url-input-ok { opacity: 1; }.ui-url-input-sm .ui-url-input-box { height: 30px; padding: 0 10px; }.ui-url-input-sm .ui-url-input-box input { font-size: 13px; }.ui-url-input-invalid .ui-url-input-box, .ui-url-input-invalid .ui-url-input-box:hover { border-color: var(--ui-danger); }.ui-url-input-hint, .ui-url-input-error { margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-url-input-error { color: var(--ui-danger); }.ui-url-input-disabled { opacity: .55; }.ui-url-input-disabled input { cursor: not-allowed; }@media (prefers-reduced-motion: reduce) { .ui-url-input-box, .ui-url-input-ok { transition: none; } }`view UrlInput(name: str = "url", label: str? = null, value: str? = null, protocol: str? = null, placeholder: str = "example.com", autocomplete: str = "url", hint: str? = null, error: str? = null, required: bool = false, disabled: bool = false, field: str? = null, size: str = "md", on_change = null, class: str? = null) { style global url_input_css let id = field ?? `url-input-${name}` let described = ui_cls([protocol != null && `${id}-protocol`, hint != null && `${id}-hint`, error != null && `${id}-error`]) // With the protocol fixed, the rest must be a host with a dot and a path with no spaces. let shape = protocol != null ? "[^\\s\\/:]+\\.[^\\s\\/:]{2,}(\\/\\S*)?" : "https?:\\/\\/[^\\s\\/]+\\.[^\\s\\/]{2,}(\\/\\S*)?" let shown = protocol != null && value != null && starts_with(lower(value), lower(protocol)) ? slice(value, len(protocol)) : value <div class={ui_cls(["ui-url-input", size == "sm" && "ui-url-input-sm", error != null && "ui-url-input-invalid", disabled && "ui-url-input-disabled", class])}> {label != null && <label.ui-url-input-label for={id}> {label}{required && <span.ui-url-input-req aria-hidden="true">*</span>} </label>} <div.ui-url-input-box> {protocol != null ? <span.ui-url-input-protocol id={`${id}-protocol`}>{protocol}</span> : <svg.ui-url-input-globe viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"> <circle cx="10" cy="10" r="7.5"/><path d="M2.5 10h15M10 2.5c2.5 2.5 2.5 12.5 0 15M10 2.5c-2.5 2.5-2.5 12.5 0 15"/> </svg>} <input id={id} type={protocol != null ? "text" : "url"} inputmode="url" name={name} placeholder={placeholder} {...(shown != null ? { value: shown } : {})} pattern={shape} autocomplete={autocomplete} autocapitalize="off" spellcheck="false" required={required} disabled={disabled} title={protocol != null ? `An address such as ${placeholder}` : "A whole address, such as https://example.com"} aria-invalid={error != null ? "true" : null} aria-describedby={described != "" ? described : null} @change={(e) => { on_change && on_change((protocol ?? "") + trim(e.value)) }}/> <svg.ui-url-input-ok viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg> </div> {hint != null && <p.ui-url-input-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-url-input-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 UrlInput: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.