separate components

This commit is contained in:
terraputix
2026-02-15 22:02:51 +01:00
parent 1c4979e12e
commit f4009c4aa1
7 changed files with 1682 additions and 1733 deletions
@@ -0,0 +1,232 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { getColor } from '../../utils/colors';
import weatherCodes from '../../utils/weather-codes';
import {
type FetchedDaily,
type WeatherUnits,
getDayLabel,
getTextColorForTemp,
getWindArrowRotation
} from './types';
interface Props {
daily: FetchedDaily | null;
selectedDay: Date;
units: WeatherUnits;
onSelectDay: (date: Date, index: number) => void;
}
let { daily, selectedDay, units, onSelectDay }: Props = $props();
const today = new Date();
</script>
<div in:fade out:fade class="daily-cards-wrapper">
<div class="daily-cards-scroll">
{#if daily}
{#each daily.dailyDates as time, index (index)}
{@const selected = time.getDate() === selectedDay.getDate()}
{@const tempMax = daily.daily.temperature_2m_max[index]}
{@const tempMin = daily.daily.temperature_2m_min[index]}
{@const wCode = daily.daily.weather_code[index]}
{@const sunDuration = daily.daily.sunshine_duration[index]}
{@const precipSum = daily.daily.precipitation_sum[index]}
{@const windMax = daily.daily.windspeed_10m_max[index]}
{@const gustMax = daily.daily.windgusts_10m_max[index]}
{@const windDir = daily.daily.winddirection_10m_dominant[index]}
{#if tempMax != null && !isNaN(tempMax)}
<button
class="daily-card {selected ? 'selected' : ''}"
onclick={() => onSelectDay(time, index)}
>
<div class="card-day-name">
{time.toLocaleDateString('en-GB', { weekday: 'short' }).toUpperCase()}
</div>
<div class="card-day-date">{getDayLabel(time, today)}</div>
<div class="card-icon">
<svg class="fill-foreground" width="52px" height="52px">
<use
xlink:href="/images/weather-icons/wi-day-{weatherCodes[
wCode as keyof typeof weatherCodes
] ?? 'clear'}.svg#Layer_1"
></use>
</svg>
</div>
<div
class="card-temp-max"
style="background-color: {getColor(
tempMax,
String(units.temperature_unit)
)}; color: {getTextColorForTemp(tempMax, String(units.temperature_unit))}"
>
{tempMax.toFixed(0)}{units.temperature_unit === 'celsius' ? '°C' : '°F'}
</div>
<div
class="card-temp-min"
style="background-color: {getColor(
tempMin,
String(units.temperature_unit)
)}; color: {getTextColorForTemp(tempMin, String(units.temperature_unit))}"
>
{tempMin.toFixed(0)}{units.temperature_unit === 'celsius' ? '°C' : '°F'}
</div>
<div class="card-detail">
<svg class="fill-foreground detail-icon" width="16px" height="16px">
<use xlink:href="/images/weather-icons/wi-strong-wind.svg#Layer_1"></use>
</svg>
<span>{windMax?.toFixed(0) ?? '-'}-{gustMax?.toFixed(0) ?? '-'}</span>
</div>
<div class="card-detail">
<svg class="fill-foreground detail-icon" width="16px" height="16px">
<use xlink:href="/images/weather-icons/wi-raindrop.svg#Layer_1"></use>
</svg>
<span>
{Number(precipSum ?? 0).toFixed(0)}{units.precipitation_unit === 'mm' ? ' mm' : "'"}
</span>
</div>
<div class="card-detail">
<svg class="fill-foreground detail-icon" width="16px" height="16px">
<use xlink:href="/images/weather-icons/wi-day-sunny.svg#Layer_1"></use>
</svg>
<span>{Number((sunDuration ?? 0) / 3600).toFixed(0)} h</span>
</div>
{#if windDir != null && !isNaN(windDir)}
<div class="card-wind-dir">
<div style="transform: {getWindArrowRotation(windDir)}; display: inline-block">
<svg class="fill-foreground" width="18px" height="18px">
<use xlink:href="/images/weather-icons/wi-direction-down.svg#Layer_1"></use>
</svg>
</div>
</div>
{/if}
</button>
{/if}
{/each}
{/if}
</div>
</div>
<style>
.daily-cards-wrapper {
margin-bottom: 1.5rem;
min-height: 260px;
}
.daily-cards-scroll {
display: flex;
gap: 4px;
overflow-x: auto;
scrollbar-width: thin;
padding-bottom: 4px;
}
.daily-card {
flex: 1;
min-width: 110px;
max-width: 130px;
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 6px;
border-radius: 12px;
cursor: pointer;
border: 2px solid transparent;
background: hsl(var(--card));
transition: all 200ms ease;
gap: 2px;
}
.daily-card:hover {
background: hsl(var(--accent));
}
.daily-card.selected {
border-color: hsl(var(--primary));
background: hsl(var(--accent));
transform: scale(1.03);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-day-name {
font-weight: 700;
font-size: 14px;
letter-spacing: 0.5px;
}
.card-day-date {
font-size: 11px;
color: hsl(var(--muted-foreground));
margin-bottom: 2px;
}
.card-icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 4px 0;
background: #0061a5;
border-radius: 6px;
margin: 3px 0;
}
.card-temp-max {
width: 100%;
text-align: center;
font-weight: 600;
font-size: 15px;
padding: 3px 0;
border-radius: 4px 4px 0 0;
}
.card-temp-min {
width: 100%;
text-align: center;
font-weight: 600;
font-size: 15px;
padding: 3px 0;
border-radius: 0 0 4px 4px;
margin-bottom: 4px;
}
.card-detail {
display: flex;
align-items: center;
gap: 3px;
font-size: 11px;
white-space: nowrap;
}
.detail-icon {
flex-shrink: 0;
}
.card-wind-dir {
margin-top: 2px;
}
@media (max-width: 768px) {
.daily-card {
min-width: 95px;
max-width: 110px;
}
.card-icon svg {
width: 40px;
height: 40px;
}
.card-temp-max,
.card-temp-min {
font-size: 13px;
}
}
</style>