New Weather Modal

This commit is contained in:
Sean Edmunds
2025-02-10 17:06:00 -06:00
parent 1d1b707d0c
commit a4b1e55968
21 changed files with 972 additions and 67 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M29.5 12A8.5 8.5 0 1 0 29.5 29A8.5 8.5 0 1 0 29.5 12Z"/><path fill="#BBDEFB" d="M37 21.893A7 7 0 1 0 37 35.893 7 7 0 1 0 37 21.893zM11 22A7 7 0 1 0 11 36 7 7 0 1 0 11 22z"/><path fill="#BBDEFB" d="M17.5 15A6.5 6.5 0 1 0 17.5 28A6.5 6.5 0 1 0 17.5 15Z"/><path fill="#BBDEFB" d="M25 19.893A7 7 0 1 0 25 33.893A7 7 0 1 0 25 19.893Z"/><path fill="#BBDEFB" d="M7,32c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V32z"/></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#FF9800" d="M24,6L6,24l1,1h1c1.104,0,2,0.896,2,2c0,0.293-0.066,0.569-0.18,0.82L23,41h2l17-17L24,6z"/><path fill="#FF9800" d="M11 11H37V37H11z"/><path fill="#FFEB3B" d="M13,24c0,6.077,4.923,11,11,11c6.076,0,11-4.923,11-11s-4.924-11-11-11C17.923,13,13,17.923,13,24"/><path fill="#64B5F6" d="M40,37h-9c-1.104,0-2-0.896-2-2s0.896-2,2-2h3c1.104,0,2-0.896,2-2s-0.896-2-2-2h-6h-3h-6c-1.104,0-2-0.896-2-2c0-1.102,0.89-1.994,1.99-1.999H21c1.104,0,2-0.896,2-2V23l0,0c0-1.104-0.896-2-2-2H7v0.001c-1.104,0-2,0.896-2,2s0.896,2,2,2h1C9.1,25.006,9.99,25.898,9.99,27c0,1.104-0.896,2-2,2H8c-1.104,0-2,0.896-2,2s0.896,2,2,2h1c1.104,0,2,0.896,2,2s-0.896,2-2,2s-2,0.896-2,2s0.896,2,2,2h31c0.8,0,1.485-0.473,1.805-1.152h0.001l0.001-0.002c0.057-0.123,0.104-0.25,0.135-0.385c0.002-0.008,0.006-0.016,0.008-0.023C41.98,39.296,42,39.15,42,39C42,37.896,41.104,37,40,37z"/><path fill="#BBDEFB" d="M24,35c4.273,0,7.969-2.44,9.79-6H28h-3h-6c-1.104,0-2-0.896-2-2c0-1.102,0.89-1.994,1.99-1.999H21c1.104,0,2-0.896,2-2V23c0-1.104-0.896-2-2-2h-7.576C13.154,21.955,13,22.959,13,24C13,30.077,17.923,35,24,35z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M29.5 5A8.5 8.5 0 1 0 29.5 22A8.5 8.5 0 1 0 29.5 5Z"/><path fill="#BBDEFB" d="M37 14.893A7 7 0 1 0 37 28.893 7 7 0 1 0 37 14.893zM11 15A7 7 0 1 0 11 29 7 7 0 1 0 11 15z"/><path fill="#BBDEFB" d="M17.5 8A6.5 6.5 0 1 0 17.5 21A6.5 6.5 0 1 0 17.5 8Z"/><path fill="#BBDEFB" d="M25 12.893A7 7 0 1 0 25 26.893A7 7 0 1 0 25 12.893Z"/><path fill="#BBDEFB" d="M7,25c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V25z"/><g><path fill="#2196F3" d="M20 29A1 1 0 1 0 20 31 1 1 0 1 0 20 29zM34 29A1 1 0 1 0 34 31 1 1 0 1 0 34 29zM19 32A1 1 0 1 0 19 34 1 1 0 1 0 19 32zM18 35A1 1 0 1 0 18 37 1 1 0 1 0 18 35zM17 38A1 1 0 1 0 17 40 1 1 0 1 0 17 38zM33 32A1 1 0 1 0 33 34 1 1 0 1 0 33 32zM32 35A1 1 0 1 0 32 37 1 1 0 1 0 32 35zM31 38A1 1 0 1 0 31 40 1 1 0 1 0 31 38zM16 41A1 1 0 1 0 16 43 1 1 0 1 0 16 41zM30 41A1 1 0 1 0 30 43 1 1 0 1 0 30 41zM12 32A1 1 0 1 0 12 34 1 1 0 1 0 12 32zM11 35A1 1 0 1 0 11 37 1 1 0 1 0 11 35zM26 32A1 1 0 1 0 26 34 1 1 0 1 0 26 32zM25 35A1 1 0 1 0 25 37 1 1 0 1 0 25 35zM10 38A1 1 0 1 0 10 40 1 1 0 1 0 10 38zM24 38A1 1 0 1 0 24 40 1 1 0 1 0 24 38zM27 29A1 1 0 1 0 27 31 1 1 0 1 0 27 29zM13 29A1 1 0 1 0 13 31 1 1 0 1 0 13 29z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#FF9800" d="M8 10.001H25V27.000999999999998H8z"/><path fill="#FF9800" d="M7.661 9.661H25.339V27.339H7.661z" transform="rotate(-45.001 16.5 18.5)"/><path fill="#FFEB3B" d="M10,18.5c0,3.591,2.909,6.5,6.5,6.5s6.5-2.909,6.5-6.5S20.091,12,16.5,12S10,14.909,10,18.5"/><g><path fill="#BBDEFB" d="M29.5 17A8.5 8.5 0 1 0 29.5 34A8.5 8.5 0 1 0 29.5 17Z"/><path fill="#BBDEFB" d="M37 26.893A7 7 0 1 0 37 40.893 7 7 0 1 0 37 26.893zM11 27A7 7 0 1 0 11 41 7 7 0 1 0 11 27z"/><path fill="#BBDEFB" d="M17.5 20A6.5 6.5 0 1 0 17.5 33A6.5 6.5 0 1 0 17.5 20Z"/><path fill="#BBDEFB" d="M25 24.893A7 7 0 1 0 25 38.893A7 7 0 1 0 25 24.893Z"/><path fill="#BBDEFB" d="M7,37c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V37z"/></g></svg>

After

Width:  |  Height:  |  Size: 851 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M29.5 5A8.5 8.5 0 1 0 29.5 22A8.5 8.5 0 1 0 29.5 5Z"/><path fill="#BBDEFB" d="M37 14.893A7 7 0 1 0 37 28.893 7 7 0 1 0 37 14.893zM11 15A7 7 0 1 0 11 29 7 7 0 1 0 11 15z"/><path fill="#BBDEFB" d="M17.5 8A6.5 6.5 0 1 0 17.5 21A6.5 6.5 0 1 0 17.5 8Z"/><path fill="#BBDEFB" d="M25 12.893A7 7 0 1 0 25 26.893A7 7 0 1 0 25 12.893Z"/><path fill="#BBDEFB" d="M7,25c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V25z"/><g><path fill="#2196F3" d="M34.95 37.15c-1.132 1.133-2.968 1.133-4.101 0-1.134-1.131-1.133-2.969 0-4.1C31.982 31.917 37 31 37 31S36.082 36.02 34.95 37.15zM23.95 41.15c-1.132 1.132-2.968 1.132-4.101 0-1.133-1.133-1.132-2.969 0-4.101 1.133-1.133 6.152-2.05 6.152-2.05S25.082 40.02 23.95 41.15zM13.95 37.15c-1.132 1.133-2.968 1.133-4.101 0-1.133-1.131-1.132-2.969 0-4.1 1.132-1.133 6.151-2.05 6.151-2.05S15.082 36.02 13.95 37.15z"/></g></svg>

After

Width:  |  Height:  |  Size: 999 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M29.5 5A8.5 8.5 0 1 0 29.5 22A8.5 8.5 0 1 0 29.5 5Z"/><path fill="#BBDEFB" d="M37 14.893A7 7 0 1 0 37 28.893 7 7 0 1 0 37 14.893zM11 15A7 7 0 1 0 11 29 7 7 0 1 0 11 15z"/><path fill="#BBDEFB" d="M17.5 8A6.5 6.5 0 1 0 17.5 21A6.5 6.5 0 1 0 17.5 8Z"/><path fill="#BBDEFB" d="M25 12.893A7 7 0 1 0 25 26.893A7 7 0 1 0 25 12.893Z"/><path fill="#BBDEFB" d="M7,25c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V25z"/><g><path fill="#2196F3" d="M35.95 37.15c-1.132 1.133-2.968 1.133-4.101 0-1.134-1.131-1.133-2.969 0-4.1C32.982 31.917 38 31 38 31S37.082 36.02 35.95 37.15zM14.95 37.15c-1.132 1.133-2.968 1.133-4.101 0-1.133-1.131-1.132-2.969 0-4.1 1.132-1.133 6.151-2.05 6.151-2.05S16.082 36.02 14.95 37.15zM23 34H25V43H23z"/><path fill="#2196F3" d="M23 33.999H25V42.999H23z" transform="rotate(120.471 24 38.499)"/><path fill="#2196F3" d="M23.001 34H24.999000000000002V43H23.001z" transform="rotate(60.679 24 38.5)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M29.5 5A8.5 8.5 0 1 0 29.5 22A8.5 8.5 0 1 0 29.5 5Z"/><path fill="#BBDEFB" d="M37 14.893A7 7 0 1 0 37 28.893 7 7 0 1 0 37 14.893zM11 15A7 7 0 1 0 11 29 7 7 0 1 0 11 15z"/><path fill="#BBDEFB" d="M17.5 8A6.5 6.5 0 1 0 17.5 21A6.5 6.5 0 1 0 17.5 8Z"/><path fill="#BBDEFB" d="M25 12.893A7 7 0 1 0 25 26.893A7 7 0 1 0 25 12.893Z"/><path fill="#BBDEFB" d="M7,25c0,2.209,1.791,4,4,4h25c2.209,0,4-1.791,4-4v-1c0-2.209-1.791-4-4-4H11c-2.209,0-4,1.791-4,4V25z"/><g><path fill="#2196F3" d="M23 34H25V43H23z"/><path fill="#2196F3" d="M23 33.999H25V42.999H23z" transform="rotate(120.471 24 38.499)"/><path fill="#2196F3" d="M23.001 34H24.999000000000002V43H23.001z" transform="rotate(60.679 24 38.5)"/><path fill="#2196F3" d="M10 31H12V40H10z"/><path fill="#2196F3" d="M10 30.999H12V39.998999999999995H10z" transform="rotate(120.444 11 35.5)"/><path fill="#2196F3" d="M10.001 31H11.998999999999999V40H10.001z" transform="rotate(60.679 11 35.5)"/><path fill="#2196F3" d="M36 31H38V40H36z"/><path fill="#2196F3" d="M35.999 30.998H37.999V39.998000000000005H35.999z" transform="rotate(120.458 36.999 35.5)"/><path fill="#2196F3" d="M36 31H37.998V40.001H36z" transform="rotate(60.679 36.999 35.5)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#FF9800" d="M38,38c0,0-9-3-14-3s-14,3-14,3s3-10,3-14s-3-14-3-14s9,3,14,3s14-3,14-3s-3,10-3,14S38,38,38,38z"/><path fill="#FF9800" d="M43,24c0,0-8.222,4.778-11.222,7.778S24,43,24,43s-4.278-7.722-7.778-11.222S5,24,5,24s8.221-4.777,11.222-7.777S24,5,24,5s4.779,8.223,7.778,11.223S43,24,43,24z"/><path fill="#FFEB3B" d="M14,24c0,5.524,4.477,10,10,10s10-4.476,10-10s-4.477-10-10-10S14,18.476,14,24"/></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="#BBDEFB" d="M18.5 4A8.5 8.5 0 1 0 18.5 21A8.5 8.5 0 1 0 18.5 4Z"/><path fill="#BBDEFB" d="M19 20A7 7 0 1 0 19 34A7 7 0 1 0 19 20Z"/><path fill="#BBDEFB" d="M11 14A7 7 0 1 0 11 28 7 7 0 1 0 11 14zM38 14A6 6 0 1 0 38 26 6 6 0 1 0 38 14z"/><path fill="#BBDEFB" d="M30 19A6 6 0 1 0 30 31A6 6 0 1 0 30 19Z"/><path fill="#BBDEFB" d="M30.5 7A6.5 6.5 0 1 0 30.5 20A6.5 6.5 0 1 0 30.5 7Z"/><path fill="#BBDEFB" d="M23 12A7 7 0 1 0 23 26A7 7 0 1 0 23 12Z"/><g><path fill="#D500F9" d="M22 44l2-7h-3l4-8h4l-3.594 6H29L22 44zM32.594 39l2-7h-3l4-8h4L36 30h3.594L32.594 39z"/></g></svg>

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#bbdefb" d="M38,15.5c0,4.7-3.8,8.5-8.5,8.5S21,20.2,21,15.5S24.8,7,29.5,7S38,10.8,38,15.5z"/><path fill="#bbdefb" d="M44 23.9c0 3.9-3.1 7-7 7s-7-3.1-7-7c0-3.9 3.1-7 7-7S44 20 44 23.9zM18 24c0 3.9-3.1 7-7 7s-7-3.1-7-7 3.1-7 7-7S18 20.1 18 24z"/><path fill="#bbdefb" d="M24,16.5c0,3.6-2.9,6.5-6.5,6.5S11,20.1,11,16.5s2.9-6.5,6.5-6.5S24,12.9,24,16.5z"/><path fill="#bbdefb" d="M32,21.9c0,3.9-3.1,7-7,7s-7-3.1-7-7c0-3.9,3.1-7,7-7S32,18,32,21.9z"/><path fill="#bbdefb" d="M7,27c0,2.2,1.8,4,4,4h25c2.2,0,4-1.8,4-4v-1c0-2.2-1.8-4-4-4H11c-2.2,0-4,1.8-4,4V27z"/><path fill="#2196f3" d="M31.9 41c-2.5 0-4.6-1.5-5.5-3.9-.2-.5.1-1.1.6-1.3.5-.2 1.1.1 1.3.6.6 1.7 2 2.7 3.7 2.6 2.3-.1 4-1.8 4-4 0-2.2-1.8-4-4-4H12c-.6 0-1-.4-1-1s.4-1 1-1h20c3.3 0 6 2.7 6 6 0 3.2-2.6 5.9-6 6C32 41 31.9 41 31.9 41zM25 27H14c-.6 0-1-.4-1-1s.4-1 1-1h11c1.6 0 3-1.4 3-3s-1.4-3-3-3c-1.2 0-2.3.8-2.8 1.9-.2.5-.8.8-1.3.6-.5-.2-.8-.8-.6-1.3.8-1.9 2.6-3.2 4.7-3.2 2.8 0 5 2.2 5 5S27.8 27 25 27z"/><path fill="#2196f3" d="M18,41c-1.5,0-2.9-0.8-3.5-2.2c-0.2-0.5,0-1.1,0.4-1.3c0.5-0.2,1.1,0,1.3,0.4c0.3,0.6,1,1,1.7,1 c1.1,0,2-0.9,2-2s-0.9-2-2-2h-8c-0.6,0-1-0.4-1-1s0.4-1,1-1h8c2.2,0,4,1.8,4,4S20.2,41,18,41z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,145 @@
import { Grid, Group, Stack, Text, ThemeIcon } from '@mantine/core';
import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa';
import { WiBarometer, WiHumidity } from 'react-icons/wi';
import WeatherIcon from './WeatherIcon';
export default function CurrentWeather({ weather }: any) {
return (
<Stack
gap={0}
w={'100%'}
justify='space-between'
style={{
backgroundColor: 'var(--color-search-background)',
padding: '1rem',
borderRadius: '8px',
paddingBottom: '.5rem',
}}
>
<Group gap={0} px={16} w={'100%'}>
<Text
c={'var(--color-text-pri)'}
style={{
fontSize: '1.375rem',
textAlign: 'left',
}}
>
Current Weather
</Text>
</Group>
<Group gap={0} w={'100%'}>
<Stack gap={0} w={'50%'}>
<Group gap={0}>
<WeatherIcon textDescription={`${weather.textDescription}`} />
<Text fz={'7rem'} fw={200}>
{weather.temperature}°<sup style={{ fontSize: '3rem' }}>F</sup>
</Text>
</Group>
<Text
c={'var(--color-text-pri)'}
style={{
fontSize: '1.375rem',
textAlign: 'left',
marginTop: -32,
marginLeft: 16,
}}
>
{weather.textDescription}
</Text>
</Stack>
<Stack gap={4} w={'50%'}>
<Text>
Feels like {weather.heatIndex ?? weather.windChill ?? weather.temperature}°
<sup>F</sup>
</Text>
<Grid align='center' w={'100%'}>
<Grid.Col span={2}>
<ThemeIcon
h={'auto'}
w={'auto'}
variant='subtle'
c={'var(--color-text-pri)'}
>
<FaLongArrowAltUp size={32} />
</ThemeIcon>
</Grid.Col>
<Grid.Col span={1}>
<Text>
{weather.highTemp}°<sup>F</sup>
</Text>
</Grid.Col>
<Grid.Col span={2}>
<ThemeIcon
h={'auto'}
w={'auto'}
variant='subtle'
c={'var(--color-text-pri)'}
>
<FaLongArrowAltDown size={32} />
</ThemeIcon>
</Grid.Col>
<Grid.Col span={1}>
<Text>
{weather.lowTemp}°<sup>F</sup>
</Text>
</Grid.Col>
</Grid>
<Grid align='center'>
<Grid.Col span={2}>
<ThemeIcon
h={'auto'}
w={'auto'}
variant='subtle'
c={'var(--color-text-pri)'}
>
<WiHumidity size={32} />
</ThemeIcon>
</Grid.Col>
<Grid.Col span={2}>
<Text>Humidity</Text>
</Grid.Col>
<Grid.Col span={2}>
<Text>{weather.humidity}%</Text>
</Grid.Col>
</Grid>
<Grid align='center'>
<Grid.Col span={2}>
<ThemeIcon
h={'auto'}
w={'auto'}
variant='subtle'
c={'var(--color-text-pri)'}
>
<FaWind size={32} />
</ThemeIcon>
</Grid.Col>
<Grid.Col span={2}>
<Text>Wind</Text>
</Grid.Col>
<Grid.Col span={2}>
<Text>{weather.windSpeed}mph</Text>
</Grid.Col>
</Grid>
<Grid align='center'>
<Grid.Col span={2}>
<ThemeIcon
h={'auto'}
w={'auto'}
variant='subtle'
c={'var(--color-text-pri)'}
>
<WiBarometer size={32} />
</ThemeIcon>
</Grid.Col>
<Grid.Col span={2}>
<Text>Pressure</Text>
</Grid.Col>
<Grid.Col span={2}>
<Text>{weather.pressure} in</Text>
</Grid.Col>
</Grid>
</Stack>
</Group>
</Stack>
);
}

View File

@@ -0,0 +1,52 @@
import { Group, Stack, Text } from '@mantine/core';
import WeatherIcon from './WeatherIcon';
export default function ExtendedForecast({ forecast }: any) {
const day = forecast.slice(2, 14).filter((item: any) => item.isDaytime);
const night = forecast.slice(2, 14).filter((item: any) => !item.isDaytime);
return (
<Stack
gap={0}
w={'100%'}
justify='space-between'
style={{
backgroundColor: 'var(--color-search-background)',
padding: '1rem',
borderRadius: '8px',
paddingBottom: '.5rem',
}}
>
<Group gap={0} px={16} w={'100%'}>
<Text
c={'var(--color-text-pri)'}
style={{
fontSize: '1.375rem',
textAlign: 'left',
}}
>
Extended Forecast
</Text>
</Group>
<Group w={'100%'} align='center' justify='space-between'>
{day.map((item: any, index: number) => {
const short = item.shortForecast.includes('And')
? item.shortForecast.substring(0, item.shortForecast.indexOf('And'))
: item.shortForecast.includes('then')
? item.shortForecast.substring(0, item.shortForecast.indexOf('then'))
: item.shortForecast;
return (
<Stack key={item.name} gap={0} align='center'>
<Text>{item.name}</Text>
<WeatherIcon textDescription={short} />
<Text>{short}</Text>
<Text>
{item.temperature}°{item.temperatureUnit}/{night[index].temperature}°
{item.temperatureUnit}
</Text>
</Stack>
);
})}
</Group>
</Stack>
);
}

View File

@@ -94,7 +94,7 @@ export default function HourlyCard({ weather }: any) {
color: 'white',
}}
>
{weather.temperature}°{weather.temperatureUnit}/{weather.relativeHumidity.value}%
{weather.temperature}°{weather.temperatureUnit}/{weather.relativeHumidity?.value}%
</Text>
<Box
mt={4}
@@ -114,7 +114,7 @@ export default function HourlyCard({ weather }: any) {
transform: `rotate(${WindDirection[weather.windDirection]}deg)`,
}}
/>
{`${weather.probabilityOfPrecipitation.value}`}%
{`${weather.probabilityOfPrecipitation?.value}`}%
</Group>
</Box>
</Stack>

View File

@@ -0,0 +1,251 @@
import { Image } from '@mantine/core';
import CloudyIcon from '../../assets/weather/cloudy.svg';
import HazeIcon from '../../assets/weather/haze.svg';
import HeavyRainIcon from '../../assets/weather/heavy-rain.svg';
import PartlyCloudyIcon from '../../assets/weather/partly-cloudy.svg';
import RainIcon from '../../assets/weather/rain.svg';
import SleetIcon from '../../assets/weather/sleet.svg';
import SnowIcon from '../../assets/weather/snow.svg';
import SunnyIcon from '../../assets/weather/sunny.svg';
import ThunderstormIcon from '../../assets/weather/thunderstorm.svg';
export default function WeatherIcon({ textDescription }: { textDescription: string }) {
const Icon = () => {
switch (textDescription.trim()) {
case 'Clear':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={SunnyIcon}
alt='SunnyIcon'
/>
);
case 'Cloudy':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={CloudyIcon}
alt='CloudyIcon'
/>
);
case 'Mostly Cloudy':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={CloudyIcon}
alt='CloudyIcon'
/>
);
case 'Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={RainIcon}
alt='RainIcon'
/>
);
case 'Showers':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={RainIcon}
alt='RainIcon'
/>
);
case 'Drizzle':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={RainIcon}
alt='RainIcon'
/>
);
case 'Snow':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={SnowIcon}
alt='SnowIcon'
/>
);
case 'Thunderstorm':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={ThunderstormIcon}
alt='ThunderstormIcon'
/>
);
case 'Mist':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Haze':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Dust':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Fog':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Smoke':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Sand':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Ash':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Squall':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Tornado':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Freezing Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Shower Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HazeIcon}
alt='HazeIcon'
/>
);
case 'Heavy Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={HeavyRainIcon}
alt='HeavyRainIcon'
/>
);
case 'Scattered Clouds':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
case 'Broken Clouds':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
case 'Overcast Clouds':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
case 'Moderate Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
case 'Light Rain':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
case 'Heavy Snow':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={SnowIcon}
alt='SnowIcon'
/>
);
case 'Sleet':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={SleetIcon}
alt='SleetIcon'
/>
);
case 'Partly Sunny':
return (
<Image
style={{ width: '100px', height: '100px' }}
src={PartlyCloudyIcon}
alt='PartlyCloudyIcon'
/>
);
default:
return (
<Image
style={{ width: '100px', height: '100px' }}
src={SunnyIcon}
alt='SunnyIcon'
/>
);
}
};
return <Icon />;
}

View File

@@ -1,15 +1,26 @@
import { Box, Center, Divider, Group, Modal, Stack, Text } from '@mantine/core';
import { Center, Group, Modal, Stack, Text } from '@mantine/core';
import { modalClasses } from 'styles';
import SmallOutlookCard from './SmallOutlookCard';
import SmallWeatherCard from './SmallWeatherCard';
import { useCommonHooks, useWeatherAPI } from 'hooks';
import { useEffect, useState } from 'react';
import HourlyCard from './HourlyCard';
import {
Area,
CartesianGrid,
ComposedChart,
Legend,
Line,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import CurrentWeather from './CurrentWeather';
import ExtendedForecast from './ExtendedForecast';
export function WeatherModal({
current,
outlook,
extendedForecast,
closeWeatherModal,
weatherModalOpened,
}: any) {
@@ -31,9 +42,32 @@ export function WeatherModal({
const [hourlyForecast, setHourlyForecast] = useState<any>([]);
const [sunset, setSunset] = useState(100);
function CustomTooltip({ payload, active, unit }: any) {
if (active) {
return (
<Stack
style={{
backgroundColor: 'var(--color-background)',
padding: '1rem',
borderRadius: '8px',
}}
>
{payload.map((item: any) => (
<Text
key={item.name}
size='sm'
>{`${item.name} - ${item.value}${item.unit}`}</Text>
))}
</Stack>
);
}
return null;
}
useEffect(() => {
if (hourly) {
setHourlyForecast(hourly.properties.periods.slice(1, 13));
setHourlyForecast(hourly.properties.periods.slice(1, 25));
}
}, [hourly]);
@@ -49,7 +83,6 @@ export function WeatherModal({
);
}
}, [hourlyForecast]);
return (
<Modal.Root
fullScreen={isMobile}
@@ -89,58 +122,99 @@ export function WeatherModal({
</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body pb={'2rem'}>
<Modal.Body m={0}>
<Center w={'100%'}>
<Stack w={'100%'}>
<Group gap={0} w={'100%'} align='center' justify='center'>
<Stack gap={0} align='center'>
<h3 style={{ padding: 0, margin: 0, color: 'white' }}>Currently</h3>
<Stack>
<SmallWeatherCard weather={current} />
</Stack>
</Stack>
<Divider
orientation={isMobile ? 'horizontal' : 'vertical'}
mx={16}
color={theme.accentColor}
/>
<Group gap={8}>
{outlook.map((o: any) => (
<Stack key={o.name} gap={0} align='center'>
<h3 style={{ padding: 0, margin: 0, color: 'white' }}>{o.name}</h3>
<Stack>
<SmallOutlookCard weather={o} />
</Stack>
</Stack>
))}
</Group>
</Group>
<Divider orientation='horizontal' mx={8} color={theme.accentColor} />
<Group
<CurrentWeather weather={current} />
<Stack
style={{
background:
hourlyForecast.length > 0 && hourlyForecast[0].isDaytime
? `linear-gradient(90deg, rgba(245, 225, 171, 0.85) ${sunset - 5}%, rgba(35, 28, 42, 0.85) ${sunset + 5}%)`
: `linear-gradient(90deg, rgba(35, 28, 42, 0.85)${sunset - 5}%, rgba(245, 225, 171, 0.85)${sunset + 5}%)`,
background: 'var(--color-search-background)',
padding: '.5rem',
borderRadius: '.5rem',
}}
>
<Box
style={{
display: 'flex',
overflow: 'auto',
whiteSpace: 'nowrap',
justifyContent: 'space-between',
width: '100%',
}}
>
{hourlyForecast.length > 0 &&
hourlyForecast.map((h: any) => {
return <HourlyCard key={h.number} weather={h} />;
})}
</Box>
</Group>
<ResponsiveContainer width={'100%'} height={150}>
<ComposedChart data={hourlyForecast}>
<XAxis
type='category'
stroke={theme.primaryColor}
dataKey={(data: any) => {
return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
});
}}
/>
<YAxis
stroke={theme.primaryColor}
unit={'°F'}
dataKey={'temperature'}
/>
<CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<CustomTooltip />} />
<Line
unit={'°F'}
type='monotone'
name='Temperature'
dataKey='temperature'
stroke={theme.primaryColor}
dot={{ stroke: theme.primaryColor, strokeWidth: 1 }}
/>
<Line
unit={'°F'}
type='monotone'
name='Dew Point'
dataKey={(data: any) => {
return (data.dewpoint.value * 9) / 5 + 32;
}}
stroke={theme.accentColor}
dot={{ stroke: theme.primaryColor, strokeWidth: 1 }}
/>
<Legend style={{ background: 'var(--color-search-background)' }} />
</ComposedChart>
</ResponsiveContainer>
<ResponsiveContainer width={'100%'} height={150}>
<ComposedChart data={hourlyForecast}>
<XAxis
type='category'
stroke={theme.primaryColor}
dataKey={(data: any) => {
return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
});
}}
/>
<YAxis stroke={theme.primaryColor} unit={'%'} domain={[0, 100]} />
<CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<CustomTooltip />} />
<Area
unit={'%'}
type='monotone'
name='Precipitation'
dataKey={(data: any) => {
return data.probabilityOfPrecipitation.value;
}}
stroke={theme.primaryColor}
fill={theme.accentColor}
dot={{ stroke: theme.primaryColor, strokeWidth: 1 }}
/>
<Area
unit={'%'}
type='monotone'
name='Humidity'
dataKey={(data: any) => {
return data.relativeHumidity.value;
}}
stroke={theme.accentColor}
fill={theme.accentColor}
dot={{ stroke: theme.primaryColor, strokeWidth: 1 }}
/>
<Legend style={{ background: 'var(--color-search-background)' }} />
</ComposedChart>
</ResponsiveContainer>
</Stack>
<ExtendedForecast forecast={extendedForecast} />
</Stack>
</Center>
</Modal.Body>

View File

@@ -4,6 +4,7 @@ import { useCommonHooks, useOSMAPI, useQueryAPI, useWeatherAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { stackClasses } from 'styles';
import type { CurrentWeather, OutlookWeather } from 'types';
import type { ExtendedForecast } from 'types/weather';
import Applications from './components/Applications/Applications';
import Bookmarks from './components/Bookmarks/Bookmarks';
import Header from './components/Header/Header';
@@ -106,14 +107,17 @@ export function Homepage() {
});
const [current, setCurrent] = useState<CurrentWeather>({
icon: '',
textDescription: '',
temperature: 0,
highTemp: 0,
lowTemp: 0,
precipitation: 'N/A',
humidity: 0,
heatIndex: 0,
heatIndex: null,
windChill: null,
windSpeed: 0,
windDirection: 0,
pressure: 0,
});
const [outlook, setOutlook] = useState<OutlookWeather[]>([
{
@@ -126,11 +130,22 @@ export function Homepage() {
windDirection: 'N',
},
]);
const [extendedForecast, setExtendedForecast] = useState<ExtendedForecast[]>([
{
detailedForecast: '',
isDaytime: true,
name: '',
shortForecast: '',
temperature: 0,
temperatureUnit: 'F',
},
]);
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
),
@@ -143,13 +158,23 @@ export function Homepage() {
precipitation:
weather.features[0]?.properties.precipitationLastHour.value || 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex: Math.round(
weather.features[0]?.properties.heatIndex.value !== null
? (weather.features[0]?.properties.heatIndex.value * 9) / 5 + 32
: (weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.heatIndex.values[0].value * 9) / 5 + 32,
),
windChill:
basicWeather.properties.windChill?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.windChill.values[0].value * 9) / 5 + 32,
),
windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0]?.properties.windDirection.value,
pressure: Math.round(
weather.features[0]?.properties.barometricPressure.value / 3386.38,
),
});
}
}, [weather, basicWeather]);
@@ -179,6 +204,16 @@ export function Homepage() {
windDirection: forecast.properties.periods[1].windDirection,
},
]);
setExtendedForecast(
forecast.properties.periods.map((item: any) => ({
detailedForecast: item.detailedForecast,
isDaytime: item.isDaytime,
name: item.name,
shortForecast: item.shortForecast,
temperature: item.temperature,
temperatureUnit: item.temperatureUnit,
})),
);
setCurrentLocation({
office: points?.properties.gridId,
station: stations?.features[0].properties.stationIdentifier,
@@ -189,11 +224,13 @@ export function Homepage() {
});
}
}, [forecast]);
return (
<Stack gap={0} className={stackClasses.containerStack} justify='center'>
<Search
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}
@@ -206,6 +243,7 @@ export function Homepage() {
current={current}
setCurrent={setCurrent}
outlook={outlook}
extendedForecast={extendedForecast}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}

View File

@@ -10,6 +10,7 @@ function Header({
current,
setCurrent,
outlook,
extendedForecast,
weather,
basicWeather,
weatherModalOpened,
@@ -154,6 +155,7 @@ function Header({
<WeatherModal
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
closeWeatherModal={closeWeatherModal}
weatherModalOpened={weatherModalOpened}
/>

View File

@@ -8,6 +8,7 @@ import { textInputClasses } from 'styles';
export default function Search({
current,
outlook,
extendedForecast,
weatherModalOpened,
closeWeatherModal,
openWeatherModal,
@@ -161,6 +162,7 @@ export default function Search({
<WeatherModal
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
closeWeatherModal={closeWeatherModal}
weatherModalOpened={weatherModalOpened}
/>

View File

@@ -1,13 +1,16 @@
export interface CurrentWeather {
icon: string;
textDescription: string;
temperature: number;
highTemp: number;
lowTemp: number;
precipitation: string;
humidity: number;
heatIndex: number;
heatIndex: number | null;
windChill: number | null;
windSpeed: number;
windDirection: number;
pressure: number;
}
export interface OutlookWeather {
@@ -19,3 +22,12 @@ export interface OutlookWeather {
windSpeed: string;
windDirection: string;
}
export interface ExtendedForecast {
detailedForecast: string;
isDaytime: boolean;
name: string;
shortForecast: string;
temperature: number;
temperatureUnit: string;
}