TimezonePicker
A drop-down of time zones grouped by region, each with its UTC offset for today, sending the IANA name with a form.
Demo
Offsets are for today, daylight saving included.
Meetings are set in Asia/Tokyo.
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/timezone_picker.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/timezone_picker.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/timezone_picker.qwe
component TimezonePickerDemoLive() { state zone = "Asia/Tokyo" <div.demo-stack> <TimezonePicker name="meeting_tz" label="Meeting time zone" value={zone} field="timezone-picker-demo-live" zones={["UTC", "Europe/London", "Europe/Berlin", "America/New_York", "America/Los_Angeles", "Asia/Tokyo", "Australia/Sydney"]} on_change={(z) => { zone = z }}/> <p.demo-note>Meetings are set in {zone}.</p> </div>}view TimezonePickerDemo() = <div.demo-stack.demo-narrow> <TimezonePicker name="tz" label="Time zone" value="Europe/Lisbon" field="timezone-picker-demo-all" hint="Offsets are for today, daylight saving included." required={true}/> <TimezonePickerDemoLive/> <div.demo-row> <TimezonePicker name="tz_small" label="Small" size="sm" placeholder="Pick a zone" field="timezone-picker-demo-small" zones={["Europe/Paris", "Asia/Kolkata", "Pacific/Auckland"]}/> <TimezonePicker name="tz_locked" label="Disabled" size="sm" value="UTC" disabled={true} zones={["UTC"]}/> </div> </div>Source
ui/timezone_picker.qwe
// TimezonePicker: a drop-down of time zones grouped by region, each with its UTC// offset ("(UTC+01:00) Paris"). It is the browser's own <select>, so a plain <form>// sends the IANA name ("Europe/Paris") as `name`, and it works with the keyboard.//// <TimezonePicker name="tz" label="Time zone" value="Europe/Lisbon"/>// <TimezonePicker name="tz" label="Time zone" zones={["UTC", "Europe/London", "America/New_York"]}/>//// value: the zone chosen to start with (an IANA name). zones: the names to offer,// in place of the built-in list of about a hundred; a region is the part before// the slash. at: the date-time the offsets are worked out for (now by default), so// daylight saving is right. placeholder: a first line that cannot be chosen.// hint, error, required, disabled, field (the id, default "timezone-picker-" +// name) and size ("sm" or "md") as in Select.// on_change(zone): inside one of your components, given the IANA name chosen.import ui/theme.qwe use ui_clslet timezone_picker_css = css`.ui-timezone-picker { display: grid; gap: 6px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-timezone-picker-label { font-size: 13px; font-weight: 500; color: var(--ui-fg); }.ui-timezone-picker-req { margin-left: 2px; color: var(--ui-danger); }.ui-timezone-picker-box { position: relative; display: block; }.ui-timezone-picker-box::after { content: ""; position: absolute; right: 14px; top: 50%; width: 7px; height: 7px; margin-top: -6px; border: solid var(--ui-muted); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); pointer-events: none;}.ui-timezone-picker select { appearance: none; display: block; width: 100%; height: 38px; padding: 0 36px 0 12px; background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); font-variant-numeric: tabular-nums; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); cursor: pointer; text-overflow: ellipsis; transition: border-color .15s, box-shadow .15s;}.ui-timezone-picker select:hover { border-color: var(--ui-strong); }.ui-timezone-picker select:focus-visible { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-timezone-picker select:has(option[value=""]:checked) { color: var(--ui-muted); }.ui-timezone-picker option { color: var(--ui-fg); background: var(--ui-surface); }.ui-timezone-picker-sm select { height: 30px; padding: 0 30px 0 10px; font-size: 13px; }.ui-timezone-picker-sm .ui-timezone-picker-box::after { right: 11px; width: 6px; height: 6px; margin-top: -5px; }.ui-timezone-picker-invalid select, .ui-timezone-picker-invalid select:hover { border-color: var(--ui-danger); }.ui-timezone-picker-hint, .ui-timezone-picker-error { margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-timezone-picker-error { color: var(--ui-danger); }.ui-timezone-picker-disabled { opacity: .55; }.ui-timezone-picker-disabled select { cursor: not-allowed; }@media (prefers-reduced-motion: reduce) { .ui-timezone-picker select { transition: none; } }`let timezone_picker_zones = [ "UTC", "Africa/Abidjan", "Africa/Accra", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Cairo", "Africa/Casablanca", "Africa/Dar_es_Salaam", "Africa/Johannesburg", "Africa/Khartoum", "Africa/Kinshasa", "Africa/Lagos", "Africa/Nairobi", "Africa/Tunis", "America/Anchorage", "America/Argentina/Buenos_Aires", "America/Asuncion", "America/Bogota", "America/Caracas", "America/Chicago", "America/Denver", "America/Guatemala", "America/Halifax", "America/Havana", "America/La_Paz", "America/Lima", "America/Los_Angeles", "America/Mexico_City", "America/Montevideo", "America/New_York", "America/Panama", "America/Phoenix", "America/Santiago", "America/Sao_Paulo", "America/St_Johns", "America/Toronto", "America/Vancouver", "Asia/Almaty", "Asia/Baghdad", "Asia/Baku", "Asia/Bangkok", "Asia/Beirut", "Asia/Colombo", "Asia/Dhaka", "Asia/Dubai", "Asia/Hong_Kong", "Asia/Jakarta", "Asia/Jerusalem", "Asia/Kabul", "Asia/Karachi", "Asia/Kathmandu", "Asia/Kolkata", "Asia/Kuala_Lumpur", "Asia/Manila", "Asia/Riyadh", "Asia/Seoul", "Asia/Shanghai", "Asia/Singapore", "Asia/Taipei", "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tokyo", "Asia/Yangon", "Asia/Yerevan", "Atlantic/Azores", "Atlantic/Cape_Verde", "Atlantic/Reykjavik", "Australia/Adelaide", "Australia/Brisbane", "Australia/Darwin", "Australia/Hobart", "Australia/Melbourne", "Australia/Perth", "Australia/Sydney", "Europe/Amsterdam", "Europe/Athens", "Europe/Belgrade", "Europe/Berlin", "Europe/Brussels", "Europe/Bucharest", "Europe/Budapest", "Europe/Copenhagen", "Europe/Dublin", "Europe/Helsinki", "Europe/Istanbul", "Europe/Kyiv", "Europe/Lisbon", "Europe/London", "Europe/Madrid", "Europe/Moscow", "Europe/Oslo", "Europe/Paris", "Europe/Prague", "Europe/Rome", "Europe/Stockholm", "Europe/Vienna", "Europe/Warsaw", "Europe/Zurich", "Indian/Maldives", "Indian/Mauritius", "Pacific/Auckland", "Pacific/Fiji", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Port_Moresby", "Pacific/Tongatapu",]// "+01:00" for a zone at the instant `at` ("+00:00" for UTC).fn timezone_picker_offset(zone: str, at: str) -> str { let t = date.to_zone(at, zone) if is_err(t) || t == null { return "+00:00" } let tail = slice(t, len(t) - 6, len(t)) ends_with(t, "Z") ? "+00:00" : tail}// "Buenos Aires" from "America/Argentina/Buenos_Aires".fn timezone_picker_city(zone: str) -> str = replace(last(split(zone, "/")), "_", " ")// Minutes from UTC, from "+05:30", for sorting.fn timezone_picker_minutes(offset: str) -> int { let sign = starts_with(offset, "-") ? -1 : 1 sign * ((int(slice(offset, 1, 3)) ?? 0) * 60 + (int(slice(offset, 4, 6)) ?? 0))}view TimezonePicker(name: str? = null, label: str? = null, value: str? = null, zones: list? = null, at: str? = null, placeholder: str? = null, 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 timezone_picker_css let id = field ?? `timezone-picker-${name ?? "zone"}` let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`]) let when = at ?? date.now() let rows = (zones ?? timezone_picker_zones) | map((z) => { let off = timezone_picker_offset(z, when) { zone: z, region: contains(z, "/") ? first(split(z, "/")) : "Other", city: timezone_picker_city(z), offset: off, minutes: timezone_picker_minutes(off) } }) let regions = rows | map((r) => r.region) | unique let opt = (r) => <option value={r.zone} selected={r.zone == value}>{`(UTC${r.offset}) ${r.city}`}</option> <div class={ui_cls(["ui-timezone-picker", size == "sm" && "ui-timezone-picker-sm", error != null && "ui-timezone-picker-invalid", disabled && "ui-timezone-picker-disabled", class])}> {label != null && <label.ui-timezone-picker-label for={id}> {label}{required && <span.ui-timezone-picker-req aria-hidden="true">*</span>} </label>} <span.ui-timezone-picker-box> <select id={id} name={name} required={required} disabled={disabled} aria-invalid={error != null ? "true" : null} aria-describedby={described != "" ? described : null} @change={(e) => { on_change && on_change(e.value) }}> {placeholder != null && <option value="" disabled selected={value == null}>{placeholder}</option>} {for g in regions => { let mine = rows | filter((r) => r.region == g) | sort_by((r) => r.city) | sort_by((r) => r.minutes) g == "Other" ? (mine | map(opt)) : <optgroup label={g}>{for r in mine => opt(r)}</optgroup> }} </select> </span> {hint != null && <p.ui-timezone-picker-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-timezone-picker-error id={`${id}-error`} role="alert">{error}</p>} </div>}Related
BirthdayInputDay, month and year drop-downs that give one date in a hidden input, the days following the month and the years the age allowed.5 views · 7 downloadsCountryPickerA drop-down of every country with its flag and ISO code, favourites first, sending the two-letter code with a form.5 views · 7 downloadsFontPickerA drop-down of fonts, each option drawn in itself, with a preview line in the one chosen.5 views · 7 downloadsLanguagePickerA drop-down of languages each written in its own name, sending the ISO 639-1 code with a form.5 views · 5 downloadsMultiSelectSeveral choices from a searchable drop-down of checkboxes, shown as chips that come off with ×.5 views · 7 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 TimezonePicker: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.