08一度押せばカード内の全温度が換算される単位トグル
摂氏・華氏を選ぶと大きい温度・時間マス・週間帯の数字が同じ瞬間にすべて換算され、選んだ単位は次に開いてもそのままです。
使いどころグローバルサービスの単位設定、旅行者向けの天気カード
localStoragearia-pressedMath.round

<main class="stage stage--paper">
<section class="ut is-demo" id="ut" aria-label="温度の単位を選ぶ">
<header class="ut__head">
<p class="ut__title">ソウルの気温</p>
<div class="ut__seg" role="group" aria-label="温度の単位">
<span class="ut__thumb" aria-hidden="true"></span>
<button class="ut__u is-on" type="button" data-unit="c" aria-pressed="true">°C</button>
<button class="ut__u" type="button" data-unit="f" aria-pressed="false">°F</button>
</div>
</header>
<p class="ut__big"><output class="ut__swap"><span class="ut__v ut-c" data-c="24">24</span><span class="ut__alt" aria-hidden="true">75</span></output><span class="ut__deg ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></p>
<ul class="ut__hours">
<li class="ut__h"><span class="ut__hl">今</span><span class="ut__ht ut__swap"><span class="ut__v ut-c" data-c="24">24</span><span class="ut__alt" aria-hidden="true">75</span></span><span class="ut__hs ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
<li class="ut__h"><span class="ut__hl">15時</span><span class="ut__ht ut__swap"><span class="ut__v ut-c" data-c="25">25</span><span class="ut__alt" aria-hidden="true">77</span></span><span class="ut__hs ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
<li class="ut__h"><span class="ut__hl">16時</span><span class="ut__ht ut__swap"><span class="ut__v ut-c" data-c="23">23</span><span class="ut__alt" aria-hidden="true">73</span></span><span class="ut__hs ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
<li class="ut__h"><span class="ut__hl">17時</span><span class="ut__ht ut__swap"><span class="ut__v ut-c" data-c="21">21</span><span class="ut__alt" aria-hidden="true">70</span></span><span class="ut__hs ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
</ul>
<ul class="ut__days">
<li class="ut__d"><span class="ut__dl">今日</span><span class="ut__dv ut__swap"><span class="ut__v ut-c" data-c="18">18</span><span class="ut__alt" aria-hidden="true">64</span></span><span class="ut__dd">/</span><span class="ut__dv ut__swap"><span class="ut__v ut-c" data-c="27">27</span><span class="ut__alt" aria-hidden="true">81</span></span><span class="ut__ds ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
<li class="ut__d"><span class="ut__dl">土曜日</span><span class="ut__dv ut__swap"><span class="ut__v ut-c" data-c="17">17</span><span class="ut__alt" aria-hidden="true">63</span></span><span class="ut__dd">/</span><span class="ut__dv ut__swap"><span class="ut__v ut-c" data-c="29">29</span><span class="ut__alt" aria-hidden="true">84</span></span><span class="ut__ds ut__swap"><span class="ut__v ut-suffix">°C</span><span class="ut__alt" aria-hidden="true">°F</span></span></li>
</ul>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 단위 토글 — 고르면 카드 안 모든 숫자가 같은 순간에 환산된다.
// 미리보기 루프도 카드의 모든 숫자·접미사를 한 컷(steps(1,end))에 같이 바꾼다 — 큰 온도만 바꾸면
// 「75°F 옆에 24°C 칸」이라는 실사용에 없는 장면이 poster 로 뽑힌다. 실제 환산은 JS 가 전 칸에 한다.
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-paper);
.ut {
box-sizing: border-box;
display: grid;
gap: $sp-2;
width: min(264px, 92vw);
padding: $sp-3;
border: 1px solid rgba(23, 20, 26, .12);
border-radius: $r-card;
background: $stage-paper;
color: $subject-ink;
box-shadow: $shadow-press;
}
.ut * { box-sizing: border-box; }
.ut__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: $sp-2;
}
.ut__title {
margin: 0;
font-size: 13px;
font-weight: 700;
line-height: 1.3;
}
// 두 단위 세그먼트 — 엄지가 칸을 옮겨 다닌다 (translateX 고정폭, 칸은 2칸 grid)
.ut__seg {
position: relative;
display: grid;
grid-template-columns: repeat(2, 36px);
padding: $sp-1;
border-radius: $r-pill;
background: rgba(23, 20, 26, .08);
}
.ut__thumb {
position: absolute;
top: $sp-1;
left: $sp-1;
width: 36px;
height: calc(100% - 8px);
border-radius: $r-pill;
background: $subject-ink;
transition: transform $dur-quick $easing;
}
.ut.is-f .ut__thumb { transform: translateX(36px); }
.ut__u {
position: relative;
z-index: 1;
padding: $sp-1 0;
border: 0;
background: none;
color: rgba($subject-ink, .72);
font-size: 11px;
font-weight: 700;
line-height: 1.3;
font-variant-numeric: tabular-nums;
cursor: pointer;
&:focus-visible { outline: 2px solid $subject-blue; outline-offset: 2px; border-radius: $r-pill; }
&.is-on { color: $subject-cream; }
}
.ut__big {
display: flex;
align-items: flex-start;
margin: 0;
font-size: 44px;
font-weight: 800;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.ut__deg {
font-size: 20px;
font-weight: 700;
}
.ut__hours {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: $sp-1;
margin: 0;
padding: 0;
list-style: none;
}
.ut__h {
display: grid;
justify-items: center;
gap: $sp-1;
padding: $sp-1 0;
border-radius: $r-control;
background: rgba(110, 200, 255, .2);
font-variant-numeric: tabular-nums;
}
.ut__hl {
font-size: 9px;
font-weight: 700;
line-height: 1;
color: rgba($subject-ink, .72);
}
.ut__ht {
font-size: 13px;
font-weight: 800;
line-height: 1.2;
}
.ut__hs {
font-size: 9px;
font-weight: 500;
line-height: 1;
color: rgba($subject-ink, .72);
}
.ut__days {
display: grid;
gap: $sp-1;
margin: 0;
padding: 0;
list-style: none;
}
.ut__d {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: $sp-2;
font-variant-numeric: tabular-nums;
}
.ut__dl {
font-size: 10px;
font-weight: 700;
line-height: 1.3;
color: rgba($subject-ink, .72);
}
.ut__dv {
font-size: 12px;
font-weight: 700;
line-height: 1.3;
}
.ut__dd { color: rgba($subject-ink, .72); }
.ut__ds {
font-size: 10px;
color: rgba($subject-ink, .72);
}
// 겹쳐 둔 두 번째 값 — 미리보기 루프에서만 보인다
.ut__swap { position: relative; display: inline-block; }
.ut__alt {
position: absolute;
inset: 0;
opacity: 0;
}
// ── 격자 미리보기 루프 (2초) ──
// 0~44% 섭씨 → 45% 컷으로 화씨(엄지 이동·강조 맞바뀜 함께) → 88% 섭씨 복귀.
@keyframes ut-va { 0%, 44% { opacity: 1; } 45%, 87% { opacity: 0; } 88%, 100% { opacity: 1; } }
@keyframes ut-vb { 0%, 44% { opacity: 0; } 45%, 87% { opacity: 1; } 88%, 100% { opacity: 0; } }
@keyframes ut-thumb {
0%, 42% { transform: translateX(0); }
56%, 84% { transform: translateX(36px); }
96%, 100% { transform: translateX(0); }
}
@keyframes ut-suffix {
0%, 44% { opacity: 1; }
45%, 87% { opacity: 0; }
88%, 100% { opacity: 1; }
}
@keyframes ut-pressf {
0%, 41% { transform: scale(1); }
45% { transform: scale(.9); }
57%, 100% { transform: scale(1); }
}
// 강조도 같은 컷에 맞바뀐다 — 엄지만 옮기면 엄지 밑 글자가 어두운 채로 묻히고
// 반대쪽 크림 글자는 밝은 바닥에서 사라져 토글이 빈 알약으로 찍힌다(poster 실측).
@keyframes ut-labelc {
0%, 44% { color: $subject-cream; }
45%, 87% { color: rgba($subject-ink, .72); }
88%, 100% { color: $subject-cream; }
}
@keyframes ut-labelf {
0%, 44% { color: rgba($subject-ink, .72); }
45%, 87% { color: $subject-cream; }
88%, 100% { color: rgba($subject-ink, .72); }
}
.ut.is-demo {
.ut__swap .ut__v { animation-name: ut-va; }
.ut__swap .ut__alt { animation-name: ut-vb; }
.ut__swap .ut__v, .ut__swap .ut__alt {
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ut__thumb {
animation-name: ut-thumb;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
// 접미사 °C/°F 도 숫자와 같은 컷 스왑 — 겹쳐 보이는 순간이 없게
.ut__deg .ut__v, .ut__deg .ut__alt {
animation-name: ut-va, ut-vb;
animation-duration: $duration, $duration;
animation-timing-function: steps(1, end), steps(1, end);
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
.ut__deg .ut__v { animation-name: ut-va; }
.ut__deg .ut__alt { animation-name: ut-vb; }
.ut__seg .ut__u[data-unit='c'] {
animation-name: ut-labelc;
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ut__seg .ut__u[data-unit='f'] {
animation-name: ut-pressf, ut-labelf;
animation-duration: $duration, $duration;
animation-timing-function: $easing, steps(1, end);
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
}
@media (max-width: 340px) {
.ut { padding: $sp-1 $sp-2; gap: $sp-1; }
.ut__big { font-size: 30px; }
.ut__deg { font-size: 16px; }
.ut__h { padding: 0; }
.ut__d { gap: $sp-1; }
.ut__dv { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.ut.is-demo .ut__swap .ut__v,
.ut.is-demo .ut__swap .ut__alt,
.ut.is-demo .ut__thumb,
.ut.is-demo .ut__u {
animation: none !important;
transform: none !important;
}
.ut.is-demo .ut__swap .ut__v { opacity: 1 !important; }
.ut.is-demo .ut__swap .ut__alt { opacity: 0 !important; }
.ut__thumb { transition: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ut {
box-sizing: border-box;
display: grid;
gap: 8px;
width: min(264px, 92vw);
padding: 12px;
border: 1px solid rgba(23, 20, 26, 0.12);
border-radius: 16px;
background: #ffffff;
color: #17141a;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.ut * {
box-sizing: border-box;
}
.ut__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.ut__title {
margin: 0;
font-size: 13px;
font-weight: 700;
line-height: 1.3;
}
.ut__seg {
position: relative;
display: grid;
grid-template-columns: repeat(2, 36px);
padding: 4px;
border-radius: 999px;
background: rgba(23, 20, 26, 0.08);
}
.ut__thumb {
position: absolute;
top: 4px;
left: 4px;
width: 36px;
height: calc(100% - 8px);
border-radius: 999px;
background: #17141a;
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ut.is-f .ut__thumb {
transform: translateX(36px);
}
.ut__u {
position: relative;
z-index: 1;
padding: 4px 0;
border: 0;
background: none;
color: rgba(23, 20, 26, 0.72);
font-size: 11px;
font-weight: 700;
line-height: 1.3;
font-variant-numeric: tabular-nums;
cursor: pointer;
}
.ut__u:focus-visible {
outline: 2px solid #2f6df6;
outline-offset: 2px;
border-radius: 999px;
}
.ut__u.is-on {
color: #fff7e6;
}
.ut__big {
display: flex;
align-items: flex-start;
margin: 0;
font-size: 44px;
font-weight: 800;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.ut__deg {
font-size: 20px;
font-weight: 700;
}
.ut__hours {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
margin: 0;
padding: 0;
list-style: none;
}
.ut__h {
display: grid;
justify-items: center;
gap: 4px;
padding: 4px 0;
border-radius: 12px;
background: rgba(110, 200, 255, 0.2);
font-variant-numeric: tabular-nums;
}
.ut__hl {
font-size: 9px;
font-weight: 700;
line-height: 1;
color: rgba(23, 20, 26, 0.72);
}
.ut__ht {
font-size: 13px;
font-weight: 800;
line-height: 1.2;
}
.ut__hs {
font-size: 9px;
font-weight: 500;
line-height: 1;
color: rgba(23, 20, 26, 0.72);
}
.ut__days {
display: grid;
gap: 4px;
margin: 0;
padding: 0;
list-style: none;
}
.ut__d {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
font-variant-numeric: tabular-nums;
}
.ut__dl {
font-size: 10px;
font-weight: 700;
line-height: 1.3;
color: rgba(23, 20, 26, 0.72);
}
.ut__dv {
font-size: 12px;
font-weight: 700;
line-height: 1.3;
}
.ut__dd {
color: rgba(23, 20, 26, 0.72);
}
.ut__ds {
font-size: 10px;
color: rgba(23, 20, 26, 0.72);
}
.ut__swap {
position: relative;
display: inline-block;
}
.ut__alt {
position: absolute;
inset: 0;
opacity: 0;
}
@keyframes ut-va {
0%, 44% {
opacity: 1;
}
45%, 87% {
opacity: 0;
}
88%, 100% {
opacity: 1;
}
}
@keyframes ut-vb {
0%, 44% {
opacity: 0;
}
45%, 87% {
opacity: 1;
}
88%, 100% {
opacity: 0;
}
}
@keyframes ut-thumb {
0%, 42% {
transform: translateX(0);
}
56%, 84% {
transform: translateX(36px);
}
96%, 100% {
transform: translateX(0);
}
}
@keyframes ut-suffix {
0%, 44% {
opacity: 1;
}
45%, 87% {
opacity: 0;
}
88%, 100% {
opacity: 1;
}
}
@keyframes ut-pressf {
0%, 41% {
transform: scale(1);
}
45% {
transform: scale(0.9);
}
57%, 100% {
transform: scale(1);
}
}
@keyframes ut-labelc {
0%, 44% {
color: #fff7e6;
}
45%, 87% {
color: rgba(23, 20, 26, 0.72);
}
88%, 100% {
color: #fff7e6;
}
}
@keyframes ut-labelf {
0%, 44% {
color: rgba(23, 20, 26, 0.72);
}
45%, 87% {
color: #fff7e6;
}
88%, 100% {
color: rgba(23, 20, 26, 0.72);
}
}
.ut.is-demo .ut__swap .ut__v {
animation-name: ut-va;
}
.ut.is-demo .ut__swap .ut__alt {
animation-name: ut-vb;
}
.ut.is-demo .ut__swap .ut__v, .ut.is-demo .ut__swap .ut__alt {
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ut.is-demo .ut__thumb {
animation-name: ut-thumb;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ut.is-demo .ut__deg .ut__v, .ut.is-demo .ut__deg .ut__alt {
animation-name: ut-va, ut-vb;
animation-duration: 2s, 2s;
animation-timing-function: steps(1, end), steps(1, end);
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
.ut.is-demo .ut__deg .ut__v {
animation-name: ut-va;
}
.ut.is-demo .ut__deg .ut__alt {
animation-name: ut-vb;
}
.ut.is-demo .ut__seg .ut__u[data-unit=c] {
animation-name: ut-labelc;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ut.is-demo .ut__seg .ut__u[data-unit=f] {
animation-name: ut-pressf, ut-labelf;
animation-duration: 2s, 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1), steps(1, end);
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
@media (max-width: 340px) {
.ut {
padding: 4px 8px;
gap: 4px;
}
.ut__big {
font-size: 30px;
}
.ut__deg {
font-size: 16px;
}
.ut__h {
padding: 0;
}
.ut__d {
gap: 4px;
}
.ut__dv {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.ut.is-demo .ut__swap .ut__v,
.ut.is-demo .ut__swap .ut__alt,
.ut.is-demo .ut__thumb,
.ut.is-demo .ut__u {
animation: none !important;
transform: none !important;
}
.ut.is-demo .ut__swap .ut__v {
opacity: 1 !important;
}
.ut.is-demo .ut__swap .ut__alt {
opacity: 0 !important;
}
.ut__thumb {
transition: none !important;
}
}// 단위를 고르면 카드 안 모든 온도가 같은 순간에 환산되고, 고른 값은 저장돼 다음에 열어도 그대로다.
var utRoot = document.getElementById('ut');
var utCells = utRoot.querySelectorAll('.ut-c');
var utSuffixes = utRoot.querySelectorAll('.ut-suffix');
var utButtons = utRoot.querySelectorAll('.ut__u');
function utShow(unit) {
for (var i = 0; i < utCells.length; i++) {
var c = Number(utCells[i].getAttribute('data-c'));
utCells[i].textContent = unit === 'f' ? String(Math.round(c * 9 / 5 + 32)) : String(c);
}
for (var j = 0; j < utSuffixes.length; j++) {
utSuffixes[j].textContent = unit === 'f' ? '°F' : '°C';
}
for (var k = 0; k < utButtons.length; k++) {
var on = utButtons[k].getAttribute('data-unit') === unit;
utButtons[k].classList.toggle('is-on', on);
utButtons[k].setAttribute('aria-pressed', on ? 'true' : 'false');
}
utRoot.classList.toggle('is-f', unit === 'f');
}
function utRemember(unit) {
try { localStorage.setItem('ut-unit', unit); } catch (err) { /* 저장이 막힌 창에서는 이번 화면만 */ }
}
utRoot.addEventListener('click', function (e) {
var btn = e.target.closest('.ut__u');
if (!btn) return;
utRoot.classList.remove('is-demo');
var unit = btn.getAttribute('data-unit');
utShow(unit);
utRemember(unit);
});
// 저장돼 있던 단위를 되살린다 — 없으면 섭씨
var utSaved = null;
try { utSaved = localStorage.getItem('ut-unit'); } catch (err2) { utSaved = null; }
utShow(utSaved === 'f' ? 'f' : 'c');import { useEffect, useState } from "react";
import type { CSSProperties } from "react";
import styles from "./WeatherUnitToggle.module.scss";
// vanilla 판(run/281/demo/08-weather-unit-toggle/index.html) 의 React 포트.
// 단위를 고르면 카드 안 모든 온도가 같은 순간에 환산되고, 고른 값은 저장돼 다음에 열어도 그대로다.
export type UtHour = {
/** 칸 라벨("지금", "15시" …) — 언어팩이 통째로 바꾼다 */
label: string;
/** 섭씨 원본값 */
c: number;
};
export type UtDay = {
label: string;
lo: number;
hi: number;
};
export type WeatherUnitToggleProps = {
title?: string;
groupLabel?: string;
segLabel?: string;
celsiusLabel?: string;
fahrenheitLabel?: string;
/** 큰 온도 — 섭씨 원본값 */
big?: number;
hours?: UtHour[];
days?: UtDay[];
/** 저장 열쇠 — 브라우저마다 이 이름으로 단위를 기억한다 */
storageKey?: string;
/** 격자 미리보기 2초 자동 루프. 사람이 손대면 꺼진다 */
demo?: boolean;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const DEFAULT_HOURS: UtHour[] = [
{ label: "지금", c: 24 },
{ label: "15시", c: 25 },
{ label: "16시", c: 23 },
{ label: "17시", c: 21 },
];
const DEFAULT_DAYS: UtDay[] = [
{ label: "오늘", lo: 18, hi: 27 },
{ label: "토요일", lo: 17, hi: 29 },
];
function toF(c: number): number {
return Math.round((c * 9) / 5 + 32);
}
function loadUnit(key: string): string {
try {
return localStorage.getItem(key) || "";
} catch (e) {
// 저장이 막힌 창에서는 이번 화면만
return "";
}
}
function saveUnit(key: string, unit: string): void {
try {
localStorage.setItem(key, unit);
} catch (e) {
/* 저장이 막힌 창에서는 이번 화면만 */
}
}
export default function WeatherUnitToggle({
title = "서울의 온도",
groupLabel = "온도 단위 고르기",
segLabel = "온도 단위",
celsiusLabel = "°C",
fahrenheitLabel = "°F",
big = 24,
hours = DEFAULT_HOURS,
days = DEFAULT_DAYS,
storageKey = "ut-unit",
demo = true,
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
}: WeatherUnitToggleProps) {
const [unit, setUnit] = useState("c");
const [live, setLive] = useState(demo);
// 저장돼 있던 단위를 되살린다 — 없으면 섭씨
useEffect(() => {
if (loadUnit(storageKey) === "f") setUnit("f");
}, []);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const isF = unit === "f";
const suffix = isF ? fahrenheitLabel : celsiusLabel;
const show = (c: number): number => (isF ? toF(c) : c);
function pick(next: string): void {
setLive(false);
setUnit(next);
saveUnit(storageKey, next);
}
const cls = [styles.ut, live ? styles.isDemo : "", isF ? styles.isF : ""].filter(Boolean).join(" ");
return (
<main className={styles.stage}>
<section className={cls} style={vars} aria-label={groupLabel}>
<header className={styles.head}>
<p className={styles.title}>{title}</p>
<div className={styles.seg} role="group" aria-label={segLabel}>
<span className={styles.thumb} aria-hidden="true" />
<button
className={[styles.u, styles.uC, !isF ? styles.isOn : ""].filter(Boolean).join(" ")}
type="button"
aria-pressed={!isF ? "true" : "false"}
onClick={() => pick("c")}
>{celsiusLabel}</button>
<button
className={[styles.u, styles.uF, isF ? styles.isOn : ""].filter(Boolean).join(" ")}
type="button"
aria-pressed={isF ? "true" : "false"}
onClick={() => pick("f")}
>{fahrenheitLabel}</button>
</div>
</header>
<p className={styles.big}>
<output className={styles.swap}>
<span className={styles.v}>{show(big)}</span>
<span className={styles.alt} aria-hidden="true">{isF ? big : toF(big)}</span>
</output>
<span className={`${styles.deg} ${styles.swap}`}>
<span className={styles.v}>{suffix}</span>
<span className={styles.alt} aria-hidden="true">{isF ? celsiusLabel : fahrenheitLabel}</span>
</span>
</p>
<ul className={styles.hours}>
{hours.map((h: UtHour) => (
<li className={styles.h} key={h.label}>
<span className={styles.hl}>{h.label}</span>
<span className={`${styles.ht} ${styles.swap}`}>
<span className={styles.v}>{show(h.c)}</span>
<span className={styles.alt} aria-hidden="true">{isF ? h.c : toF(h.c)}</span>
</span>
<span className={`${styles.hs} ${styles.swap}`}>
<span className={styles.v}>{suffix}</span>
<span className={styles.alt} aria-hidden="true">{isF ? celsiusLabel : fahrenheitLabel}</span>
</span>
</li>
))}
</ul>
<ul className={styles.days}>
{days.map((d: UtDay) => (
<li className={styles.d} key={d.label}>
<span className={styles.dl}>{d.label}</span>
<span className={`${styles.dv} ${styles.swap}`}>
<span className={styles.v}>{show(d.lo)}</span>
<span className={styles.alt} aria-hidden="true">{isF ? d.lo : toF(d.lo)}</span>
</span>
<span className={styles.dd}>/</span>
<span className={`${styles.dv} ${styles.swap}`}>
<span className={styles.v}>{show(d.hi)}</span>
<span className={styles.alt} aria-hidden="true">{isF ? d.hi : toF(d.hi)}</span>
</span>
<span className={`${styles.ds} ${styles.swap}`}>
<span className={styles.v}>{suffix}</span>
<span className={styles.alt} aria-hidden="true">{isF ? celsiusLabel : fahrenheitLabel}</span>
</span>
</li>
))}
</ul>
</section>
</main>
);
}08 一度押せばカード内の全温度が換算される単位トグル — 計測 累積面積 2.162% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · 天気 ウィジェット 9選 — 温度・時計・アイコンをコピペOK
このデモが属する記事
天気 ウィジェット 9選 — 温度・時計・アイコンをコピペOK天気 ウィジェットとは、今の空・温度・時刻を一枚の小さなカードにまとめてページへ貼る表示部品のことです。九つともHTML・SCSS・数行のJavaScriptだけで作ってあり、貼った場所でそのまま動くコピペOKの部品で、天気のAPIは一つも この記事のデモをすべて見る


