Skip to content

Commit

Permalink
Feat/christmas mood (#564)
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardZaydler authored Dec 5, 2024
1 parent f3f75ed commit cbead25
Show file tree
Hide file tree
Showing 14 changed files with 478 additions and 3 deletions.
139 changes: 139 additions & 0 deletions src/Components/ChristmasLights/ChristmasLights.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
@bulb-size: 8px;
@lines-color: #000000;

.ChristmasLightsWrapper {
height: 0;
width: 100%;
max-width: 100%;
display: flex;
justify-content: start;
align-items: start;
position: sticky;
z-index: 30;
overflow-x: clip;
pointer-events: none;
}

.Bulb {
display: flex;
margin-left: 8px;
margin-right: 24px;
border-radius: 50%;
height: @bulb-size;
width: @bulb-size;
position: relative;
transition: 1s all ease;

&:before {
position: absolute;
content: "";
height: 2px;
width: 3px;
left: 2.5px;
top: -3px;
background: @lines-color;
border-radius: 2px;
border-bottom: 2px solid @lines-color;
}

&:after {
position: absolute;
content: "";
top: -11px;
left: 4px;
width: 40px;
height: 12px;
border-bottom: solid @lines-color 1.5px;
border-radius: 50%;
}

&:last-child::after {
content: "";
position: absolute;
border: none;
}
}

@keyframes glow-red {
0%,
100% {
box-shadow: none;
background-color: rgba(192, 57, 43, 0.25);
border: 0.5px solid rgba(192, 57, 43, 0.33);
}
33% {
background-color: rgb(192, 57, 43);
}
50% {
box-shadow: 0 0 15px 2px rgb(192, 57, 43);
background-color: rgb(192, 57, 43);
}
}

@keyframes glow-green {
0%,
100% {
box-shadow: none;
background-color: rgba(46, 204, 113, 0.25);
border: 0.5px solid rgba(46, 204, 113, 0.33);
}
33% {
background-color: rgb(46, 204, 113);
}
50% {
box-shadow: 0 0 15px 2px rgb(46, 204, 113);
background-color: rgb(46, 204, 113);
}
}

@keyframes glow-blue {
0%,
100% {
box-shadow: none;
background-color: rgba(116, 247, 225, 0.25);
border: 0.5px solid rgba(116, 247, 225, 0.33);
}
33% {
background-color: rgb(116, 247, 225);
}
50% {
box-shadow: 0 0 15px 2px rgb(116, 247, 225);
background-color: rgb(116, 247, 225);
}
}

@keyframes glow-yellow {
0%,
100% {
box-shadow: none;
background-color: rgba(241, 196, 15, 0.25);
border: 0.5px solid rgba(241, 196, 15, 0.33);
}
33% {
background-color: rgb(241, 196, 15);
}
50% {
box-shadow: 0 0 15px 2px rgb(241, 196, 15);
background-color: rgb(241, 196, 15);
}
}

.RedBulb {
border-color: rgb(192, 57, 43);
animation: glow-red infinite;
}

.GreenBulb {
background-color: rgb(46, 204, 113);
animation: glow-green infinite;
}

.BlueBulb {
background-color: rgb(116, 247, 225);
animation: glow-blue infinite;
}

.YellowBulb {
background-color: rgb(241, 196, 15);
animation: glow-yellow infinite;
}
52 changes: 52 additions & 0 deletions src/Components/ChristmasLights/ChristmasLights.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useEffect, useState } from "react";
import classNames from "classnames/bind";
import styles from "./ChristmasLights.less";

const cn = classNames.bind(styles);

export function ChristmasLights() {
const [width, setWidth] = useState(window.innerWidth);

useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);

window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

const count = Math.floor(width / 40);
const ids = [...Array(count).keys()];

return (
<div className={cn("ChristmasLightsWrapper")}>
{ids.map((id) => (
<ChristmasBulb index={id} key={id} />
))}
</div>
);
}

export const randomNumberBetween = (min: number, max: number): number => {
if (min > max) {
throw new Error("Minimum value should be smaller than maximum value.");
}
const range = max - min;
return parseFloat((min + range * Math.random()).toFixed(2));
};

const ChristmasBulb: React.FC<{ index: number }> = ({ index }) => {
const [duration] = useState(randomNumberBetween(3, 6));

let bulbClassName;
if (index % 4 === 0) {
bulbClassName = cn("Bulb", "RedBulb");
} else if (index % 4 === 1) {
bulbClassName = cn("Bulb", "GreenBulb");
} else if (index % 4 === 2) {
bulbClassName = cn("Bulb", "BlueBulb");
} else {
bulbClassName = cn("Bulb", "YellowBulb");
}

return <div className={bulbClassName} style={{ animationDuration: `${duration}s` }} />;
};
20 changes: 20 additions & 0 deletions src/Components/ChristmasMoodToggle/ChristmasMoodToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { FC } from "react";
import { Toggle } from "@skbkontur/react-ui/components/Toggle";
import { useChristmasMood } from "../../hooks/useChristmasMood";
import { useAppDispatch } from "../../store/hooks";
import { toggleChristmasMood } from "../../store/Reducers/UIReducer.slice";

export const ChristmasMoodToggle: FC = () => {
const dispatch = useAppDispatch();
const [isChristmasMood, setChristmasMood] = useChristmasMood();

const handleToggleChristmasMood = (selected: boolean) => {
setChristmasMood(selected);
dispatch(toggleChristmasMood(selected));
};
return (
<Toggle checked={isChristmasMood} onValueChange={handleToggleChristmasMood}>
<span style={{ color: "#fff" }}>New Year mood</span>
</Toggle>
);
};
36 changes: 36 additions & 0 deletions src/Components/Header/Components/ChristmasHat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { SVGProps } from "react";

export const ChristmasHatSVG = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
viewBox="0 0 16 12"
width="16"
height="12"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fill="#f5f5f5"
d="M 12.737 9.488 C 14.078 8.294 13.191 7.254 12.282 6.809 C 12.139 6.743 11.985 6.704 11.827 6.689 C 10.786 6.579 9.025 7.183 7.28 7.254 C 5.042 7.348 2.526 7.057 1.372 7.446 C 1.181 7.511 1.028 7.595 0.919 7.702 C -0.444 9.043 0.463 9.934 1.372 10.38 C 2.284 10.827 5.565 10.423 8.645 9.934 C 10.094 9.706 12.307 9.87 12.737 9.488 Z"
strokeWidth="0.3"
stroke="white"
transform="matrix(0.987688, -0.156434, 0.156434, 0.987688, -1.265158, 1.173506)"
/>
<path
fill="#e53939"
d="M 0.73 7.278 C 1.884 6.888 4.4 7.179 6.639 7.085 C 8.384 7.014 10.144 6.41 11.186 6.521 C 11.639 5.133 6.83 2.495 8.003 3.07 C 8.912 3.517 11.186 5.747 12.549 6.194 C 12.549 5.747 13.457 5.302 13.457 5.302 C 11.64 3.07 8.994 -0.131 5.275 0.392 C 2.092 0.837 1.184 4.834 0.73 7.278 Z"
strokeWidth="0.3"
stroke="white"
transform="matrix(0.987688, -0.156434, 0.156434, 0.987688, -0.508147, 1.15653)"
/>
<circle
cx="13.084"
cy="4.383"
r="1.6"
stroke="white"
strokeWidth="0.3"
fill="#f5f5f5"
/>
</svg>
);
};
9 changes: 8 additions & 1 deletion src/Components/Header/Header.less
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@

.logo-link {
text-decoration: none;
position: relative;
}

.christmas-hat {
position: absolute;
left: 38px;
top: -4px;
}

.logo-img {
Expand All @@ -41,7 +48,7 @@
.menu {
margin-left: auto;

a {
> * {
margin-left: 15px;
color: #fff !important;
}
Expand Down
10 changes: 9 additions & 1 deletion src/Components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import RouterLink from "../RouterLink/RouterLink";
import svgLogo from "./moira-logo.svg";
import { AdminMenu } from "./Components/AdminMenu";
import { useSelector } from "react-redux";
import { selectPlatform } from "../../store/Reducers/ConfigReducer.slice";
import { selectIsChristmasMood, selectPlatform } from "../../store/Reducers/ConfigReducer.slice";
import { useGetConfigQuery } from "../../services/BaseApi";
import { Platform } from "../../Domain/Config";
import { ChristmasHatSVG } from "./Components/ChristmasHat";
import { ChristmasMoodToggle } from "../ChristmasMoodToggle/ChristmasMoodToggle";
import { useAppSelector } from "../../store/hooks";
import { UIState } from "../../store/selectors";
import classNames from "classnames/bind";

import styles from "./Header.less";
Expand All @@ -21,6 +25,8 @@ const cn = classNames.bind(styles);
export default function Header(): React.ReactElement {
const platform = useSelector(selectPlatform);
const { isLoading } = useGetConfigQuery();
const { isChristmasMood } = useAppSelector(UIState);
const isChristmasMoodEnabled = useSelector(selectIsChristmasMood);

return (
<header
Expand All @@ -32,9 +38,11 @@ export default function Header(): React.ReactElement {
>
<div className={cn("container")}>
<Link to={getPageLink("index")} className={cn("logo-link")}>
{isChristmasMood && <ChristmasHatSVG className={cn("christmas-hat")} />}
<img className={cn("logo-img")} src={svgLogo} alt="Moira" />
</Link>
<nav className={cn("menu")}>
{isChristmasMoodEnabled && <ChristmasMoodToggle />}
<AdminMenu />
<RouterLink to={getPageLink("teams")} icon={<PeopleIcon />}>
Teams
Expand Down
6 changes: 6 additions & 0 deletions src/Components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import React, { useRef, ReactNode, FC, useEffect } from "react";
import { Loader } from "@skbkontur/react-ui/components/Loader";
import WarningIcon from "@skbkontur/react-icons/Warning";
import { NewVersionAvailableHint } from "../NewVersionAvailableHint/NewVersionAvailableHint";
import { SnowfallBackground } from "../SnowFall/SnowFall";
import { useAppSelector } from "../../store/hooks";
import { UIState } from "../../store/selectors";
import classNames from "classnames/bind";

import styles from "./Layout.less";
Expand All @@ -27,6 +30,7 @@ export const Block: FC<IBlockProps> = ({ children, className }) => (

export const Layout: FC<ILayoutProps> = ({ loading = false, error = null, children }) => {
const scrollRef = useRef<HTMLDivElement>(null);
const { isChristmasMood } = useAppSelector(UIState);

useEffect(() => {
if (scrollRef.current) {
Expand All @@ -46,7 +50,9 @@ export const Layout: FC<ILayoutProps> = ({ loading = false, error = null, childr
<WarningIcon /> {error}
</div>
)}

<Loader className={cn("loader")} active={loading} caption="Loading">
{isChristmasMood && <SnowfallBackground topOffset={60} />}
{children}
</Loader>
<NewVersionAvailableHint />
Expand Down
Loading

0 comments on commit cbead25

Please sign in to comment.