animations
This commit is contained in:
parent
bb8c202928
commit
7f84792673
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --host",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,3 +13,52 @@ export const searchMotion: Variants = {
|
|||
type: "tween",
|
||||
},
|
||||
};
|
||||
|
||||
export const searchMobileMotion: Variants = {
|
||||
rest: {
|
||||
width: "12rem",
|
||||
right: "8rem",
|
||||
type: "spring",
|
||||
borderColor: "rgba(166, 166, 166, 0)",
|
||||
},
|
||||
active: {
|
||||
width: "100%",
|
||||
right: "0",
|
||||
type: "spring",
|
||||
borderColor: "rgba(166, 166, 166, 1)",
|
||||
},
|
||||
|
||||
logoRest: {
|
||||
display: "block",
|
||||
opacity: 1,
|
||||
transform: "translateX(0%)",
|
||||
type: "spring",
|
||||
},
|
||||
logoActive: {
|
||||
opacity: 0,
|
||||
transform: "translateX(-150%)",
|
||||
type: "spring",
|
||||
},
|
||||
|
||||
langRest: {
|
||||
opacity: 1,
|
||||
transform: "translateX(0%)",
|
||||
type: "spring",
|
||||
},
|
||||
langActive: {
|
||||
opacity: 0,
|
||||
transform: "translateX(150%)",
|
||||
type: "spring",
|
||||
},
|
||||
|
||||
loopRest: {
|
||||
width: "1.3rem",
|
||||
height: "1.4rem",
|
||||
type: "spring",
|
||||
},
|
||||
loopActive: {
|
||||
width: "2rem",
|
||||
height: "2rem",
|
||||
type: "spring",
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ const MainImg = () => {
|
|||
<LazyLoadImage
|
||||
src={
|
||||
(data[0].featured_images.length > 0
|
||||
? data[0].featured_images[0]
|
||||
? data[0].featured_images[0].path
|
||||
: "") as string
|
||||
}
|
||||
alt={
|
||||
(data[0].featured_images.length > 0
|
||||
? data[0].featured_images[0]
|
||||
? data[0].featured_images[0].file_name
|
||||
: "") as string
|
||||
}
|
||||
useIntersectionObserver
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ const NewsScroll = ({ title, category }: Props) => {
|
|||
|
||||
return (
|
||||
<div className="news-scroll">
|
||||
<SectionTitle title="Лента новостей" />
|
||||
<div className="news-scroll-wrapper">
|
||||
{title === true ? (
|
||||
<SectionTitle
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Modules
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useState } from "react";
|
||||
|
|
@ -16,6 +17,9 @@ import { ILanguage, RootState } from "../../types/store.types";
|
|||
// Actions
|
||||
import { setLanguage } from "../../actions/setLanguage";
|
||||
|
||||
// Animations
|
||||
import { searchMobileMotion } from "../../animations/search.animation";
|
||||
|
||||
const languages: ILanguage[] = [
|
||||
{
|
||||
title: "RU",
|
||||
|
|
@ -28,7 +32,12 @@ const languages: ILanguage[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const LanguageSelect = () => {
|
||||
interface IProps {
|
||||
isSmall: boolean;
|
||||
isInputFocused: boolean;
|
||||
}
|
||||
|
||||
const LanguageSelect = ({ isSmall, isInputFocused }: IProps) => {
|
||||
const [dropdown, setDropdown] = useState<boolean>(false);
|
||||
|
||||
const activeLanguage = useSelector<RootState, RootState["language"]["title"]>(
|
||||
|
|
@ -41,48 +50,54 @@ const LanguageSelect = () => {
|
|||
};
|
||||
return (
|
||||
<motion.div
|
||||
className="language"
|
||||
onClick={() => setDropdown(!dropdown)}
|
||||
initial={"wrapperRest"}
|
||||
animate={dropdown ? "wrapperActive" : "wrapperRest"}
|
||||
variants={languageMotion}
|
||||
initial={isSmall ? "langRest" : {}}
|
||||
animate={isSmall ? (isInputFocused ? "langActive" : "langRest") : {}}
|
||||
variants={searchMobileMotion}
|
||||
>
|
||||
<span>{activeLanguage}</span>
|
||||
<motion.div
|
||||
className="icon-wrapper"
|
||||
initial={"arrowRest"}
|
||||
animate={dropdown ? "arrowActive" : "arrowRest"}
|
||||
className="language"
|
||||
onClick={() => setDropdown(!dropdown)}
|
||||
initial={"wrapperRest"}
|
||||
animate={dropdown ? "wrapperActive" : "wrapperRest"}
|
||||
variants={languageMotion}
|
||||
>
|
||||
<ArrowDownBlack />
|
||||
<span>{activeLanguage}</span>
|
||||
<motion.div
|
||||
className="icon-wrapper"
|
||||
initial={"arrowRest"}
|
||||
animate={dropdown ? "arrowActive" : "arrowRest"}
|
||||
variants={languageMotion}
|
||||
>
|
||||
<ArrowDownBlack />
|
||||
</motion.div>
|
||||
<motion.ul
|
||||
className="language-dropdown"
|
||||
variants={languageMotion}
|
||||
initial="rest"
|
||||
animate={dropdown ? "active" : "rest"}
|
||||
>
|
||||
{languages.map((language: ILanguage) => {
|
||||
return (
|
||||
<li key={uuidv4()}>
|
||||
<motion.button
|
||||
type="button"
|
||||
initial={{
|
||||
background: "#ffffff",
|
||||
type: "tween",
|
||||
}}
|
||||
whileHover={{
|
||||
background: "#f1f1f1",
|
||||
type: "spring",
|
||||
}}
|
||||
onClick={() => onLanguageClick(language.title)}
|
||||
>
|
||||
{language.title}
|
||||
</motion.button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</motion.ul>
|
||||
</motion.div>
|
||||
<motion.ul
|
||||
className="language-dropdown"
|
||||
variants={languageMotion}
|
||||
initial="rest"
|
||||
animate={dropdown ? "active" : "rest"}
|
||||
>
|
||||
{languages.map((language: ILanguage) => {
|
||||
return (
|
||||
<li key={uuidv4()}>
|
||||
<motion.button
|
||||
type="button"
|
||||
initial={{
|
||||
background: "#ffffff",
|
||||
type: "tween",
|
||||
}}
|
||||
whileHover={{
|
||||
background: "#f1f1f1",
|
||||
type: "spring",
|
||||
}}
|
||||
onClick={() => onLanguageClick(language.title)}
|
||||
>
|
||||
{language.title}
|
||||
</motion.button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</motion.ul>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
// Modules
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
// Icons
|
||||
import { ReactComponent as Logo } from "../../assets/icons/logo.svg";
|
||||
import { ReactComponent as Instagram } from "../../assets/icons/insta-black.svg";
|
||||
|
|
@ -10,48 +13,94 @@ import { ReactComponent as TikTok } from "../../assets/icons/tiktok-black.svg";
|
|||
import Search from "./Search";
|
||||
import LanguageSelect from "./LanguageSelect";
|
||||
|
||||
// Hooks
|
||||
import useMediaQuery from "../../hooks/useMediaQuery";
|
||||
|
||||
// Animation
|
||||
import { searchMobileMotion } from "../../animations/search.animation";
|
||||
|
||||
const Nav = () => {
|
||||
const isSmall = useMediaQuery("(max-width: 850px)");
|
||||
const [isInputFocused, setIsInputFocused] = useState<boolean>(false);
|
||||
return (
|
||||
<nav className="nav">
|
||||
<div className="container">
|
||||
<div className="nav-inner">
|
||||
<div className="nav-left">
|
||||
<Search />
|
||||
<LanguageSelect />
|
||||
{!isSmall ? (
|
||||
<div className="nav-inner">
|
||||
<div className="nav-left">
|
||||
<Search
|
||||
isSmall={isSmall}
|
||||
isInputFocused={isInputFocused}
|
||||
setIsInputFocused={setIsInputFocused}
|
||||
/>
|
||||
|
||||
<LanguageSelect
|
||||
isSmall={isSmall}
|
||||
isInputFocused={isInputFocused}
|
||||
/>
|
||||
</div>
|
||||
<div className="nav-mid">
|
||||
<Link to="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav-right">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<Instagram />
|
||||
<span>hhm@inst.com</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<Facebook />
|
||||
<span>hhm@face.com</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<TikTok />
|
||||
<span>@hhm</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="nav-mid">
|
||||
<Link to="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav-right">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<Instagram />
|
||||
<span>hhm@inst.com</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<Facebook />
|
||||
<span>hhm@face.com</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" target={"_blank"}>
|
||||
<TikTok />
|
||||
<span>@hhm</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="nav-burger">
|
||||
<span className="line line-1"></span>
|
||||
<span className="line line-2"></span>
|
||||
<span className="line line-3"></span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<motion.div className="nav-inner mobile">
|
||||
<motion.div
|
||||
className="nav-mid"
|
||||
initial={isSmall ? "logoRest" : {}}
|
||||
animate={
|
||||
isSmall ? (isInputFocused ? "logoActive" : "logoRest") : {}
|
||||
}
|
||||
variants={searchMobileMotion}
|
||||
>
|
||||
<Link to="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="search-wrap"
|
||||
initial={isSmall ? "rest" : {}}
|
||||
animate={isSmall ? (isInputFocused ? "active" : "rest") : {}}
|
||||
variants={searchMobileMotion}
|
||||
>
|
||||
<Search
|
||||
isSmall={isSmall}
|
||||
isInputFocused={isInputFocused}
|
||||
setIsInputFocused={setIsInputFocused}
|
||||
/>
|
||||
</motion.div>
|
||||
<div className="lang-wrap">
|
||||
<LanguageSelect
|
||||
isSmall={isSmall}
|
||||
isInputFocused={isInputFocused}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
// Modules
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
|
||||
// Animations
|
||||
import { searchMotion } from "../../animations/search.animation";
|
||||
import {
|
||||
searchMotion,
|
||||
searchMobileMotion,
|
||||
} from "../../animations/search.animation";
|
||||
|
||||
// Icons
|
||||
import { ReactComponent as Loop } from "../../assets/icons/loop.svg";
|
||||
|
|
@ -14,10 +18,17 @@ import { RootState } from "../../types/store.types";
|
|||
// Actions
|
||||
import { setSearch } from "../../actions/setSearch";
|
||||
|
||||
const Search = () => {
|
||||
interface IProps {
|
||||
isSmall: boolean;
|
||||
isInputFocused: boolean;
|
||||
setIsInputFocused: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Search = ({ isSmall, isInputFocused, setIsInputFocused }: IProps) => {
|
||||
const onInputChange = (value: string) => {
|
||||
dispatch(setSearch(value));
|
||||
};
|
||||
|
||||
// redux
|
||||
const dispatch = useDispatch();
|
||||
const inputValue = useSelector<RootState, RootState["search"]["value"]>(
|
||||
|
|
@ -37,14 +48,21 @@ const Search = () => {
|
|||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onInputChange(event.target.value)
|
||||
}
|
||||
onFocus={() => {
|
||||
setIsInputFocused(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsInputFocused(false);
|
||||
}}
|
||||
/>
|
||||
<motion.div
|
||||
className="search-content"
|
||||
initial={"rest"}
|
||||
animate={inputValue.length > 0 ? "active" : "rest"}
|
||||
variants={searchMotion}
|
||||
>
|
||||
<Loop />
|
||||
<span>Search anything...</span>
|
||||
<span>Search</span>
|
||||
</motion.div>
|
||||
</form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Modules
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
|
||||
import { Navigation, Pagination, Autoplay } from "swiper";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
|
|
@ -13,12 +13,13 @@ interface IProps {
|
|||
}
|
||||
|
||||
const NewsArticleSlider = ({ images }: IProps) => {
|
||||
let loop = images.length > 1;
|
||||
return (
|
||||
<div className="news-article-slider">
|
||||
<Swiper
|
||||
spaceBetween={24}
|
||||
slidesPerView={1}
|
||||
loop
|
||||
loop={loop}
|
||||
autoplay={{
|
||||
delay: 3000,
|
||||
disableOnInteraction: false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Modules
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useMediaQuery = (query: string): boolean => {
|
||||
const [matches, setMatches] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query);
|
||||
if (media.matches !== matches) {
|
||||
setMatches(media.matches);
|
||||
}
|
||||
const listener = () => {
|
||||
setMatches(media.matches);
|
||||
};
|
||||
media.addListener(listener);
|
||||
return () => media.removeListener(listener);
|
||||
}, [matches, query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export default useMediaQuery;
|
||||
|
|
@ -21,7 +21,7 @@ const Main = () => {
|
|||
<div className="news-inner">
|
||||
<MainContent />
|
||||
<div className="news-outer-wrapper">
|
||||
<NewsScroll title={false} />
|
||||
<NewsScroll title={true} />
|
||||
<Aside />
|
||||
</div>
|
||||
<Videos />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap');
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Raleway&display=swap");
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
|
|
@ -16,6 +16,7 @@ html {
|
|||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
.nav-inner {
|
||||
position: relative;
|
||||
padding: 3rem 0;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
|
@ -6,17 +7,44 @@
|
|||
gap: 2.4rem;
|
||||
justify-content: space-between;
|
||||
border-bottom: 0.1rem solid $black;
|
||||
z-index: 2;
|
||||
|
||||
&.mobile {
|
||||
min-height: 10rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
justify-content: space-between;
|
||||
|
||||
.nav-mid {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 3rem;
|
||||
}
|
||||
.search-wrap {
|
||||
position: absolute;
|
||||
right: 8rem;
|
||||
top: 3rem;
|
||||
}
|
||||
.lang-wrap {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 0.1rem solid $gray-dark;
|
||||
border-radius: 0.5rem;
|
||||
input {
|
||||
border: 0.1rem solid $gray-dark;
|
||||
padding: 1rem 1.4rem;
|
||||
font-size: 1.6rem;
|
||||
color: $black;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
div {
|
||||
|
|
@ -37,6 +65,7 @@
|
|||
}
|
||||
|
||||
.nav-left {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
|
|
@ -162,39 +191,34 @@
|
|||
|
||||
// Media
|
||||
@media screen and (max-width: 850px) {
|
||||
// .nav-burger {
|
||||
// display: block;
|
||||
// width: 3rem;
|
||||
// height: 3rem;
|
||||
|
||||
// .line {
|
||||
// position: absolute;
|
||||
// width: 100%;
|
||||
// border-radius: 1rem;
|
||||
// background: $black;
|
||||
// height: 0.33rem;
|
||||
|
||||
// &-1 {
|
||||
// top: 15%;
|
||||
// }
|
||||
// &-2 {
|
||||
// top: 50%;
|
||||
// transform: translateY(-50%);
|
||||
// }
|
||||
// &-3 {
|
||||
// top: 75%;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
.search,
|
||||
.nav-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
justify-content: flex-end;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 1rem 0.8rem;
|
||||
}
|
||||
|
||||
div {
|
||||
left: 0.8rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-inner {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@
|
|||
color: $black;
|
||||
}
|
||||
|
||||
.news-image {
|
||||
img {
|
||||
@include wh100;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.news-title {
|
||||
font-size: 2rem;
|
||||
|
|
|
|||
Loading…
Reference in New Issue