From 7be958b05cf328deb2b0261e4b367d0833cf8194 Mon Sep 17 00:00:00 2001 From: Monica Hackney Date: Tue, 8 Apr 2025 13:23:29 -0400 Subject: [PATCH] animation module workshop --- src/components/Header/Header.jsx | 39 ++++------ src/components/MobileMenu/MobileMenu.jsx | 99 ++++++++++++++++-------- src/components/Navlink/Navlink.jsx | 60 ++++++++++++++ src/components/Navlink/index.js | 1 + src/components/ShoeCard/ShoeCard.jsx | 52 ++++++++----- 5 files changed, 176 insertions(+), 75 deletions(-) create mode 100644 src/components/Navlink/Navlink.jsx create mode 100644 src/components/Navlink/index.js diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index ad301e5..3068a72 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,13 +1,14 @@ -import React from 'react'; -import styled from 'styled-components'; - -import { QUERIES, WEIGHTS } from '../../constants'; -import Logo from '../Logo'; -import Icon from '../Icon'; -import UnstyledButton from '../UnstyledButton'; -import SuperHeader from '../SuperHeader'; -import MobileMenu from '../MobileMenu'; -import VisuallyHidden from '../VisuallyHidden'; +import React from "react"; +import styled from "styled-components"; + +import { QUERIES } from "../../constants"; +import Logo from "../Logo"; +import Icon from "../Icon"; +import UnstyledButton from "../UnstyledButton"; +import SuperHeader from "../SuperHeader"; +import MobileMenu from "../MobileMenu"; +import VisuallyHidden from "../VisuallyHidden"; +import NavLink from "../Navlink"; const Header = () => { const [showMobileMenu, setShowMobileMenu] = React.useState(false); @@ -81,6 +82,12 @@ const DesktopNav = styled.nav` } `; +const HoverDesktopNav = styled.nav` + display: none; + &:hover: + +`; + const MobileActions = styled.div` display: none; @@ -114,16 +121,4 @@ const Filler = styled.div` } `; -const NavLink = styled.a` - font-size: 1.125rem; - text-transform: uppercase; - text-decoration: none; - color: var(--color-gray-900); - font-weight: ${WEIGHTS.medium}; - - &:first-of-type { - color: var(--color-secondary); - } -`; - export default Header; diff --git a/src/components/MobileMenu/MobileMenu.jsx b/src/components/MobileMenu/MobileMenu.jsx index f402df3..917f8b0 100644 --- a/src/components/MobileMenu/MobileMenu.jsx +++ b/src/components/MobileMenu/MobileMenu.jsx @@ -1,12 +1,12 @@ -import React from 'react'; -import styled from 'styled-components'; -import * as Dialog from '@radix-ui/react-dialog'; +import React from "react"; +import styled, { keyframes } from "styled-components"; +import * as Dialog from "@radix-ui/react-dialog"; -import { QUERIES, WEIGHTS } from '../../constants'; +import { QUERIES, WEIGHTS } from "../../constants"; -import UnstyledButton from '../UnstyledButton'; -import Icon from '../Icon'; -import VisuallyHidden from '../VisuallyHidden'; +import UnstyledButton from "../UnstyledButton"; +import Icon from "../Icon"; +import VisuallyHidden from "../VisuallyHidden"; const MobileMenu = ({ isOpen, onDismiss }) => { return ( @@ -14,57 +14,92 @@ const MobileMenu = ({ isOpen, onDismiss }) => { - - - Dismiss menu - - - Mobile navigation - Mobile navigation - - - -
- Terms and Conditions - Privacy Policy - Contact Us -
+ + + + Dismiss menu + + + Mobile navigation + Mobile navigation + + + +
+ Terms and Conditions + Privacy Policy + Contact Us +
+
); }; +const fadeIn = keyframes` + from { + opacity: 0; + } + to { + opacity: 1; + } +`; +const slideIn = keyframes` + from { + transform: translateX(100%); + } + to { + transform: translateX(0%); + } +`; + const Overlay = styled(Dialog.Overlay)` position: fixed; inset: 0; background: var(--color-backdrop); + animation: ${fadeIn} 750ms; `; const Content = styled(Dialog.Content)` + --overfill: 16px; position: fixed; top: 0; right: 0; bottom: 0; - background: white; - width: 300px; + display: flex; + flex-direction: column; + width: calc(300px + var(--overfill)); + margin-right: calc(var(--overfill) * -1); height: 100%; padding: 24px 32px; + background: white; + + @media (prefers-reduced-motion: no-preference) { + animation: ${slideIn} 500ms both cubic-bezier(0, 0.6, 0.32, 1.06); + animation-delay: 200ms; + } +`; + +const Wrapper = styled.div` display: flex; flex-direction: column; + height: 100%; + animation: ${fadeIn} 600ms both; + animation-delay: 400ms; `; const CloseButton = styled(UnstyledButton)` position: absolute; top: 10px; - right: 0; + right: var(--overfill); padding: 16px; `; diff --git a/src/components/Navlink/Navlink.jsx b/src/components/Navlink/Navlink.jsx new file mode 100644 index 0000000..5ab3a49 --- /dev/null +++ b/src/components/Navlink/Navlink.jsx @@ -0,0 +1,60 @@ +import React from "react"; +import styled from "styled-components"; + +import { WEIGHTS } from "../../constants"; + +const NavLink = ({ children, ...delegated }) => { + return ( + + {children} + {children} + + ); +}; + +const Wrapper = styled.a` + position: relative; + display: block; + font-size: 1.125rem; + text-transform: uppercase; + text-decoration: none; + color: var(--color-gray-900); + font-weight: ${WEIGHTS.medium}; + /* Text slide-up effect */ + overflow: hidden; + + &:first-of-type { + color: var(--color-secondary); + } +`; + +const Text = styled.span` + display: block; + transform: translateY(var(--translate-from)); + transition: transform 500ms; + + @media (prefers-reduced-motion: no-preference) { + ${Wrapper}:hover & { + transition: transform 250ms; + transform: translateY(var(--translate-to)); + } + } +`; + +const MainText = styled(Text)` + --translate-from: 0%; + --translate-to: -100%; +`; + +const HoverText = styled(Text)` + --translate-from: 100%; + --translate-to: 0%; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + font-weight: ${WEIGHTS.bold}; +`; + +export default NavLink; diff --git a/src/components/Navlink/index.js b/src/components/Navlink/index.js new file mode 100644 index 0000000..ef10bab --- /dev/null +++ b/src/components/Navlink/index.js @@ -0,0 +1 @@ +export { default } from "./Navlink"; diff --git a/src/components/ShoeCard/ShoeCard.jsx b/src/components/ShoeCard/ShoeCard.jsx index b33d371..00c6a13 100644 --- a/src/components/ShoeCard/ShoeCard.jsx +++ b/src/components/ShoeCard/ShoeCard.jsx @@ -1,9 +1,9 @@ -import React from 'react'; -import styled from 'styled-components'; +import React from "react"; +import styled from "styled-components"; -import { WEIGHTS } from '../../constants'; -import { formatPrice, pluralize, isNewShoe } from '../../utils'; -import Spacer from '../Spacer'; +import { WEIGHTS } from "../../constants"; +import { formatPrice, pluralize, isNewShoe } from "../../utils"; +import Spacer from "../Spacer"; const ShoeCard = ({ slug, @@ -36,30 +36,26 @@ const ShoeCard = ({ - {variant === 'on-sale' && Sale} - {variant === 'new-release' && ( - Just released! - )} + {variant === "on-sale" && Sale} + {variant === "new-release" && Just released!} {name} {formatPrice(price)} - {pluralize('Color', numOfColors)} - {variant === 'on-sale' ? ( + {pluralize("Color", numOfColors)} + {variant === "on-sale" ? ( {formatPrice(salePrice)} ) : undefined} @@ -73,15 +69,29 @@ const Link = styled.a` color: inherit; `; -const Wrapper = styled.article``; +const Wrapper = styled.article` + position: relative; +`; const ImageWrapper = styled.div` - position: relative; + border-radius: 16px 16px 4px 4px; + overflow: hidden; `; const Image = styled.img` + display: block; width: 100%; - border-radius: 16px 16px 4px 4px; + transform-origin: 50% 75%; + transition: transform 500ms; + will-change: transform; + @media (hover: hover) and (prefers-reduced-motion: no-preference) { + ${Link}:hover &, + ${Link}:focus & { + transform: scale(1.1); + transition: transform 200ms; + opacity: 75%; + } + } `; const Row = styled.div` @@ -92,7 +102,7 @@ const Row = styled.div` const Name = styled.h3` font-weight: ${WEIGHTS.medium}; - color: var(--color-gray-900); + color: var(--color-gray-900);\ `; const Price = styled.span`