WeatherStrip
The weather for a news page: the temperature now with the sky drawn, the high and low, the hours or days ahead with rain chances, and a °C/°F switch.
Demo
- NowPartly cloudy, 18°
- 3 PMSunny, 20°
- 4 PMShowers, 19°, chance of rain 40%
- 5 PMRain, 17°, chance of rain 80%
- 6 PMThunderstorms, 16°, chance of rain 70%
- 7 PMCloudy, 15°
- 8 PMPartly cloudy, 13°
- 9 PMClear, 12°
Unit not switched yet.
ChicagoNow -2° CelsiusLight snow · H -1° L -7°
- ThuSnow, -1° -7°, chance of rain 60%
- FriSleet, 2° -4°, chance of rain 30%
- SatFog, 5° -1°
- SunWindy, 7° 1°
- MonSunny, 4° -2°
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/weather_strip.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/weather_strip.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/weather_strip.qwe
component WeatherStripDemoLive() { state note = "Unit not switched yet." <div.demo-stack> <WeatherStrip id="weather-strip-demo-lisbon" place="Lisbon" temp={18} condition="partly" high={21} low={12} feels={17} unit="C" updated="Forecast at 14:00" href="#weather-lisbon" on_unit={(u) => { note = `on_unit gave ${u}` }} hours={[ { label: "Now", temp: 18, condition: "partly" }, { label: "3 PM", temp: 20, condition: "sun" }, { label: "4 PM", temp: 19, condition: "showers", rain: 40 }, { label: "5 PM", temp: 17, condition: "rain", rain: 80 }, { label: "6 PM", temp: 16, condition: "storm", rain: 70 }, { label: "7 PM", temp: 15, condition: "cloud", rain: 5 }, { label: "8 PM", temp: 13, condition: "partly_night" }, { label: "9 PM", temp: 12, condition: "clear_night" }, ]}/> <p.demo-note id="weather-strip-demo-note">{note}</p> </div>}view WeatherStripDemo() = <div.demo-stack> <WeatherStripDemoLive/> <WeatherStrip id="weather-strip-demo-chicago" place="Chicago" temp={28} condition="snow" summary="Light snow" high={31} low={19} unit="F" show="C" remember={true} hours={[ { label: "Thu", high: 31, low: 19, condition: "snow", rain: 60 }, { label: "Fri", high: 35, low: 24, condition: "sleet", rain: 30 }, { label: "Sat", high: 41, low: 30, condition: "fog" }, { label: "Sun", high: 44, low: 33, condition: "wind" }, { label: "Mon", high: 39, low: 28, condition: "sun" }, ]}/> </div>Source
ui/weather_strip.qwe
// WeatherStrip: the weather in a news site's header or on its front: the place, the// temperature now with a picture of the sky, the day's high and low, and the hours// (or days) ahead in a row that scrolls sideways where it is narrow, each with its// chance of rain. °C and °F switch every temperature at once, and the choice can be// remembered in this browser. The forecast is data given to it: it fetches nothing.//// <WeatherStrip place="Lisbon" temp={18} condition="partly" high={21} low={12} unit="C" href="/weather/lisbon"// hours={[{ label: "3 PM", temp: 20, condition: "sun" }, { label: "4 PM", temp: 19, condition: "showers", rain: 40 }]}/>//// place: where. temp: the temperature now, in unit; high and low: the day's. unit:// "C" (default) or "F", that of the numbers given; show: the one to show first (unit).// condition: the sky now: "sun", "clear_night", "partly", "partly_night", "cloud",// "rain", "showers", "storm", "snow", "sleet", "fog" or "wind"; summary: how it reads// ("Sunny intervals"; each condition has its own words). hours: maps of { label, temp,// condition, rain? } (rain: the chance, 0 to 100), hours or days ("Tue"); for days,// give high and low instead of temp. feels: the "feels like" temperature. updated:// when the forecast is from. href: the full forecast, a link on the place. toggle: the// °C/°F switch (true). remember: keep the unit picked in this browser (localStorage),// for every strip on the site. on_unit(unit): inside one of your components, given// "C" or "F" when it is switched. id: the element's id.import ui/theme.qwe use ui_clslet weather_strip_css = css`.ui-wx { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 12px 20px; min-width: 0; padding: 14px 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.35 var(--ui-font); font-variant-numeric: tabular-nums; }.ui-wx-now { display: grid; grid-template-columns: auto auto; align-items: center; gap: 0 12px; }.ui-wx-now > .ui-wx-icon { grid-row: span 3; width: 52px; height: 52px; }.ui-wx-place { display: flex; align-items: baseline; gap: 8px; font-size: 13px; font-weight: 650; }.ui-wx-place a { color: inherit; text-decoration: none; }.ui-wx-place a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-wx-temp { font-size: 34px; line-height: 1.05; font-weight: 750; letter-spacing: -.02em; }.ui-wx-desc { color: var(--ui-muted); font-size: 13px; }.ui-wx-desc b { color: var(--ui-fg); font-weight: 600; }.ui-wx-side { display: grid; gap: 8px; min-width: 0; }.ui-wx-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; color: var(--ui-muted); font-size: 12px; }.ui-wx-units { display: inline-flex; padding: 2px; border-radius: 999px; background: var(--ui-subtle); }.ui-wx-units button { min-width: 38px; height: 26px; padding: 0 8px; border: 0; border-radius: 999px; background: transparent; color: var(--ui-muted); font: 650 12px var(--ui-font); cursor: pointer; }.ui-wx-units button[aria-pressed="true"] { background: var(--ui-surface); color: var(--ui-fg); box-shadow: 0 1px 2px rgb(0 0 0 / .12); }.ui-wx button:focus-visible, .ui-wx a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 999px; }.ui-wx-hours { display: flex; gap: 4px; margin: 0; padding: 0 0 2px; list-style: none; overflow-x: auto; scroll-snap-type: x proximity; scrollbar-width: thin; }.ui-wx-hours li { flex: 1 0 58px; display: grid; justify-items: center; gap: 3px; padding: 6px 4px; border-radius: var(--ui-radius-sm); scroll-snap-align: start; text-align: center; }.ui-wx-hours li:first-child { background: var(--ui-subtle); }.ui-wx-when { color: var(--ui-muted); font-size: 12px; white-space: nowrap; }.ui-wx-hours .ui-wx-icon { width: 26px; height: 26px; }.ui-wx-t { font-weight: 650; white-space: nowrap; }.ui-wx-t small { color: var(--ui-muted); font-weight: 500; }.ui-wx-rain { color: var(--ui-info); font-size: 11px; font-weight: 600; }.ui-wx-rain-none { visibility: hidden; }.ui-wx-icon { display: block; overflow: visible; }.ui-wx-icon .ui-wx-sun { fill: var(--ui-warning); }.ui-wx-icon .ui-wx-ray { stroke: var(--ui-warning); stroke-width: 1.6; stroke-linecap: round; }.ui-wx-icon .ui-wx-moon { fill: color-mix(in srgb, var(--ui-warning) 45%, var(--ui-muted)); }.ui-wx-icon .ui-wx-cloud { fill: var(--ui-surface); stroke: var(--ui-muted); stroke-width: 1.4; }.ui-wx-icon .ui-wx-dark { fill: color-mix(in srgb, var(--ui-muted) 35%, var(--ui-surface)); }.ui-wx-icon .ui-wx-drop { stroke: var(--ui-info); stroke-width: 1.6; stroke-linecap: round; }.ui-wx-icon .ui-wx-flake { fill: var(--ui-info); }.ui-wx-icon .ui-wx-bolt { fill: var(--ui-warning); }.ui-wx-icon .ui-wx-line { stroke: var(--ui-muted); stroke-width: 1.6; stroke-linecap: round; fill: none; }@media (max-width: 560px) { .ui-wx { grid-template-columns: minmax(0, 1fr); } }`// The words for a condition.fn weather_strip_words(c: str?) -> str = match c { "sun" => "Sunny" "clear_night" => "Clear" "partly" => "Partly cloudy" "partly_night" => "Partly cloudy" "cloud" => "Cloudy" "rain" => "Rain" "showers" => "Showers" "storm" => "Thunderstorms" "snow" => "Snow" "sleet" => "Sleet" "fog" => "Fog" "wind" => "Windy" _ => ""}// A temperature given in `from` ("C" or "F") as a whole number in `to`.fn weather_strip_temp(t, from: str, to: str) -> int = from == to ? round(t) : (to == "F" ? round(t * 9 / 5 + 32) : round((t - 32) * 5 / 9))// A coordinate as text, to one decimal, written alike on the server and in the browser: 9, 12.5.fn weather_strip_n(n: num) -> str { let r = round(n * 10) r % 10 == 0 ? str(floor(r / 10)) : str(r / 10)}// A picture of the sky for a condition, in a 24 by 24 box.fn weather_strip_icon(c: str?) { let cloud = (dark: bool) => <path class={dark ? "ui-wx-cloud ui-wx-dark" : "ui-wx-cloud"} d="M7 18.5h10.5a3.5 3.5 0 0 0 .3-7 5 5 0 0 0-9.6-1.2A4 4 0 0 0 7 18.5z"/> let sun = (x: num, y: num, r: num) => [ <g>{for i in range(8) => { let a = i * math.pi / 4 <line.ui-wx-ray x1={weather_strip_n(x + (r + 2) * math.cos(a))} y1={weather_strip_n(y + (r + 2) * math.sin(a))} x2={weather_strip_n(x + (r + 4) * math.cos(a))} y2={weather_strip_n(y + (r + 4) * math.sin(a))}/> }}</g>, <circle.ui-wx-sun cx={weather_strip_n(x)} cy={weather_strip_n(y)} r={weather_strip_n(r)}/>, ] let moon = <path.ui-wx-moon d="M10 3.5a6.5 6.5 0 1 0 7.5 9A5.5 5.5 0 0 1 10 3.5z"/> let drops = (n: int) => for i in range(n) => <line.ui-wx-drop x1={weather_strip_n(9 + i * 3.5)} y1="20.5" x2={weather_strip_n(8 + i * 3.5)} y2="23"/> <svg class="ui-wx-icon" viewBox="0 0 24 24" aria-hidden="true"> {match c { "sun" => sun(12, 12, 4.5) "clear_night" => moon "partly" => [sun(9, 8, 3.5), cloud(false)] "partly_night" => [<g transform="translate(-2 -2) scale(.8)">{moon}</g>, cloud(false)] "rain" => [cloud(true), drops(3)] "showers" => [sun(9, 7, 3), cloud(false), drops(2)] "storm" => [cloud(true), <path.ui-wx-bolt d="M12.5 17l-2.5 4h2.5l-1 3 3.5-4.5h-2.5l1-2.5z"/>] "snow" => [cloud(false), for i in range(3) => <circle.ui-wx-flake cx={weather_strip_n(9 + i * 3.5)} cy="21.5" r="1.1"/>] "sleet" => [cloud(false), <line.ui-wx-drop x1="9.5" y1="20.5" x2="8.5" y2="23"/>, <circle.ui-wx-flake cx="14" cy="21.5" r="1.1"/>] "fog" => <g>{for i in range(4) => <line.ui-wx-line x1={weather_strip_n(4 + (i % 2) * 2)} y1={weather_strip_n(7 + i * 3.5)} x2={weather_strip_n(20 - (i % 2) * 2)} y2={weather_strip_n(7 + i * 3.5)}/>}</g> "wind" => <path.ui-wx-line d="M3 9h11a2.5 2.5 0 1 0-2.5-2.5M3 13h15a2.5 2.5 0 1 1-2.5 2.5M3 17h8"/> _ => cloud(false) }} </svg>}component WeatherStrip(place: str, temp, condition: str = "cloud", summary: str? = null, high = null, low = null, feels = null, hours: list = [], unit: str = "C", show: str? = null, updated: str? = null, href: str? = null, toggle: bool = true, remember: bool = false, on_unit = null, id: str? = null, class: str? = null) { style global weather_strip_css state shown = show ?? unit let deg = (v) => `${weather_strip_temp(v, unit, shown)}°` let pick = (u: str) => { if u != shown { shown = u if remember { js("localStorage.setItem", "ui-weather-unit", u) } if on_unit != null { on_unit(u) } } } let words = summary ?? weather_strip_words(condition) <section class={ui_cls(["ui-wx", class])} id={id} aria-label={`Weather in ${place}`} data-unit={shown} @mount={(_el) => { if remember { let saved = js("localStorage.getItem", "ui-weather-unit") if saved == "C" || saved == "F" { shown = saved } } }}> <div.ui-wx-now> {weather_strip_icon(condition)} <span.ui-wx-place>{href != null ? <a href={href}>{place}</a> : place}</span> <span.ui-wx-temp><span.ui-sr>Now </span>{deg(temp)}<span.ui-sr>{shown == "F" ? " Fahrenheit" : " Celsius"}</span></span> <span.ui-wx-desc> <b>{words}</b> {high != null && low != null && [" · ", <span>H {deg(high)} L {deg(low)}</span>]} {feels != null && [" · ", <span>Feels like {deg(feels)}</span>]} </span> </div> <div.ui-wx-side> {(toggle || updated != null) && <div.ui-wx-bar> <span>{updated}</span> {toggle && <div.ui-wx-units role="group" aria-label="Temperature unit"> <button type="button" aria-pressed={shown == "C" ? "true" : "false"} @click={() => pick("C")}><span aria-hidden="true">°C</span><span.ui-sr>Celsius</span></button> <button type="button" aria-pressed={shown == "F" ? "true" : "false"} @click={() => pick("F")}><span aria-hidden="true">°F</span><span.ui-sr>Fahrenheit</span></button> </div>} </div>} {len(hours) > 0 && <ol.ui-wx-hours aria-label="Forecast"> {for h in hours => <li> <span.ui-wx-when>{h.label}</span> {weather_strip_icon(h.condition)} <span.ui-sr>{weather_strip_words(h.condition)}, </span> <span.ui-wx-t>{h.temp != null ? deg(h.temp) : [deg(h.high), " ", <small>{deg(h.low)}</small>]}</span> <span class={ui_cls(["ui-wx-rain", (h.rain == null || h.rain < 10) && "ui-wx-rain-none"])}> {h.rain != null && h.rain >= 10 && [<span.ui-sr>, chance of rain </span>, `${h.rain}%`]} </span> </li>} </ol>} </div> </section>}Related
AdSlotAn advertisement's place kept at its size so the story never jumps: standard sizes by name, another on phones, a clear label, a placeholder, sticky in a rail.6 views · 8 downloadsArticleHeaderThe top of a story: its label and section, the headline, the dek, every author grouped by role, published and updated times, and the reading time.8 views · 8 downloadsBreakingNewsA breaking-news banner: the label in a block of colour, the headline and a line more, how long ago it came in, kept up to date, and a close that can be remembered.9 views · 8 downloadsCorrectionNoticeA correction, clarification, update, editor's note or retraction on a story, dated, with a link to the corrections policy; boxed on top or a line at the end.8 views · 7 downloadsElectionResultsOne race's results: candidates in party colours with votes and share bars, the share of the vote counted, the margin, and the winner once it is called.8 views · 8 downloadsExtractiveSummaryKeeps a text's best sentences, scored by word frequency and position, in their order, with a length slider and the chosen ones marked. No AI service.2 views · 6 downloads
Thread
Thread 0 posts
On WeatherStrip: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.