From b6797311d578b97aebff306d9f9c767824733396 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Mon, 18 Mar 2024 19:32:43 -0500 Subject: [PATCH] UI / responsiveness improvements --- src/components/banner/HeroBanner.js | 26 ++++---- src/components/courses/CoursesCarousel.js | 45 ++++++++------ src/components/navbar/Navbar.js | 6 +- src/components/navbar/user/UserAvatar.js | 3 + src/components/workshops/WorkshopsCarousel.js | 62 ++++++++++++++++--- src/hooks/useWindowWidth.js | 26 ++++++++ src/pages/_app.js | 9 +-- src/pages/_document.js | 30 +++++---- src/styles/globals.css | 27 +++++++- tailwind.config.js | 3 + 10 files changed, 176 insertions(+), 61 deletions(-) create mode 100644 src/hooks/useWindowWidth.js diff --git a/src/components/banner/HeroBanner.js b/src/components/banner/HeroBanner.js index f099e34..88321fb 100644 --- a/src/components/banner/HeroBanner.js +++ b/src/components/banner/HeroBanner.js @@ -4,19 +4,21 @@ import Image from 'next/image'; const HeroBanner = () => { const options = ['Bitcoin', 'Lightning', 'Nostr']; const [currentOption, setCurrentOption] = useState(0); - const [fade, setFade] = useState(true); + const [isFlipping, setIsFlipping] = useState(false); useEffect(() => { const interval = setInterval(() => { - setFade(false); + setIsFlipping(true); setTimeout(() => { setCurrentOption((prevOption) => (prevOption + 1) % options.length); - setFade(true); - }, 700); // Half the interval time - }, 1500); // Change text every 2 seconds - + setTimeout(() => { + setIsFlipping(false); + }, 400); // Start preparing to flip back a bit before the halfway point + }, 400); // Update slightly before the midpoint for smoother transition + }, 2500); // Increased to provide a slight pause between animations for readability + return () => clearInterval(interval); - }, []); + }, []); return (
@@ -27,16 +29,16 @@ const HeroBanner = () => { height={1080} quality={100} /> -
-

Learn how to code

-

+

+

Learn how to code

+

Build{' '} - + {options[currentOption]} {' '}apps

-

Become a Bitcoin developer

+

Become a Bitcoin developer

); diff --git a/src/components/courses/CoursesCarousel.js b/src/components/courses/CoursesCarousel.js index 070d65b..7db2af6 100644 --- a/src/components/courses/CoursesCarousel.js +++ b/src/components/courses/CoursesCarousel.js @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react'; -import { Button } from 'primereact/button'; import { Carousel } from 'primereact/carousel'; import { useRouter } from 'next/router'; import Image from 'next/image'; @@ -38,31 +37,29 @@ export default function CoursesCarousel() { useEffect(() => { // Update the state to the current window width setScreenWidth(window.innerWidth); - + const handleResize = () => { // Update the state to the new window width when it changes setScreenWidth(window.innerWidth); }; - + window.addEventListener('resize', handleResize); - + // Remove the event listener on cleanup return () => window.removeEventListener('resize', handleResize); }, []); // The empty array ensures this effect only runs once, similar to componentDidMount - - // Function to calculate image dimensions based on screenWidth + const calculateImageDimensions = () => { if (screenWidth >= 1200) { // Large screens - return { width: 344, height: 194 }; + return { width: 426, height: 240 }; } else if (screenWidth >= 768 && screenWidth < 1200) { // Medium screens - return { width: 300, height: 169 }; + return { width: 344, height: 194 }; } else { - console.log('screenWidth:', screenWidth); // Small screens - return { width: screenWidth - 30, height: (screenWidth - 30) * (9 / 16) }; + return { width: screenWidth - 50, height: (screenWidth - 50) * (9 / 16) }; } }; @@ -77,24 +74,32 @@ export default function CoursesCarousel() { const courseTemplate = (course) => { const { width, height } = calculateImageDimensions(); - console.log('width:', width); - console.log('height:', height); - return ( -
router.push(`/details/${course.id}`)} className="flex flex-col items-center w-full mx-auto px-4 cursor-pointer mt-8"> -
+
router.push(`/details/${course.id}`)} className="flex flex-col items-center mx-auto px-4 cursor-pointer mt-8 rounded-md shadow-lg"> +
resource thumbnail -
-
-

{course.title}

-

{course.summary}

-

Published: {formatTimestampToHowLongAgo(course.published_at)}

+
+

{course.title}

+

+ {course.summary} +

+

Published: {formatTimestampToHowLongAgo(course.published_at)}

+
); diff --git a/src/components/navbar/Navbar.js b/src/components/navbar/Navbar.js index 2fb2512..7123a33 100644 --- a/src/components/navbar/Navbar.js +++ b/src/components/navbar/Navbar.js @@ -68,11 +68,11 @@ const Navbar = () => { const start = (
-
+ {/*
menu.current.toggle(e)}> -
+
*/}
router.push('/')} className="flex flex-row items-center justify-center cursor-pointer"> logo {
diff --git a/src/components/navbar/user/UserAvatar.js b/src/components/navbar/user/UserAvatar.js index 9b9e51b..1ee7f5e 100644 --- a/src/components/navbar/user/UserAvatar.js +++ b/src/components/navbar/user/UserAvatar.js @@ -6,6 +6,7 @@ import { Button } from 'primereact/button'; import { Menu } from 'primereact/menu'; import { useSelector, useDispatch } from 'react-redux'; import { setUser } from '@/redux/reducers/userReducer'; +import useWindowWidth from '@/hooks/useWindowWidth'; import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; import styles from '../navbar.module.css'; @@ -15,6 +16,7 @@ const UserAvatar = () => { const dispatch = useDispatch(); const user = useSelector((state) => state.user.user); const { returnImageProxy } = useImageProxy(); + const windowWidth = useWindowWidth(); const menu = useRef(null); @@ -71,6 +73,7 @@ const UserAvatar = () => { className="text-[#f8f8ff]" rounded onClick={() => router.push('/login')} + size={windowWidth < 768 ? 'small' : 'normal'} /> ); } diff --git a/src/components/workshops/WorkshopsCarousel.js b/src/components/workshops/WorkshopsCarousel.js index f4b3668..f456c53 100644 --- a/src/components/workshops/WorkshopsCarousel.js +++ b/src/components/workshops/WorkshopsCarousel.js @@ -28,10 +28,40 @@ const responsiveOptions = [ export default function WorkshopsCarousel() { const workshops = useSelector((state) => state.events.resources); const [processedWorkshops, setProcessedWorkshops] = useState([]); + const [screenWidth, setScreenWidth] = useState(null); const { returnImageProxy } = useImageProxy(); const router = useRouter(); + useEffect(() => { + // Update the state to the current window width + setScreenWidth(window.innerWidth); + + const handleResize = () => { + // Update the state to the new window width when it changes + setScreenWidth(window.innerWidth); + }; + + window.addEventListener('resize', handleResize); + + // Remove the event listener on cleanup + return () => window.removeEventListener('resize', handleResize); + }, []); // The empty array ensures this effect only runs once, similar to componentDidMount + + + const calculateImageDimensions = () => { + if (screenWidth >= 1200) { + // Large screens + return { width: 426, height: 240 }; + } else if (screenWidth >= 768 && screenWidth < 1200) { + // Medium screens + return { width: 344, height: 194 }; + } else { + // Small screens + return { width: screenWidth - 50, height: (screenWidth - 50) * (9 / 16) }; + } + }; + useEffect(() => { const processWorkshops = workshops.map(workshop => { const { id, content, title, summary, image, published_at } = parseEvent(workshop); @@ -41,21 +71,33 @@ export default function WorkshopsCarousel() { }, [workshops]); const workshopTemplate = (workshop) => { + const { width, height } = calculateImageDimensions(); return ( -
router.push(`/details/${workshop.id}`)} className="flex flex-col items-center w-full mx-auto px-4 cursor-pointer mt-8"> -
+
router.push(`/details/${workshop.id}`)} className="flex flex-col items-center mx-auto px-4 cursor-pointer mt-8 rounded-md shadow-lg"> +
resource thumbnail -
-
-

{workshop.title}

-

{workshop.summary}

-

Published: {formatTimestampToHowLongAgo(workshop.published_at)}

+
+

{workshop.title}

+

+ {workshop.summary} +

+

Published: {formatTimestampToHowLongAgo(workshop.published_at)}

+
); diff --git a/src/hooks/useWindowWidth.js b/src/hooks/useWindowWidth.js new file mode 100644 index 0000000..8e645f0 --- /dev/null +++ b/src/hooks/useWindowWidth.js @@ -0,0 +1,26 @@ +import { useState, useEffect } from 'react'; + +const useWindowWidth = () => { + const [windowWidth, setWindowWidth] = useState(undefined); + + useEffect(() => { + // Handler to call on window resize + function handleResize() { + // Set window width to state + setWindowWidth(window.innerWidth); + } + + // Add event listener + window.addEventListener('resize', handleResize); + + // Call handler right away so state gets updated with initial window size + handleResize(); + + // Remove event listener on cleanup + return () => window.removeEventListener('resize', handleResize); + }, []); // Empty array ensures that effect is only run on mount and unmount + + return windowWidth; +}; + +export default useWindowWidth; diff --git a/src/pages/_app.js b/src/pages/_app.js index 580aa94..02148db 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -18,12 +18,13 @@ export default function MyApp({
-
- -
+ {/*
*/} + {/* */} + {/*
*/} +
-
+ {/*
*/}
diff --git a/src/pages/_document.js b/src/pages/_document.js index 54e8bf3..61eaabe 100644 --- a/src/pages/_document.js +++ b/src/pages/_document.js @@ -1,13 +1,21 @@ -import { Html, Head, Main, NextScript } from 'next/document' +import Document, { Html, Head, Main, NextScript } from 'next/document'; -export default function Document() { - return ( - - - -
- - - - ) +class MyDocument extends Document { + render() { + return ( + + + + + + + +
+ + + + ); + } } + +export default MyDocument; diff --git a/src/styles/globals.css b/src/styles/globals.css index db89f81..04b107f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -9,6 +9,11 @@ @tailwind utilities; } +body { + font-family: 'Blinker', sans-serif; +} + + .p-tabmenu .p-tabmenu-nav { background-color: transparent !important; border: none !important; @@ -25,4 +30,24 @@ .p-button .pi.pi-bolt { color: yellow; -} \ No newline at end of file +} + +/* hero banner animation */ +@keyframes flip { + 0%, 100% { + transform: rotateX(0); + opacity: 1; + } + 45%, 60% { /* Adjusted for quicker opacity transition */ + transform: rotateX(180deg); + opacity: 0; + } +} + +.flip-enter-active, .flip-exit-active { + animation-name: flip; + animation-duration: 800ms; /* Keep as is for smooth transition */ + animation-timing-function: ease-in-out; + transform-origin: center center; + animation-fill-mode: forwards; /* Ensures the end state of the animation is retained */ +} diff --git a/tailwind.config.js b/tailwind.config.js index 43a07d7..d4b3319 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -15,6 +15,9 @@ module.exports = { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, + fontFamily: { + 'blinker': ['Blinker', 'sans-serif'], + }, }, }, plugins: [],