From 36cd85f6e8a108bacb9d109942458c943a0797a6 Mon Sep 17 00:00:00 2001 From: Ruben Olano Date: Sun, 28 Sep 2025 22:39:24 -0500 Subject: [PATCH 1/2] first draft of the timeline --- config/timeline.config.ts | 63 ++++++ package-lock.json | 127 ++++++++++++ package.json | 1 + src/app/about/page.tsx | 2 + src/components/About/Divisions.tsx | 15 +- src/components/About/Timeline.tsx | 112 ++++++++++ src/components/Home/Navbar.tsx | 283 +++++++++++++++++++------- src/components/ui/navigation-menu.tsx | 120 +++++++++++ 8 files changed, 643 insertions(+), 80 deletions(-) create mode 100644 config/timeline.config.ts create mode 100644 src/components/About/Timeline.tsx create mode 100644 src/components/ui/navigation-menu.tsx diff --git a/config/timeline.config.ts b/config/timeline.config.ts new file mode 100644 index 0000000..76e22b2 --- /dev/null +++ b/config/timeline.config.ts @@ -0,0 +1,63 @@ +export type TimelineItem = { + title: string; + date: string; +}; + +export const timeline: TimelineItem[] = [ + { + title: 'ACM UTD is charted', + date: 'Circa 1996', + }, + { + title: 'Computer Science Association is formed', + date: 'Fall 2012', + }, + { + title: 'ACM merges with CSSA', + date: 'Summer 2013', + }, + { + title: 'ACM creates the first Hack_UTD', + date: 'January 17, 2015', + }, + { + title: 'ACM Projects becomes a division', + date: 'Fall 2016', + }, + { + title: 'ACM Labs starts its first projects', + date: 'Fall 2017', + }, + { + title: 'ACM Ignite Launches', + date: 'Circa 2018', + }, + { + title: 'ACM Education (v1) becomes a division', + date: 'Circa 2018', + }, + { + title: 'ACM Education start the mentor program', + date: 'January 2020', + }, + { + title: 'ACM Education start the technical interview prep program', + date: 'Fall 2020', + }, + { + title: 'ACM Development becomes a division', + date: 'May 2020', + }, + { + title: 'ACM Research becomes a division', + date: 'Fall 2020', + }, + { + title: 'ACM Media becomes a division', + date: 'September 2020', + }, + { + title: 'You join ACM!!!', + date: '?????', + }, +]; diff --git a/package-lock.json b/package-lock.json index 6e34be1..abcbaf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.13", "@radix-ui/react-hover-card": "^1.1.7", + "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", "@tanstack/react-query": "^5.85.9", @@ -1883,6 +1884,94 @@ } } }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.7.tgz", @@ -2353,6 +2442,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", @@ -2389,6 +2493,29 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", diff --git a/package.json b/package.json index 4681383..372a6ed 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.13", "@radix-ui/react-hover-card": "^1.1.7", + "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", "@tanstack/react-query": "^5.85.9", diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 3fe2b59..93d3b1d 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -3,6 +3,7 @@ import { aboutPageData } from '../../../config/about.config'; import Divisions from '@/components/About/Divisions'; import AboutHeader from '@/components/About/AboutHeader'; import { AboutPageData } from '../../../lib/types'; +import Timeline from '@/components/About/Timeline'; export default function About() { const { divisionDescription, divisions }: AboutPageData = aboutPageData; @@ -12,6 +13,7 @@ export default function About() {
+
); diff --git a/src/components/About/Divisions.tsx b/src/components/About/Divisions.tsx index 6451b9d..e393694 100644 --- a/src/components/About/Divisions.tsx +++ b/src/components/About/Divisions.tsx @@ -12,23 +12,22 @@ export default function Divisions({ data, description }: DivisionsProps) { const values = Object.values(data); const divisionCards = keys.map((division: string, index: number) => ( - + )); return ( -
+

Divisions

{description}

-
+
{divisionCards.slice(0, 6)}
-
+
{divisionCards.slice(6)}
diff --git a/src/components/About/Timeline.tsx b/src/components/About/Timeline.tsx new file mode 100644 index 0000000..e65aed2 --- /dev/null +++ b/src/components/About/Timeline.tsx @@ -0,0 +1,112 @@ +import { timeline, TimelineItem } from '../../../config/timeline.config'; +import { Calendar, Clock } from 'lucide-react'; + +export default function Timeline() { + return ( +
+
+

+ Our Journey +

+

+ Fun fact: ACM UTD is the 77th student chapter of ACM founded. ACM's history is a + little long, but what do you expect with the largest CS organization at UTD? +

+
+ +
+
+ +
+ {timeline.map((item, index) => ( + + ))} +
+
+
+ ); +} + +function TimelineItemComponent({ item, index }: { item: TimelineItem; index: number }) { + const isEven = index % 2 === 0; + const isLast = index === timeline.length - 1; + + return ( +
+
+ {isLast && ( + <> +
+
+ + )} +
+ +
+
+
+ + {item.date} +
+ +

+ {item.title} +

+ +
+ + +
+
+ +
+
+ ); +} diff --git a/src/components/Home/Navbar.tsx b/src/components/Home/Navbar.tsx index 90eaee9..4017ca6 100644 --- a/src/components/Home/Navbar.tsx +++ b/src/components/Home/Navbar.tsx @@ -7,35 +7,69 @@ import Link from 'next/link'; import { useState, useEffect } from 'react'; import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from "@/components/ui/hover-card"; + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from '@/components/ui/navigation-menu'; +import { cn } from '@/lib/utils'; -const navigation = [ - { name: 'about', href: '/about', current: true }, - { name: 'officers', href: '/officers', current: false }, - { name: 'apply', href: '/apply', current: false }, - { name: 'events', href: '/events', current: false }, - { name: 'sponsors', href: '/sponsors', current: false }, - { name: 'connect', href: '/connect', current: false }, -]; +type NavigationItem = { + name: string; + href: string; + submenu?: SubmenuItem[]; +}; + +type SubmenuItem = { + name: string; + href: string; + submenu?: DivisionItem[]; +}; + +type DivisionItem = { + name: string; + href: string; +}; -const divisions = [ - { name: 'Media', href: '/media' }, - { name: 'Research', href: '/research' }, - { name: 'Development', href: '/development' }, - { name: 'Projects', href: '/projects' }, - { name: 'Education - TIP', href: '/education/tip' }, - { name: 'Education - Mentor', href: '/education/mentor' }, - { name: 'Community', href: '/community' }, - { name: 'HackUTD', href: '/hackutd' }, - { name: 'Industry', href: '/industry' }, +const navigation: NavigationItem[] = [ + { + name: 'about', + href: '/about', + submenu: [ + { name: 'About Us', href: '/about' }, + { + name: 'Divisions', + href: '/about#divisions', + submenu: [ + { name: 'Media', href: '/media' }, + { name: 'Research', href: '/research' }, + { name: 'Development', href: '/development' }, + { name: 'Projects', href: '/projects' }, + { name: 'Education - TIP', href: '/education/tip' }, + { name: 'Education - Mentor', href: '/education/mentor' }, + { name: 'Community', href: '/community' }, + { name: 'HackUTD', href: '/hackutd' }, + { name: 'Industry', href: '/industry' }, + ], + }, + { name: 'Our Journey', href: '/about#our-journey' }, + ], + }, + { name: 'officers', href: '/officers' }, + { name: 'apply', href: '/apply' }, + { name: 'events', href: '/events' }, + { name: 'sponsors', href: '/sponsors' }, + { name: 'connect', href: '/connect' }, ]; export default function Navbar() { const [top, setTop] = useState(true); useEffect(() => { + if (typeof window === 'undefined') return; + const handleScroll = () => { setTop(window.scrollY === 0); }; @@ -47,80 +81,185 @@ export default function Navbar() { }; }, []); - const navStyles = top ? 'md:bg-opacity-0 md:bg-none bg-black/70' : 'bg-black bg-opacity-70 backdrop-blur-xl'; + const navStyles = top + ? 'md:bg-opacity-0 md:bg-none bg-black/70 md:shadow-none shadow-lg md:backdrop-blur-none backdrop-blur-sm' + : 'bg-black/80 backdrop-blur-xl border-b border-primary/10 shadow-2xl shadow-black/20 backdrop-saturate-150'; + return ( {({ open }) => ( <>
-
+
- {/* Mobile menu button*/} - + Open main menu {open ? ( -
- - Your Company + +
+ ACM Logo +
-
-
- {navigation.map((item) => ( - - - {item.name} - - {item.name === 'about' && ( - -
- {divisions.map((division) => ( - - {division.name} +
+ + + {navigation.map((item) => ( + + {item.submenu ? ( + <> + + + {item.name} - ))} -
- - )} - - ))} -
+ + +
+ {item.submenu.map((submenuItem) => ( +
+ {submenuItem.submenu ? ( +
+
+ {submenuItem.name} +
+
+ {submenuItem.submenu.map((division) => ( + + + {division.name} + + + ))} +
+
+ ) : ( + + + {submenuItem.name} + + + )} +
+ ))} +
+
+ + ) : ( + + {item.name} + + )} + + ))} + +
- -
+ +
{navigation.map((item) => ( - - {item.name} - - ))} +
+ {item.submenu ? ( + <> +
+ {item.name} +
+
+ {item.submenu.map((submenuItem) => ( +
+ {submenuItem.submenu ? ( + <> +
+ {submenuItem.name} +
+
+ {submenuItem.submenu.map((division) => ( + + {division.name} + + ))} +
+ + ) : ( + + {submenuItem.name} + + )} +
+ ))} +
+ + ) : ( + + {item.name} + + )} +
+ ))}
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx new file mode 100644 index 0000000..151f777 --- /dev/null +++ b/src/components/ui/navigation-menu.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; +import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'; +import { cva } from 'class-variance-authority'; +import { ChevronDown } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const NavigationMenu = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + +)); +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; + +const NavigationMenuItem = NavigationMenuPrimitive.Item; + +const navigationMenuTriggerStyle = cva( + 'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent', +); + +const NavigationMenuTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children}{' '} + +)); +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; + +const NavigationMenuLink = NavigationMenuPrimitive.Link; + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ +
+)); +NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName; + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +)); +NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName; + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +}; From b77a4cf0b0e3f22dd705b585fa929566f26e2d6c Mon Sep 17 00:00:00 2001 From: Ruben Olano Date: Sun, 28 Sep 2025 23:10:25 -0500 Subject: [PATCH 2/2] Add hovercard to see details and add a couple of details --- config/timeline.config.ts | 29 ++++++- src/components/About/Timeline.tsx | 85 +------------------- src/components/About/TimelineItem.tsx | 107 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 86 deletions(-) create mode 100644 src/components/About/TimelineItem.tsx diff --git a/config/timeline.config.ts b/config/timeline.config.ts index 76e22b2..c335bc3 100644 --- a/config/timeline.config.ts +++ b/config/timeline.config.ts @@ -1,63 +1,86 @@ export type TimelineItem = { title: string; date: string; + description: string; }; export const timeline: TimelineItem[] = [ { title: 'ACM UTD is charted', date: 'Circa 1996', + description: 'ACM v1, the first ACM chapter at UTD', }, { title: 'Computer Science Association is formed', date: 'Fall 2012', + description: 'Another organization is formed at UTD', }, { title: 'ACM merges with CSSA', date: 'Summer 2013', + description: 'The two organizations merge to create a larger ACM', }, { title: 'ACM creates the first Hack_UTD', date: 'January 17, 2015', + description: 'The first of our many hackathons. This also marks the beginning of ACM v2', }, { title: 'ACM Projects becomes a division', date: 'Fall 2016', + description: 'The first projects cohort creates various projects', }, { title: 'ACM Labs starts its first projects', date: 'Fall 2017', + description: 'Labs is a precursor to what would later become ACM Development', }, { title: 'ACM Ignite Launches', date: 'Circa 2018', + description: + 'ACM Ignite was a year-long program that would later be transformed into modern-day ACM Research', }, { - title: 'ACM Education (v1) becomes a division', + title: 'ACM Education becomes a division', date: 'Circa 2018', + description: 'The education program begins with brand new programs for beginners', }, { - title: 'ACM Education start the mentor program', + title: 'ACM Education starts the mentor program', date: 'January 2020', + description: 'The mentor program is started to help students with their projects', }, { - title: 'ACM Education start the technical interview prep program', + title: 'ACM Education starts the technical interview prep program (TIP)', date: 'Fall 2020', + description: + 'The technical interview prep program is started to help students with their interviews. The creation of TIP and the mentor program marks the beginning of ACM v3', }, { title: 'ACM Development becomes a division', date: 'May 2020', + description: + 'ACM Development is born to create and maintain platforms for ACM and UTD students', }, { title: 'ACM Research becomes a division', date: 'Fall 2020', + description: 'ACM Research begins its first cohort of research participants', }, { title: 'ACM Media becomes a division', date: 'September 2020', + description: 'ACM Media is born to create and maintain the media for ACM', + }, + { + title: 'ACM Community becomes a division', + date: 'Spring 2022', + description: 'ACM Community is born to create and connect with the community', }, { title: 'You join ACM!!!', date: '?????', + description: 'You can become part of our family!', }, ]; diff --git a/src/components/About/Timeline.tsx b/src/components/About/Timeline.tsx index e65aed2..a972418 100644 --- a/src/components/About/Timeline.tsx +++ b/src/components/About/Timeline.tsx @@ -1,5 +1,5 @@ -import { timeline, TimelineItem } from '../../../config/timeline.config'; -import { Calendar, Clock } from 'lucide-react'; +import { timeline } from '../../../config/timeline.config'; +import TimelineItemComponent from './TimelineItem'; export default function Timeline() { return ( @@ -29,84 +29,3 @@ export default function Timeline() {
); } - -function TimelineItemComponent({ item, index }: { item: TimelineItem; index: number }) { - const isEven = index % 2 === 0; - const isLast = index === timeline.length - 1; - - return ( -
-
- {isLast && ( - <> -
-
- - )} -
- -
-
-
- - {item.date} -
- -

- {item.title} -

- -
- - -
-
- -
-
- ); -} diff --git a/src/components/About/TimelineItem.tsx b/src/components/About/TimelineItem.tsx new file mode 100644 index 0000000..9073190 --- /dev/null +++ b/src/components/About/TimelineItem.tsx @@ -0,0 +1,107 @@ +import { TimelineItem, timeline } from '../../../config/timeline.config'; +import { Calendar } from 'lucide-react'; +import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; + +export default function TimelineItemComponent({ + item, + index, +}: { + item: TimelineItem; + index: number; +}) { + const isEven = index % 2 === 0; + const isLast = index === timeline.length - 1; + + return ( +
+
+ {isLast && ( + <> +
+
+ + )} +
+ +
+ + +
+
+ + {item.date} +
+ +

+ {item.title} +

+ +
+ + +
+
+ +

+ {item.description} +

+
+
+
+ +
+
+ ); +}