Skip to content

Commit 4c0a3a5

Browse files
fix(weather): align forecast and hourly columns
1 parent ea58810 commit 4c0a3a5

5 files changed

Lines changed: 160 additions & 72 deletions

File tree

defaultmodules/weather/forecast.njk

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,41 @@
11
{% if forecast %}
22
{% set numSteps = forecast | calcNumSteps %}
33
{% set currentStep = 0 %}
4-
<table class="{{ config.tableClass }} weather-forecast">
4+
<div class="{{ config.tableClass }} weather-forecast" role="table">
55
{% if config.ignoreToday %}
66
{% set forecast = forecast.splice(1) %}
77
{% endif %}
88
{% set forecast = forecast.slice(0, numSteps) %}
99
{% for f in forecast %}
10-
<tr
11-
{% if config.colored %}class="colored"{% endif %}
12-
{% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}
13-
>
10+
<div class="weather-forecast-row{% if config.colored %}{{ " colored" }}{% endif %}" role="row" {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
1411
{% if (currentStep == 0) and config.ignoreToday == false and config.absoluteDates == false %}
15-
<td class="day">{{ "TODAY" | translate }}</td>
12+
<div class="day" role="cell">{{ "TODAY" | translate }}</div>
1613
{% elif (currentStep == 1) and config.ignoreToday == false and config.absoluteDates == false %}
17-
<td class="day">{{ "TOMORROW" | translate }}</td>
14+
<div class="day" role="cell">{{ "TOMORROW" | translate }}</div>
1815
{% else %}
19-
<td class="day">{{ f.date.format(config.forecastDateFormat) }}</td>
16+
<div class="day" role="cell">{{ f.date.format(config.forecastDateFormat) }}</div>
2017
{% endif %}
21-
<td class="bright weather-icon">
18+
<div class="bright weather-icon" role="cell">
2219
<span class="wi weathericon wi-{{ f.weatherType }}"></span>
23-
</td>
24-
<td class="align-right min-temp">{{ f.minTemperature | roundValue | unit("temperature") | decimalSymbol }}</td>
25-
<td class="align-right bright max-temp">{{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }}</td>
20+
</div>
21+
<div class="align-right min-temp" role="cell">{{ f.minTemperature | roundValue | unit("temperature") | decimalSymbol }}</div>
22+
<div class="align-right bright max-temp" role="cell">{{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }}</div>
2623
{% if config.showUVIndex %}
27-
<td class="align-right dimmed uv-index">
24+
<div class="align-right dimmed uv-index" role="cell">
2825
{{ f.uvIndex }}
2926
<span class="wi dimmed weathericon wi-hot"></span>
30-
</td>
27+
</div>
3128
{% endif %}
3229
{% if config.showPrecipitationAmount %}
33-
<td class="align-right bright precipitation-amount">{{ f.precipitationAmount | unit("precip", f.precipitationUnits) }}</td>
30+
<div class="align-right bright precipitation-amount" role="cell">{{ f.precipitationAmount | unit("precip", f.precipitationUnits) }}</div>
3431
{% endif %}
3532
{% if config.showPrecipitationProbability %}
36-
<td class="align-right bright precipitation-prob">{{ f.precipitationProbability | unit('precip', '%') }}</td>
33+
<div class="align-right bright precipitation-prob" role="cell">{{ f.precipitationProbability | unit('precip', '%') }}</div>
3734
{% endif %}
38-
</tr>
35+
</div>
3936
{% set currentStep = currentStep + 1 %}
4037
{% endfor %}
41-
</table>
38+
</div>
4239
{% else %}
4340
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
4441
{% endif %}

defaultmodules/weather/hourly.njk

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,47 @@
11
{% if hourly %}
22
{% set numSteps = hourly | calcNumEntries %}
33
{% set currentStep = 0 %}
4-
<table class="{{ config.tableClass }}">
4+
<div class="{{ config.tableClass }} weather-hourly" role="table">
55
{% set hours = hourly.slice(0, numSteps) %}
66
{% for hour in hours %}
7-
<tr
8-
{% if config.colored %}class="colored"{% endif %}
9-
{% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}
10-
>
11-
<td class="day">{{ hour.date | formatTime }}</td>
12-
<td class="bright weather-icon">
7+
<div class="weather-hourly-row{% if config.colored %}{{ " colored" }}{% endif %}" role="row" {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
8+
<div class="time" role="cell">{{ hour.date | formatTime }}</div>
9+
<div class="bright weather-icon" role="cell">
1310
<span class="wi weathericon wi-{{ hour.weatherType }}"></span>
14-
</td>
15-
<td class="align-right bright">{{ hour.temperature | roundValue | unit("temperature") }}</td>
11+
</div>
12+
<div class="align-right bright temperature" role="cell">{{ hour.temperature | roundValue | unit("temperature") }}</div>
1613
{% if config.showUVIndex %}
17-
<td class="align-right bright uv-index">
14+
<div class="align-right bright uv-index" role="cell">
1815
{% if hour.uvIndex!=0 %}
1916
{{ hour.uvIndex }}
2017
<span class="wi weathericon wi-hot"></span>
2118
{% endif %}
22-
</td>
19+
</div>
2320
{% endif %}
2421
{% if config.showHumidity != "none" %}
25-
<td class="align-left bright humidity-hourly">
22+
<div class="align-left bright humidity-hourly" role="cell">
2623
{{ hour.humidity }}
2724
<span class="wi wi-humidity humidity-icon"></span>
28-
</td>
25+
</div>
2926
{% endif %}
3027
{% if config.showPrecipitationAmount %}
31-
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
32-
<td class="align-right bright precipitation-amount">{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}</td>
33-
{% endif %}
28+
<div class="align-right bright precipitation-amount{% if config.hideZeroes and hour.precipitationAmount <= 0 %}{{ " weather-hidden" }}{% endif %}" role="cell">
29+
{% if not config.hideZeroes or hour.precipitationAmount > 0 %}
30+
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
31+
{% endif %}
32+
</div>
3433
{% endif %}
3534
{% if config.showPrecipitationProbability %}
36-
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
37-
<td class="align-right bright precipitation-prob">{{ hour.precipitationProbability | unit('precip', '%') }}</td>
38-
{% endif %}
35+
<div class="align-right bright precipitation-prob{% if config.hideZeroes and hour.precipitationAmount <= 0 %}{{ " weather-hidden" }}{% endif %}" role="cell">
36+
{% if not config.hideZeroes or hour.precipitationAmount > 0 %}
37+
{{ hour.precipitationProbability | unit('precip', '%') }}
38+
{% endif %}
39+
</div>
3940
{% endif %}
40-
</tr>
41+
</div>
4142
{% set currentStep = currentStep + 1 %}
4243
{% endfor %}
43-
</table>
44+
</div>
4445
{% else %}
4546
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
4647
{% endif %}

defaultmodules/weather/weather.css

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,44 @@
1111
padding-bottom: 6px;
1212
}
1313

14-
.weather .weather-forecast tr {
15-
display: flex;
14+
.weather .weather-forecast,
15+
.weather .weather-hourly {
16+
display: grid;
17+
width: max-content;
18+
grid-template-columns: repeat(7, max-content);
19+
}
20+
21+
.weather .weather-forecast-row,
22+
.weather .weather-hourly-row {
23+
display: grid;
24+
grid-column: 1 / -1;
25+
grid-template-columns: subgrid;
1626
align-items: baseline;
27+
column-gap: 25px;
1728
}
1829

1930
.weather .day {
20-
padding-left: 0;
21-
padding-right: 25px;
31+
white-space: nowrap;
2232
}
2333

2434
.weather .weather-icon {
25-
padding-right: 30px;
2635
text-align: center;
2736
}
2837

2938
.weather .min-temp {
30-
padding-left: 0;
3139
padding-right: 0;
3240
}
3341

34-
.weather .max-temp {
35-
padding-left: 20px;
36-
}
37-
42+
.weather .max-temp,
3843
.weather .precipitation-amount,
3944
.weather .precipitation-prob,
4045
.weather .humidity-hourly,
4146
.weather .uv-index {
42-
padding-left: 20px;
43-
padding-right: 0;
47+
padding: 0;
48+
}
49+
50+
.weather .weather-hidden {
51+
visibility: hidden;
4452
}
4553

4654
.weather .weather-forecast .day {
@@ -71,11 +79,41 @@
7179
order: var(--weather-forecast-precipitation-prob-order, 7);
7280
}
7381

74-
.weather tr.colored .min-temp {
82+
.weather .weather-hourly .time {
83+
order: var(--weather-hourly-time-order, 1);
84+
white-space: nowrap;
85+
}
86+
87+
.weather .weather-hourly .weather-icon {
88+
order: var(--weather-hourly-icon-order, 2);
89+
text-align: center;
90+
}
91+
92+
.weather .weather-hourly .temperature {
93+
order: var(--weather-hourly-temperature-order, 3);
94+
}
95+
96+
.weather .weather-hourly .uv-index {
97+
order: var(--weather-hourly-uv-index-order, 4);
98+
}
99+
100+
.weather .weather-hourly .humidity-hourly {
101+
order: var(--weather-hourly-humidity-order, 5);
102+
}
103+
104+
.weather .weather-hourly .precipitation-amount {
105+
order: var(--weather-hourly-precipitation-amount-order, 6);
106+
}
107+
108+
.weather .weather-hourly .precipitation-prob {
109+
order: var(--weather-hourly-precipitation-prob-order, 7);
110+
}
111+
112+
.weather .weather-forecast-row.colored .min-temp {
75113
color: #bcddff;
76114
}
77115

78-
.weather tr.colored .max-temp {
116+
.weather .weather-forecast-row.colored .max-temp {
79117
color: #ff8e99;
80118
}
81119

tests/e2e/modules/weather_forecast_spec.js

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,50 @@ describe("Weather module: Weather Forecast", () => {
1818
const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"];
1919
for (const [index, day] of days.entries()) {
2020
it(`should render day ${day}`, async () => {
21-
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`);
21+
const dayCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .day`);
2222
await expect(dayCell).toHaveText(day);
2323
});
2424
}
2525

2626
const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"];
2727
for (const [index, icon] of icons.entries()) {
2828
it(`should render icon ${icon}`, async () => {
29-
const iconElement = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`);
29+
const iconElement = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) span.wi-${icon}`);
3030
await expect(iconElement).toBeVisible();
3131
});
3232
}
3333

3434
const maxTemps = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
3535
for (const [index, temp] of maxTemps.entries()) {
3636
it(`should render max temperature ${temp}`, async () => {
37-
const maxTempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.max-temp`);
37+
const maxTempCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .max-temp`);
3838
await expect(maxTempCell).toHaveText(temp);
3939
});
4040
}
4141

4242
const minTemps = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"];
4343
for (const [index, temp] of minTemps.entries()) {
4444
it(`should render min temperature ${temp}`, async () => {
45-
const minTempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.min-temp`);
45+
const minTempCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .min-temp`);
4646
await expect(minTempCell).toHaveText(temp);
4747
});
4848
}
4949

5050
const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667];
5151
for (const [index, opacity] of opacities.entries()) {
5252
it(`should render fading of rows with opacity=${opacity}`, async () => {
53-
const row = page.locator(`.weather table.small tr:nth-child(${index + 1})`);
53+
const row = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1})`);
5454
await expect(row).toHaveAttribute("style", `opacity: ${opacity};`);
5555
});
5656
}
57+
58+
it("should align forecast columns across rows", async () => {
59+
for (const column of [".day", ".weather-icon", ".min-temp", ".max-temp"]) {
60+
const firstCell = await page.locator(`.weather .weather-forecast-row:nth-child(1) ${column}`).boundingBox();
61+
const secondCell = await page.locator(`.weather .weather-forecast-row:nth-child(2) ${column}`).boundingBox();
62+
expect(firstCell.x).toBe(secondCell.x);
63+
}
64+
});
5765
});
5866

5967
describe("Absolute configuration", () => {
@@ -65,7 +73,7 @@ describe("Weather module: Weather Forecast", () => {
6573
const days = ["Fri", "Sat", "Sun", "Mon", "Tue"];
6674
for (const [index, day] of days.entries()) {
6775
it(`should render day ${day}`, async () => {
68-
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`);
76+
const dayCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .day`);
6977
await expect(dayCell).toHaveText(day);
7078
});
7179
}
@@ -78,27 +86,55 @@ describe("Weather module: Weather Forecast", () => {
7886
});
7987

8088
it("should render custom table class", async () => {
81-
await expect(page.locator(".weather table.myTableClass.weather-forecast")).toBeVisible();
89+
await expect(page.locator(".weather .myTableClass.weather-forecast")).toBeVisible();
8290
});
8391

8492
it("should render colored rows", async () => {
85-
const rows = page.locator(".weather table.myTableClass tr");
93+
const rows = page.locator(".weather .myTableClass.weather-forecast .weather-forecast-row");
8694
await expect(rows).toHaveCount(5);
8795
});
8896

97+
it("should align optional columns across rows", async () => {
98+
for (const column of [".precipitation-amount"]) {
99+
const firstCell = await page.locator(`.weather .myTableClass.weather-forecast .weather-forecast-row:nth-child(1) ${column}`).boundingBox();
100+
const secondCell = await page.locator(`.weather .myTableClass.weather-forecast .weather-forecast-row:nth-child(2) ${column}`).boundingBox();
101+
const firstRow = await page.locator(".weather .myTableClass.weather-forecast .weather-forecast-row:nth-child(1)").boundingBox();
102+
const secondRow = await page.locator(".weather .myTableClass.weather-forecast .weather-forecast-row:nth-child(2)").boundingBox();
103+
expect(firstCell.x).toBe(secondCell.x);
104+
expect(firstCell.y).toBe(firstRow.y);
105+
expect(secondCell.y).toBe(secondRow.y);
106+
}
107+
});
108+
89109
it("should expose forecast column order through CSS", async () => {
90-
const minTempCell = page.locator(".weather table.myTableClass tr:first-child td.min-temp");
91-
const maxTempCell = page.locator(".weather table.myTableClass tr:first-child td.max-temp");
110+
const minTempCell = page.locator(".weather .myTableClass.weather-forecast .weather-forecast-row:first-child .min-temp");
111+
const maxTempCell = page.locator(".weather .myTableClass.weather-forecast .weather-forecast-row:first-child .max-temp");
92112

93113
await expect(minTempCell).toHaveCSS("order", "3");
94114
await expect(maxTempCell).toHaveCSS("order", "4");
115+
116+
await page.addStyleTag({
117+
content: `
118+
.weather .myTableClass.weather-forecast {
119+
--weather-forecast-min-temp-order: 4;
120+
--weather-forecast-max-temp-order: 3;
121+
}
122+
`
123+
});
124+
125+
await expect(minTempCell).toHaveCSS("order", "4");
126+
await expect(maxTempCell).toHaveCSS("order", "3");
127+
128+
const minTempBox = await minTempCell.boundingBox();
129+
const maxTempBox = await maxTempCell.boundingBox();
130+
expect(minTempBox.x - (maxTempBox.x + maxTempBox.width)).toBe(25);
95131
});
96132

97133
const precipitations = [undefined, "2.51 mm"];
98134
for (const [index, precipitation] of precipitations.entries()) {
99135
if (precipitation) {
100136
it(`should render precipitation amount ${precipitation}`, async () => {
101-
const precipCell = page.locator(`.weather table tr:nth-child(${index + 1}) td.precipitation-amount`);
137+
const precipCell = page.locator(`.weather .myTableClass.weather-forecast .weather-forecast-row:nth-child(${index + 1}) .precipitation-amount`);
102138
await expect(precipCell).toHaveText(precipitation);
103139
});
104140
}
@@ -115,7 +151,7 @@ describe("Weather module: Weather Forecast", () => {
115151
const temperatures = ["75_9°", "69_8°", "73_2°", "74_1°", "69_1°"];
116152
for (const [index, temp] of temperatures.entries()) {
117153
it(`should render custom decimalSymbol = '_' for temp ${temp}`, async () => {
118-
const tempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.max-temp`);
154+
const tempCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .max-temp`);
119155
await expect(tempCell).toHaveText(temp);
120156
});
121157
}
@@ -126,7 +162,7 @@ describe("Weather module: Weather Forecast", () => {
126162
for (const [index, precipitation] of precipitations.entries()) {
127163
if (precipitation) {
128164
it(`should render precipitation amount ${precipitation}`, async () => {
129-
const precipCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-amount`);
165+
const precipCell = page.locator(`.weather .weather-forecast-row:nth-child(${index + 1}) .precipitation-amount`);
130166
await expect(precipCell).toHaveText(precipitation);
131167
});
132168
}

0 commit comments

Comments
 (0)