Skip to content

Commit

Permalink
feat: ✨ implemented moon phase
Browse files Browse the repository at this point in the history
  • Loading branch information
dobromiryor committed Jul 21, 2024
1 parent e748072 commit 26d727b
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 7 deletions.
88 changes: 88 additions & 0 deletions src/components/atoms/MoonIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import clsx from "clsx";
import { useMemo } from "react";

interface MoonIconProps {
lat: number;
phase: number;
}

const ease = (x: number) => 1 - Math.sqrt(1 - Math.pow(x, 2));

export const MoonIcon = ({ lat, phase }: MoonIconProps) => {
const isGibbous = useMemo(() => phase >= 0.25 && phase < 0.75, [phase]);

const scale = useMemo(() => {
const min = 1;
const max = 4;

if (phase < 0.25) {
return min + (phase / 0.25) * (max - min);
} else if (phase < 0.5) {
return max - ((phase - 0.25) / 0.25) * (max - min);
} else if (phase < 0.75) {
return min + ((phase - 0.5) / 0.25) * (max - min);
} else {
return max - ((phase - 0.75) / 0.25) * (max - min);
}
}, [phase]);

const translation = useMemo(() => {
if (phase < 0.25) {
const min = 32;
const max = 36;

return min + (phase / 0.25) * (max - min);
} else if (phase < 0.5) {
const min = 64;
const max = 36;

return max - ease((phase - 0.25) / 0.25) * (max - min);
} else if (phase < 0.75) {
const min = 64;
const max = 68;

return min + ((phase - 0.5) / 0.25) * (max - min);
} else {
const min = 96;
const max = 68;

return max - ease((phase - 0.75) / 0.25) * (max - min);
}
}, [phase]);

const maskStyles = "w-8 h-8 rounded-full";

return (
<div
className="relative flex justify-center items-center h-8 w-8 rounded-full pointer-events-none overflow-hidden origin-center"
style={{ rotate: `-${90 - lat}deg` }}
>
{/* Background */}
<div
className={clsx(
"h-8 w-8 rounded-full transition-all",
!isGibbous ? "bg-zinc-700" : "bg-primary"
)}
/>

<div
className="absolute left-8 flex origin-left transition-all"
style={{
transform: `scale(${scale}) translateX(${-translation}px)`,
}}
>
{/* Right mask */}
<div className={clsx(maskStyles, "bg-primary")} />

{/* Center mask */}
<div className={clsx(maskStyles, "bg-zinc-700")} />

{/* Left mask */}
<div className={clsx(maskStyles, "bg-primary")} />
</div>

{/* Shadow overlay */}
<div className="absolute w-8 h-8 rounded-full shadow-center-inset-2 shadow-primary" />
</div>
);
};
8 changes: 6 additions & 2 deletions src/components/atoms/WeatherIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ export const WeatherIcon = ({
);

const scaleMapStyles = {
1: "drop-shadow-sm",
1: "drop-shadow-md",
2: "drop-shadow-xl",
4: "drop-shadow-2xl",
};

return (
<img
/* Using scale to get rid of the unnecessary padding */
className={clsx("scale-150", scaleMapStyles[scale], className)}
className={clsx(
"scale-150 dark:drop-shadow-none",
scaleMapStyles[scale],
className
)}
src={src.href}
/* Scaling down to avoid blurred icons */
width={(50 * scale) / 2}
Expand Down
89 changes: 89 additions & 0 deletions src/components/molecules/MoonPhaseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import clsx from "clsx";
import { useWeatherStore } from "../../stores/weather.store";
import { getAdjustedTime } from "../../utils/getAdjustedTime";
import { getMoonIllumination } from "../../utils/getMoonIllumination";
import { getMoonPhase } from "../../utils/getMoonPhase";
import { MoonIcon } from "../atoms/MoonIcon";
import { ScrollButtons } from "../atoms/ScrollButtons";

export const MoonPhaseCard = () => {
const { data } = useWeatherStore();

return (
<li
className="col-span-2 flex flex-col gap-1 py-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm"
tabIndex={0}
>
<span className="px-4">Moon phase</span>

<div className="flex gap-4 pl-4">
<div className="flex-shrink-0 flex flex-col justify-between h-full">
<div className="flex flex-col">
<span className="text-xs text-inverted-text/75 dark:text-text/75">
Moonrise
</span>
<span className="text-2xl">
{new Date(
getAdjustedTime(data.timezone_offset, data.daily[0].moonrise)
).toLocaleTimeString("en-gb", { timeStyle: "short" })}
</span>
</div>
<div className="flex flex-col">
<span className="text-xs text-inverted-text/75 dark:text-text/75">
Moonset
</span>
<span className="text-2xl">
{new Date(
getAdjustedTime(data.timezone_offset, data.daily[0].moonset)
).toLocaleTimeString("en-gb", { timeStyle: "short" })}
</span>
</div>
</div>

<ScrollButtons>
<ul
className={clsx(
"flex justify-between items-center gap-4 max-w-full h-full overflow-x-scroll scrollbar-none snap-x snap-proximity transition-all ",
data.isFallback && "blur"
)}
aria-label="Moon phase list"
tabIndex={0}
>
{data.daily.map((day, index) => (
<li
className="flex-1 flex flex-col gap-1 justify-center items-center"
key={`Moon__Cycle__${day.dt}__${day.moon_phase}__${index}`}
>
<span className="text-xs text-center text-inverted-text/75 dark:text-text/75">
{getMoonPhase(day.moon_phase)}
</span>

<MoonIcon lat={data.lat} phase={day.moon_phase} />

<span
aria-label={`${getMoonIllumination(
day.moon_phase
).toFixed()}% illumination`}
className="text-xs"
>
{getMoonIllumination(day.moon_phase).toFixed()}%
</span>

<time className="text-xs text-inverted-text/75 dark:text-text/75">
{index === 0
? "Today"
: new Date(
getAdjustedTime(data.timezone_offset, day.dt)
).toLocaleDateString("en-gb", {
day: "numeric",
month: "short",
})}
</time>
</li>
))}
</ul>
</ScrollButtons>
</div>
</li>
);
};
2 changes: 2 additions & 0 deletions src/components/organisms/CurrentConditions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SkipToContent } from "../atoms/SkipToContent";
import { HumidityCard } from "../molecules/HumidityCard";
import { MoonPhaseCard } from "../molecules/MoonPhaseCard";
import { PressureCard } from "../molecules/PressureCard";
import { SunriseAndSunsetCard } from "../molecules/SunriseAndSunsetCard";
import { UVCard } from "../molecules/UVCard";
Expand All @@ -22,6 +23,7 @@ export const CurrentConditions = () => {
<UVCard />
<PressureCard />
<SunriseAndSunsetCard />
<MoonPhaseCard />
</ul>
</article>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/organisms/DailyForecast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const DailyForecast = () => {
{Math.round(item.pop * 10) * 10 > 0 && (
<span
aria-description="Probability of precipitation"
className="text-sm text-blue-300"
className="text-xs text-blue-300"
>
{Math.round(item.pop * 10) * 10}%
</span>
Expand Down
8 changes: 4 additions & 4 deletions src/components/organisms/HourlyForecast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const HourlyForecast = () => {
{data.hourly.map((item, index) => (
<li
className={clsx(
"flex-shrink-0 flex flex-col justify-between items-center min-w-10 snap-center",
"flex-shrink-0 flex flex-col justify-between items-center gap-1 min-w-10 snap-center",
data.isFallback && "blur"
)}
key={`Hourly__Forecast__${item.dt}__${index}`}
Expand All @@ -36,12 +36,12 @@ export const HourlyForecast = () => {
{Math.round(item.pop * 10) * 10 > 0 ? (
<span
aria-description="Probability of precipitation"
className="text-sm text-blue-300"
className="text-xs text-blue-300"
>
{Math.round(item.pop * 10) * 10}%
</span>
) : (
<div className="w-1 h-5" />
<div className="w-1 h-4" />
)}
<div className="flex flex-col justify-center items-center">
<WeatherIcon
Expand All @@ -50,7 +50,7 @@ export const HourlyForecast = () => {
/>
<time
id={`time-${index}`}
className="text-sm text-inverted-text/75 dark:text-text/75"
className="text-xs text-inverted-text/75 dark:text-text/75"
>
{index === 0
? "Now"
Expand Down
11 changes: 11 additions & 0 deletions src/utils/getMoonIllumination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const getMoonIllumination = (phase: number) => {
const min = 0;
const max = 100;
const phasePercent = phase * 100;

if (phase <= 0.5) {
return (min + (phasePercent / 50) * (max - min));
} else {
return max - ((phasePercent - 50) / 50) * (max - min);
}
};
18 changes: 18 additions & 0 deletions src/utils/getMoonPhase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const getMoonPhase = (phase: number) => {
if (phase === 0 || phase === 100) {
return "New moon";
}

if (phase === 0.25) return "First quarter";

if (phase === 0.5) return "Full Moon";

if (phase === 0.75) return "Last quarter";

if (phase > 0 && phase < 0.25) return "Waxing crescent";
if (phase > 0.25 && phase < 0.5) return "Waxing gibbous";
if (phase > 0.5 && phase < 0.75) return "Waning gibbous";
if (phase > 0.75 && phase < 1) return "Waning crescent";

return "";
};
3 changes: 3 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
boxShadow: {
"center-inset-2": "0 0 2px 0 var(--tw-shadow-colored) inset",
},
screens: {
xs: "360px",
},
Expand Down

0 comments on commit 26d727b

Please sign in to comment.